1. 程式人生 > >Android之Context底層原理

Android之Context底層原理

Context的中文翻譯為:語境; 上下文; 背景; 環境,在開發中我們經常說稱之為“上下文”。從Android系統的角度來理解:Context是一個場景,代表與作業系統的互動的一種過程。Context在載入資源、啟動Activity、獲取系統服務、建立View等操作都要參與  。從程式的角度上來理解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實現。

  • 獲取AssetManager:getAssets();
  • 獲取Resources:getResources();
  • 獲取PackageManager:getPackageManager();
  • 獲取ContentResolver:getContentResolver();
  • 獲取主執行緒Looper:getMainLooper();
  • 獲取Application的Context:getApplicationContext();
  • 獲取資原始檔:getText,getString,getColor,getDrawable,getColorStateList;
  • 設定主題,獲取主題資源id:setTheme,getThemeResId;
  • 獲取樣式屬性TypedArray:obtainStyledAttributes();
  • 獲取類載入器ClassLoader:getClassLoader();
  • 獲取應用資訊物件ApplicationInfo:getApplicationInfo();
  • 獲取SharedPreferences:getSharedPreferences();
  • 開啟檔案FileInputStream:openFileInput();
  • 刪除檔案:deleteFile();
  • 獲取檔案File:getFileStreamPath();
  • 開啟或者建立資料庫:openOrCreateDatabase();
  • 移除或者刪除資料庫:moveDatabaseFrom(),deleteDatabase();
  • 啟動Activity:startActivity(),startActivityAsUser(),startActivityForResult(),startActivities();
  • 註冊、傳送、登出廣播:registerReceiver(),sendBroadcast(),sendOrderedBroadcast(),unregisterReceiver();
  • 啟動、繫結、解除繫結、停止服務:startService(),bindService(),unbindService(),stopService();
  • 獲取系統服務:getSystemService();
  • 檢查許可權(Android 6.0以上):checkPermission();
  • 根據應用名建立Context:createPackageContext();
  • 根據應用資訊建立Context:createApplicationContext();
  • 獲取顯示資訊物件Display:getDisplay();

1.ContextImpl、ContextWrapper與Context的關係

Context是一個抽象類,ContextImpl和ContextWrapper都繼承了Context,也就是都實現了Context的抽象方法,但是,從程式碼中我們看到ContextImpl是Context抽象方法的詳細實現類,而ContextWrapper是呼叫了mBase對應的方法,而mBase是Context,從程式碼跟蹤看mBase其實就是ContextImpl,因此ContextWrapper最終是呼叫ContextImpl中的實現方法。也就是說我們呼叫的Context中的任何方法都是在ContextImpl中處理的,因此我們在跟蹤程式碼時只需要去ContextImpl中檢視對應方法處理就好了。上面只是介紹,下面我們根據具體程式碼來分析一下到底怎麼實現的。

從ContextWrapper程式碼中我們可以看到(不貼全部程式碼了),只有建構函式、attachBaseContext方法以及getBaseContext方法不是複寫方法,其他方方法均為複寫方法:

【ContextWrapper.java】

    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() {
        return mBase;
    }

我們可以看到只有建構函式和attachBaseContext方法傳入了mBase,那麼從attachBaseContext方法中我們看到如果mBase存在又呼叫了該方法就會丟擲異常,因此我們知道如果呼叫了該方法,那麼建構函式不能傳入這個值,我們看一下哪些地方呼叫了這個attachBaseContext方法,由程式碼可以看到Application、activity和service均呼叫了這個方法,首先我們來看Application中的程式碼:

【Application.java】

    /**
     * @hide
     */
    /* package */ final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

Application中的attach方法中呼叫了attachBaseContext方法,引數context也是通過attach方法傳入的,那麼我們再跟蹤這個attach方法:

是在Instrumentation類中呼叫的:

【Instrumentation.java】

    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
    }

上面方法呼叫地方是:

【Instrumentation.java】

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

從程式碼可以看到是在new Application時呼叫的,那麼我們接著看哪裡呼叫了這個方法:

【LoadedApk.java】

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        ...

        try {
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            ...
        }
        ...
        return app;
    }

上面方法是在LoadedApk類中呼叫的,我們先不分析這個類,後續我們會詳細講這個過程,我們先分析上面這段程式碼,我們看到這裡面通過呼叫ContextImpl.createAppContext方法來建立ContextImpl,然後將引數傳入newApplication方法,因此我們看到上面的mBase就是ContextImpl,那麼還有Activity和Service.我們先分析Service,因為從關係圖可以看到Service和Application都是直接繼承ContextWrapper,而Activity則是繼承ContextThemeWrapper,ContextThemeWrapper繼承ContextWrapper。

【Service.java】

public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
        attachBaseContext(context);
        ...
    }

attachBaseContext方法是在Service中的attach方法中呼叫的,接著看attach方法的呼叫:

【ActivityThread.java】

private void handleCreateService(CreateServiceData data) {
        ...
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();
         ...
    }

在這裡我們看到傳入的context就是ContextImpl,從而得到驗證,下面我們還看到service.onCreate方法,我們看到了先呼叫attach方法然後呼叫onCreate方法。

最後我們看一下Activity,從上面關係圖我們看到,Activity不是直接繼承ContextWrapper,而是繼承的ContextThemeWrapper,ContextThemeWrapper繼承ContextWrapper。從名字我們可以看到ContextThemeWrapper包含主題的資訊,其實不難理解,四大元件只有Activity是帶介面的,其他都是沒有介面的,因此Activity需要主題資訊來顯示不同的介面效果。在ContextThemeWrapper中我們看到複寫了attachBaseContext方法,方法中只有一行程式碼就是呼叫父類的attachBaseContext方法。如下所示:

【ContextThemeWrapper.java】

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

在Activity中只有一個方法中呼叫了該方法,看程式碼:

【Activity.java】

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,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        ...
    }

我們接著追蹤attach方法,看程式碼:

【ActivitThread.java】

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...
        
            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);

                ...
            }
            ...
        return activity;
    }

方法performLaunchActivity其實是啟動Activity的方法,這裡我們暫時不講,後續我們會詳細講解,我們先理清楚Context,從上面程式碼我們可以看到此處傳入的Context是通過createBaseContextForActivity方法建立的,那麼我們看一下這個方法:

【ActivitThread.java】

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        ...

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.token, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;

        ...
        return baseContext;
    }

從上面程式碼我肯可以清楚的看到baseContext是appContext賦值的,而appContext就是ContextImpl,因此Activity中的Context也是ContextImpl。