By 高焕堂
ee ee
框架创建Activity对象时,如何取得子类名称呢?
1. 复习:框架的主控权
我们已经说明了,透过基类和API可以让框架有效地「框住」应用子类。例如,在下图里,基类Activity提供了抽象或可覆写的函数(如图里的onCreate()函数)来框住myActivity子类。此时,框架基类获得高达(0.8 : 0.2)的主导性。
此外,如果具备另一个条件:由框架来创建应用子类的对象;则框架基类就能获得(1.0 : 0.0)的高度主导性了。如下图:
图1、绝对强势的制约力量
此时,框架基类具有绝对的主导性,其制约力量强弱程度比率为:
基类:子类 ==》1.0 : 0.0
也就是具有极强大的制约力量。值得留意的是,不一定是由自己亲属的基类(如Activity或Activity的父类)来创建子类(如myActivity)对象。常常是由框架里的其它基类来创建。例如,Android框架的基类与应用子类之间,就实现了上述(1.0 : 0.0)的强大制约力量,让Android框架(内含基类)拥有制高点(即主控权)。
2. AndroidManifest.xml文件的特殊用途
在Android框架里有4个嫡系基类:Activity、Service、BroadcastReceiver和ContentProvider,其各自的任务为:包括:
- Activity: 处理UI互动的事情
- Service: 幕后服务(无关于UI的服务)
- BroadcastReceiver: 接收讯息及事件处理
- ContentProvider: 储存共享数据
如下图所示:
图2、 Android框架里的四大台柱
基于这些基类,可以撰写AP子类,如下图:
图3、基类的子类成为应用软件的核心要素
其中,值得留意的事:谁来创建这些子类的对象呢? 答案是:Android框架。这样让Android框架能有绝对的制约能力去「框住」应用子类的结构和行为。接着,请你来思考一个有趣的问题:
- 框架开发者(强龙)先写框架基类的程序码;
- 然后,AP开发者(地头蛇)才撰写应用子类在后。
那么框架开发者事先又如何知道地头蛇后来撰写的应用子类的名称呢? 如果不知道应用子类的名称,又如何创建应用子类的对象呢? 此时,AndroidManifest.xml文件就派上场了。例如:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.misoo.pkm">
<uses-permission xmlns:android="http://schemas.android.com/apk/res/android"
android:name="android.permission.INTERNET">
</uses-permission>
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".FirstActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".LoadActivity">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service android:name=".LoadService" android:process=":remote">
<intent-filter>
<action android:name="com.misoo.pkm.REMOTE_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>
这个AndroidManifest.xml就是一种配置(Configuration)文件,提供程序执行时的各项配置参数值。由于「框架基类撰写」、「应用子类撰写」和「创建对象」三个任务时间是有先后顺序的,如下图:
图4、由子类开发者撰写AndroidManifest.xml文件
AP开发者把子类别的名称写入到AndroidManifest.xml文件里,等到执行时刻(Run-time),Android框架会去读取这个文件。于是框架得知有3个嫡系应用子类,如下图:
图5、框架参考AndroidManifest.xml文件来创建应用子类的对象
Android框架从AndroidManifest.xml文件里读取子类别的名称,以及其它关于子类的配置参数信息,就能顺利创建对象了。例如,上图里的各应用子类的对象可以全部创建于同一个进程(Process)里,也可以创建于不同的进程里。例如,Android框架从上述文件里读到:
<service android:name=".LoadService" android:process=":remote">
框架就会将子类别LoadService的对象创建于独立的进程(名称叫“remote”)里。于是,FirstActivity与LoadService之间就属于跨进程的沟通了。这种跨进程的沟通,就是大家熟知的IPC(Inter-Process Communication)机制了。 [歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
3. 让你的框架来创建App的对象
3.1 由框架创建子类的对象
例如,下图里的范例。如果你仔细观察其对象的创建情形,就能绘制其架构图如下:
图6、由子类创建另一个子类的对象并不是最佳设计
这个myDrawing对象是由myActivity所创建的。如果能由框架基类来创建它,就能大大强化框架的主控权。如下图所示:
图7、由框架的GraphView基类来创建myDrawing子类的对象
此刻,会遇到基类开发发者如何知道子类名称的问题。在GraphView基类里,可以使用下述两个指令来创建myDrawing对象:
- 使用new myDrawing()指令。
- 使用newInstance(“myDrawing”)指令。
使用这两个指令的缺点是:框架开发者必须与AP开发者沟通才能得知子类名称为:myDrawing。这意味着,框架开发者必须等到AP开发者决定子类名称之后,才能完成框架基类的开发。这大大违背框架基类先开发的前提条件。此时,可以采用配置(或资源)文件的方法,将“myDrawing”字符串写入到/res/values/strings.xml里。当然也能写入到其它资源或配置文件里。如下图所示:
图8、框架基类在执行时刻读取strings.xml资源档
此图呈现出,myActivity创建GraphView对象,然后GraphView透过GraphActivity基类而读取strings.xml资源文件来取的子类别的名称字符串。GraphView就呼叫newInstance()函数来创建子类别的对象。接下来,就展开实际绘图的动作了,如下图所示:
图9、对象创建完成,就展开实际绘图的流程
由Android的View基类反向呼叫GarphView的onDraw(),再呼叫到myDrawing的onDraw()函数,就完成绘图的任务了。
3.2 撰写代码:<创建对象>的实践途径
兹将上图落实为Android代码,其执行画面如下:
首先建立一个Android的Ex04_01项目(Project),如下:
背景图像sunrise.png存在/res里。
- 撰写你的框架基类和API
// strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, myActivity! </string>
<string name="app_name">Ex04_01</string>
<string name="drawingclass">com.misoo.pk001.myDrawing</string>
</resources>
// GraphView.java
package myFramework;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class GraphView extends View{
private IDraw fgDrawer;
private Bitmap bm;
public GraphView(Context context, Bitmap bmp) {
super(context);
bm = bmp;
String classname = ((GraphActivity)context).getDrawingClass();
try {
fgDrawer = (IDraw)Class.forName(classname).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.drawBackground(canvas);
fgDrawer.doDraw(canvas);
}
protected void drawBackground(Canvas canvas){
Paint paint= new Paint();
canvas.drawBitmap(bm, null , new Rect(0, 0, 250, 280), paint);
}
}
// GraphActivity.java
package myFramework;
import com.misoo.pk001.R;
import android.app.Activity;
public abstract class GraphActivity extends Activity{
public String getDrawingClass(){
return this.getResources().getString(R.string.drawingclass);
}
}
// IDraw.java
package myFramework;
import android.graphics.Canvas;
public interface IDraw {
void doDraw(Canvas canvas);
}
- 把基类和API送人,协助别人去开发应用子类
// myDrawing.java
package com.misoo.pk001;
import myFramework.IDraw;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
public class myDrawing implements IDraw {
private Paint paint = new Paint();;
private RectF mRect1, mRect2, mRect3, mRect4;
public void doDraw(Canvas canvas) {
mRect1 = new RectF(20,75,120,155);
mRect2 = new RectF(100,105,195,170);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.RED);
canvas.drawArc(mRect1,230,125, false, paint);
canvas.drawArc(mRect2,235,120, false, paint);
mRect3 = new RectF(90,45,140,85);
mRect4 = new RectF(140,45,190,85);
paint.setColor(Color.BLUE);
canvas.drawArc(mRect3,210,140, false, paint);
canvas.drawArc(mRect4,195,140, false, paint);
}
}
// myActivity.java
package com.misoo.pk001;
import myFramework.GraphActivity;
import myFramework.GraphView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
public class myActivity extends GraphActivity implements OnClickListener {
private GraphView mv = null;
private Button ibtn;
private Bitmap bm;
@Override protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
bm = BitmapFactory.decodeResource(getResources(), R.drawable.taipei101);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
mv = new GraphView(this, bm);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(250, 280);
param.topMargin = 10;
param.leftMargin = 10;
layout.addView(mv, param);
//----------------------------------------------
ibtn = new Button(this);
ibtn.setOnClickListener(this);
ibtn.setText("Exit");
ibtn.setBackgroundResource(R.drawable.gray);
LinearLayout.LayoutParams param1 =
new LinearLayout.LayoutParams(100, 65);
param1.topMargin = 10;
param1.leftMargin = 10;
layout.addView(ibtn, param1);
//-----------------------------------------------
setContentView(layout);
}
public void onClick(View v) { finish(); }
}
4. 结语
由框架(的基类)来创建应用(AP或App)子类的对象是框架开发的基本技巧。此时面临的主要挑战是:框架基类由强龙设计在先,而应用子类则由地头蛇开发在后。也就是说,当强龙撰写框架基类时,地头蛇(和客户)都尚未出现,基类开发者还不知道AP子类的名称,那么又如何去<<new>>一个AP子类的对象呢? 其实并不困难,因为在程序的执行时间( Run-time)才会真正创建对象,这执行时间已经是在地头蛇撰写子类的时间之后了,如下图所示:
图10、由子类开发者撰写AndroidManifest.xml文件
因此,解决之道是:请地头蛇撰写完AP子类时,也将子类的名称字符串(classname string)写入一个特定的配置(Configuration)文文件里,然后于程序执行时,才去读取配置文文件里的子类的名称字符串,然后透过Java的指令:
Class.forName(classname).newInstance();
就能创建该AP子类的对象了。于是,实践了由框架创建AP子类对象的任务了。 ◆
[Go Back]