Android原始碼之DeskClock (三) Proxy/Delegate Application 架構應用

來源:互聯網
上載者:User

標籤:color   個人化定製   launcher   attach   需求   csdn   對象   而在   tap   

一.概述       當項目有加殼子,外掛程式化或熱修複等需求的時候,能夠使用Proxy/Delegate Application架構的方式,在正常的模式中,一個程式一般僅僅有一個Application入口,而Proxy/Delegate模式中須要有兩個Application,原程式的Application改為Delegate Application,再新加一個Proxy Application,由Proxy Application 提供一系列的個人化定製,再將所有的context和context相關的引用所有轉化為Delegate Application的執行個體,讓外界包含Delegate Application自身都以為該App的Application入口就是Delegate Application.
二.執行個體1.Proxy/Delegate 之前       這裡就在Android 4.4原生的DeskClock程式上應用Proxy/Delegate架構為示範範例       原生的DeskClock程式沒有自己定義Application,這裡先定義一個,並print該程式眼下ApplicationContext的名字(在DeskClock中使用的Log是自己定義的)
/** * Created by jesse on 15-7-17. */public class MyApplication extends Application{    private final String TAG = MyApplication.class.getSimpleName();    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG + ", onCreate " + this.getApplicationContext().getClass().getSimpleName());    }}
       而且在DeskClock的入口Activity,DeskClock處也print出該程式眼下ApplicationContext的名字用於興許Proxy後的對照.       Application的Manifest配置是
    <application android:name="cn.jesse.MyApplication"                 android:label="@string/app_label"                 android:icon="@mipmap/ic_launcher_alarmclock"                 android:requiredForAllUsers="true"                 android:supportsRtl="true">
       過濾後的執行Log: 簡單的流程就是先啟動自己定義MyApplication 之後再launch DeskClock,同一時候都列印出來ApplicationContext的名字

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >

2.使用Proxy/Delegate架構之後       使用Proxy/Delegate架構,須要又一次構建出來一個新的ProxyApplication,用來做代理Application,原先的MyApplication的作用為DelegateApplication       所以Manifest的配置須要更改,app的主入口更改為MyProxyApplication,把DelegateApplication的資訊以meta-data子項目的形式儲存(當然也能夠用其它的方式)

    <application android:name="cn.jesse.MyProxyApplication"                 android:label="@string/app_label"                 android:icon="@mipmap/ic_launcher_alarmclock"                 android:requiredForAllUsers="true"                 android:supportsRtl="true">        <meta-data            android:name="DELEGATE_APPLICATION_CLASS_NAME"            android:value="cn.jesse.MyApplication" >        </meta-data>    </application>
       定義一個抽象類別,提供一個用於替換當前ProxyApplication 的ClassLoader成父類的ClassLoader的抽象方法(或者一些其它的個人化定製)
 * Created by jesse on 15-7-17. */public abstract class ProxyApplication extends Application{    protected abstract void initProxyApplication();}
       當我們要替換當前ProxyApplication的ClassLoader為父類的ClassLoader,所以這個替換的動作要足夠得早(要保證在app Context最早被構建的入口處替換ClassLoader),要不然就會出現替換不乾淨的情況,就會有程式中大部分使用的DelegateApplication的ClassLoader,而一小部分是使用的ProxyApplication的ClassLoader,這樣可能會出現一些意想不到的bug.       通常來說在Application的OnCreate中來做替換就足夠了,可是當app有注冊ContentProvider的時候ContentProvider:OnCreate的調用是在Application:OnCreate之前的,所以我們必須保證替換ClassLoader的動作要在ContentProvider之前.       通過查看原始碼能夠看到Application是繼承自ContextWrapper,而在ContextWrapper中系統在構建完畢完好的Context之後第一次回調是通過attachBaseContext方法,既然這樣就通過在ProxyApplication中複寫該方法來擷取剛出爐熱噴噴的Context來轉換ClassLoader.
    /**     * 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;    }
       轉換ClassLoader的入口也確定之後就能夠自己定義一個MyProxyApplication,繼承自ProxyApplication而且複寫attachBaseContext方法,print相關資訊
/** * Created by jesse on 15-7-17. */public class MyProxyApplication extends ProxyApplication {    private final String TAG = MyProxyApplication.class.getSimpleName();    private Context mContext;    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        Log.i(TAG + ", attachBaseContext");        mContext = base;        this.initProxyApplication();    }    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG + ", onCreate" + this.getApplicationContext().getClass().getSimpleName());        BootLoader.boot(mContext);    }    @Override    protected void initProxyApplication() {        Log.i(TAG + ", initProxyApplication");        BootLoader.resetClassLoader(mContext);    }}
       Log執行的順序,先進入attachBaseContext->initProxyApplication->onCreate->DeskClock:onCreate (這裡DeskClock的onCreate擷取到的ApplicationContext的名字是(MyProxyApplication)
       入口的順序沒問題了之後,就能夠在initProxyApplication方法中替換當前的ClassLoader到父類的ClassLoader,而且在MyProxyApplication的onCreate中將應用程式層全部的Application的引用全部從ProxyApplication替換成MyApplication(當前在DeskClock程式中沒有替換ClassLoader的需求,僅僅須要替換全部的Application的引用就能達到代理的效果,所以在initProxyApplication方法處就寫了一個空方法帶過).       先從AndroidManifest設定檔裡的metadata拿到DelegateApplication的屬性
            String className = CLASS_NAME;            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(super.getPackageName(), PackageManager.GET_META_DATA);            Bundle bundle = appInfo.metaData;            if (bundle != null && bundle.containsKey(KEY)) {                className = bundle.getString(KEY);                if (className.startsWith("."))                    className = super.getPackageName() + className;            }
       依據className反射得到MyApplication,建立MyApplication執行個體而且取得MyProxyApplication的執行個體
            Class delegateClass = Class.forName(className, true, getClassLoader());            Application delegate = (Application) delegateClass.newInstance();            Application proxyApplication = (Application)getApplicationContext();
       使用反射更換MyProxyApplication context成員中的mOuterContext屬性
            Class contextImplClass = Class.forName("android.app.ContextImpl");            Field mOuterContext = contextImplClass.getDeclaredField("mOuterContext");            mOuterContext.setAccessible(true);            mOuterContext.set(mContext, delegate);
       擷取MyProxyApplication Context的PackageInfo對象,替換掉當中的mApplication屬性
            Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");            mPackageInfoField.setAccessible(true);            Object mPackageInfo = mPackageInfoField.get(mContext);            Class loadedApkClass = Class.forName("android.app.LoadedApk");            Field mApplication = loadedApkClass.getDeclaredField("mApplication");            mApplication.setAccessible(true);            mApplication.set(mPackageInfo, delegate);
       再依據之前反射得到的packageInfo對象擷取到mActivityThread屬性,替換掉當中的mInitialApplication屬性
            Class activityThreadClass = Class.forName("android.app.ActivityThread");            Field mAcitivityThreadField = loadedApkClass.getDeclaredField("mActivityThread");            mAcitivityThreadField.setAccessible(true);            Object mActivityThread = mAcitivityThreadField.get(mPackageInfo);            Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");            mInitialApplicationField.setAccessible(true);            mInitialApplicationField.set(mActivityThread, delegate);
       拿著之前的mActivityThread對象擷取到mAllApplications屬性,注意該屬性是一個list。這裡就移除MyProxyApplication加入DelegateApplication,至此應用程式層MyProxyApplication的Context的引用所有都替換成了MyApplication的引用.
            Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");            mAllApplicationsField.setAccessible(true);            ArrayList<Application> al = (ArrayList<Application>)mAllApplicationsField.get(mActivityThread);            al.add(delegate);            al.remove(proxyApplication);
       給MyApplication通過反射和attach內部方法設定baseContext,並調用MyApplication的onCreate方法完畢DelegateApplication的初始化.
            Method attach = Application.class.getDeclaredMethod("attach", Context.class);            attach.setAccessible(true);            attach.invoke(delegate, mContext);            delegate.onCreate();
       完畢這些步驟之後再又一次執行查看Log,觀察DeskClock處擷取的ApplicationContext的名字已經變成MyApplication.

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
       可是這樣還沒有全然結束,還記得開頭說的ContentProvider嗎?他的構造是在Application的onCreate之前的,那麼ContentProvider部分有沒有須要替換的Context引用呢?從framework/base/core/java/android/app下能夠找到ActivityThread.java從當中裝載ContentProvider的部分能夠看到,假設當前Context的包名和ProviderInfo的包名一樣的話,ContentProvider就會引用當前的MyProxyApplication的Context.因為當前的MyProxyApplication僅僅是做代理啟動用的,所以在MyProxyApplication處複寫getPackageName而且返回空就能夠避免ContentProvider複用當前Context了.

    private IActivityManager.ContentProviderHolder installProvider(Context context,            IActivityManager.ContentProviderHolder holder, ProviderInfo info,            boolean noisy, boolean noReleaseNeeded, boolean stable) {        ContentProvider localProvider = null;        IContentProvider provider;        if (holder == null || holder.provider == null) {            if (DEBUG_PROVIDER || noisy) {                Slog.d(TAG, "Loading provider " + info.authority + ": "                        + info.name);            }            Context c = null;            ApplicationInfo ai = info.applicationInfo;            if (context.getPackageName().equals(ai.packageName)) {                c = context;            } else if (mInitialApplication != null &&                    mInitialApplication.getPackageName().equals(ai.packageName)) {                c = mInitialApplication;            } else {                try {                    c = context.createPackageContext(ai.packageName,                            Context.CONTEXT_INCLUDE_CODE);                } catch (PackageManager.NameNotFoundException e) {                    // Ignore                }            }
三.總結       這篇僅僅是先簡單的走了下Proxy/Delegate架構的流程,這個架構事實上是有非常多使用情境的,比如多dex動態載入,外掛程式化,線上程式熱修複bug等能夠靈活使用出非常多有趣的技術,有時間的話還會再發一篇以Proxy/Delegate實現的線上程式熱修複bug的部落格.

轉載請註明出處:http://blog.csdn.net/l2show/article/details/46914881

Android原始碼之DeskClock (三) Proxy/Delegate Application 架構應用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.