`

Android关于Intent对象创建时报异常相关调查

 
阅读更多
/frameworks/base/core/java/android/content/Intent.java
./frameworks/base/core/java/android/content/Context.java
./frameworks/base/core/java/android/app/ContextImpl.java
./frameworks/base/core/java/android/app/Activity.java
./frameworks/base/core/java/android/app/ActivityThread.java
./frameworks/base/core/java/android/view/ContextThemeWrapper.java
./frameworks/base/core/java/android/content/ContextWrapper.java


--------------------------------------------------------------------------------
Activity与ContextImpl的关联:

继承关系:
public class Activity extends ContextThemeWrapper
public class ContextThemeWrapper extends ContextWrapper
public class ContextWrapper extends Context
class ContextImpl extends Context

Activity本身是一个Context类型的子类。
在ContextWrapper中间接实现了Context相关的方法,所以Activity也具有这些方法。
而在ContextWrapper中对Context相关的方法的实现实际上是调用mBase的相关方法的。
而这个mBase实际上就是ContextImpl的实例。
也就是说,所有Context子类型的对象调用Context相关方法时,实际上都是调用的ContextImpl实例的方法。

mBase的赋值是在ContextWrapper的以下方法中:
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

而这个方法的调用是在Activity的以下方法中:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance,
HashMap<String,Object> lastNonConfigurationChildInstances,
Configuration config) {
attachBaseContext(context);

而这个方法的调用是在ActivityThread的以下方法中:
private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent)
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstance,
r.lastNonConfigurationChildInstances, config);
mInstrumentation.callActivityOnCreate(activity, r.state);

这个方法是在luncher一个Activity时调用的。
通过方法可以看出,系统首先创建一个Activity对象,然后构造相关的Application对象。
之后,创建ContextImpl对象,并调用init方法初始化相关成员,之后将其添加给Activity对象。
这样,Activity就和ContextImpl对象关联上了。
在之后会调用Activity相关的回调函数,比如onCreate()等。


--------------------------------------------------------------------------------
Android关于Intent对象创建时报异常相关调查


<示例代码>
public class MyServiceActivity extends Activity {
private Button startSer1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startSer1 = (Button) findViewById(R.id.startSer1);
startSer1.setOnClickListener(btnListener);
}

private OnClickListener btnListener = new OnClickListener() {
private Intent intent = new Intent(MyServiceActivity.this, MyService.class);

@Override
public void onClick(View v) {
}
};
}

<问题>
程序在加载时会报异常,位置是 private Intent intent = new Intent()。
而将该Intent对象的创建放到onClick中就没有问题。

<分析>
1、需要理解Intent对象的创建过程:
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}

public ComponentName(Context pkg, Class<?> cls) {
mPackage = pkg.getPackageName();
mClass = cls.getName();
}

可以看到,最后会调用pkg对象的getPackageName方法。
而getPackageName方法的实现是在ContextImpl中:
public String getPackageName() {
if (mPackageInfo != null) {
return mPackageInfo.getPackageName();
}
throw new RuntimeException("Not supported in system context");
}

这里又调用了mPackageInfo成员的getPackageName方法。
而mPackageInfo成员是在ContextImpl的init方法中赋值的:
final void init(ActivityThread.PackageInfo packageInfo,
IBinder activityToken, ActivityThread mainThread,
Resources container) {
mPackageInfo = packageInfo;

而这个方法是在创建完Activity的实例之后,创建Application信息时调用的:
private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstance,
r.lastNonConfigurationChildInstances, config);
mInstrumentation.callActivityOnCreate(activity, r.state);

也就是说,如果要创建一个Intent对象,必须在ContextImpl对象的init方法运行完之后才可以。
而Activity对象的创建明显处在ContextImpl对象的init方法调用之前。

而Activity的onCreate方法的调用则处在ContextImpl对象的init方法调用之后:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
activity.onCreate(icicle);


2、出错原因主要是,Intent对象的创建是在Listener对象创建时创建的,而它的创建又依赖于Activity对象:
private Intent intent = new Intent(MyServiceActivity.this,
MyService.class);
(该方法中的第一个参数是一个Context的实例)

Listener对象是在Activity创建时创建的,:
private OnClickListener btnListener = new OnClickListener() {

而此时,Activity对象还没有创建完成,ContextImpl的init方法还没有被调用。
所以,此时创建Intent对象时,会因为没有mPackageInfo成员而创建失败。
所以,整个程序在启动的过程中,当创建到Activity对象的Listener对象的Intent对象时就直接报空指针异常了。

3、正确的做法是:
1、要么将Listener对象的创建放到onCreate中。
2、要么将Intent的创建放到onCreate中或者onClick中。

当然,还存在其它修改方法,但都大同小异。
而本质都是在创建Intent对象时,保证Activity的相关环境已经初始化完成。

通过以上分析可以看出,严格遵循Activity的生命周期进行编程才能写出健壮不易出错的代码。


转载自http://hi.baidu.com/gaogaf/blog/item/7efdb01185806264ca80c4d9.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics