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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章