聊聊Context

奇舞移動發表於2018-11-05

在上篇文章《設計模式之裝飾模式》中我們談到了裝飾模式,在 Android 中關於 Context 的設計就用到了裝飾模式。這篇文章我們就來聊一聊 Context。

關於 Context,作為 Android 開發人員再熟悉不過了。啟動 Actiivty、Service 需要 Context,獲取資源需要 Context。離開 Context 整個系統都玩不轉了,可見 Context 在 Android 中多麼重要。

先來回憶一下上篇文章中裝飾模式的結構圖

1.png

再來對比一下 Context 的繼承結構圖

2.png

看著這兩張圖我們來找一下對應關係

Componment -> Context

ConcreteComponment -> ContextImpl

Decorator -> ContextWrapper

ConcreteDecorator -> ContextThemeWraper、Activity、Service、Application

再來具體看一下程式碼

Context.java

public abstract void startActivity(@RequiresPermission Intent intent);

public abstract ComponentName startService(Intent service);

public abstract Resources getResources();

複製程式碼

Context 是一個抽象類,定義了我們常用的大部分抽象方法。

ContextImpl.java

@Override
public void startActivity(Intent intent) {
	warnIfCallingFromSystemProcess();
	startActivity(intent, null);
}
複製程式碼
@Override
public void startActivity(Intent intent, Bundle options) {

    //省略程式碼
    //啟動activity的入口
    mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity) null, intent, -1, options);
}

複製程式碼
@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}

複製程式碼

ContextImpl 是 Context 的具體實現,也就是我們實際使用的物件。

ContextWrapper.java

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    // 省略程式碼
    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }

}

複製程式碼

ContextWrapper 這個類比較簡單,裡面有一個 Context(mBase) 的引用,也就是 ContextImpl 類的物件。還有一個 attachBaseContext 方法下面會提到。是給引用的 ContextImpl(mBase)賦值的地方。下面我們看下 ContextImpl 這個物件是什麼時候建立和賦值給 mBase 的。

要理解 ContextImpl 是如何建立的就不得不提到 Activity、Service、Application 的建立流程,由於涉及的程式碼比較多,我們只看關鍵部分。

主要分析 ActivityThread 這個類,看一下 performLaunchActivity 方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    // 省略程式碼
    //在這裡建立了ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    if (activity != null) {
		 // 這個方法引數很多,現在我們只關心第一個appContext
        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, r.configCallback);

        if (customIntent != null) {
            activity.mIntent = customIntent;
        }
        // 省略程式碼
        r.activity = activity;
    }
	// 省略程式碼
    return activity;
}

複製程式碼

Activity 建立關鍵程式碼,注意 attach 這個方法

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, ActivityConfigCallback activityConfigCallback) {
       
        attachBaseContext(context);
		 
		 // 省略程式碼
    }


複製程式碼

看到這可以發現,在 attach 方法中,第一個引數 appContext 也就是 ContextImpl 類的物件,在 attach 方法中又呼叫了 ContextWrapper 的attachBaseContext(context),最終把 appContext 賦值給 mBase。

Service建立關鍵程式碼

private void handleCreateService(CreateServiceData data) {
   
    // 省略程式碼
    Service service = null;
    java.lang.ClassLoader cl = packageInfo.getClassLoader();
    service = packageInfo.getAppFactory()
            .instantiateService(cl, data.info.name, data.intent);
   
    // 省略程式碼
    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,
            ActivityManager.getService());
    service.onCreate();
    mServices.put(data.token, service);
       
    // 省略程式碼
}


複製程式碼

Service.java關鍵程式碼

public final void attach(
        Context context,
        ActivityThread thread, String className, IBinder token,
        Application application, Object activityManager) {
   
    attachBaseContext(context);
    
    // 省略程式碼
}

複製程式碼

可以看到,同樣是在 attach 方法中,通過呼叫 attachBaseContext(context) 把 ContextImpl 的物件賦值給了 mBase。其他的建立過程不再分析,有興趣可以瞭解下 ActivityThread 原始碼。至此整個過程就串起來了,這就是裝飾模式在 Context 中的實現。

最後再來看一下 RePlugin 中 PluginContext 和 HostContext 是如何獲取的,直接上程式碼。

RePlugin.java

// 獲取外掛的Context
public static Context getPluginContext() {
    return RePluginEnv.getPluginContext();
}

// 獲取宿主的Context
public static Context getHostContext() {
        return RePluginEnv.getHostContext();
}

複製程式碼

可以看到兩個方法都是呼叫了 RePluginEnv 中的方法,再去看下 RePluginEnv 的程式碼

RePluginEnv.java

// 獲取外掛的Context
public static Context getPluginContext() {
        return sPluginContext;
}

// 獲取宿主的Context
public static Context getHostContext() {
        return sHostContext;
}

複製程式碼

以上兩個 Context 都是在 init 方法中賦值的。

static void init(Context context, ClassLoader cl, IBinder manager) {
    sPluginContext = context;

    // 確保獲取的一定是主程式的Context
    sHostContext = ((ContextWrapper) context).getBaseContext();
}

複製程式碼

再往下看, init 方法是在 Entry 的 create 方法呼叫。

Entry.java

public class Entry {

    public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
        // 初始化外掛框架
        RePluginFramework.init(cl);
        // 初始化Env
        RePluginEnv.init(context, cl, manager);
        return new IPlugin.Stub() {
            @Override
            public IBinder query(String name) throws RemoteException {
                return RePluginServiceManager.getInstance().getService(name);
            }
        };
    }
}

複製程式碼

Entry 的 create 又是在 Loader.java 中通過反射呼叫的,看下程式碼

Loader.java

mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);

複製程式碼
IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, getClass().getClassLoader(), manager);

複製程式碼

可以看到, create 方法的第一個引數就是 mPkgContext(PluginContext物件),它就是外掛的 Context,有自己的 classloader 和 resource。同時 PluginContext 的構造方法接受一個 Context,也就是宿主的 Context,它也就是mBase。所以在上面的程式碼中就可以拿到宿主的 sHostContext 了。

sHostContext = ((ContextWrapper) context).getBaseContext();

複製程式碼

至此,RePlugin 外掛中獲取的宿主和外掛 Context 分析完畢。

關注微信公眾號,最新技術乾貨實時推送

image src=

相關文章