Android插件化之DroidPlugin原理解析

DroidPlugin原理解析

從系統設計的角度,組件和窗口的邏輯實體都存在於系統服務,比如Activity創建後,其邏輯控制主體在AMS,對於窗口,其邏輯控制主體在WMS

android將邏輯主體放置於系統服務,系統就可以對組件和窗口的生命週期,顯示狀態進行強掌控,這樣就能做到在各種狀態變更時能做到及時回調通知

所以,創建任何組件,都需要通過RPC通訊到AMS創建 — 第一個hook點

那邏輯主體確定後,AMS就需要創建進程去運行真實的Activity對象(可以認爲它是一個提線木偶)

Android進程啓動後,JAVA的入口是ActivityThread.main

ActivityThread主要幹兩件事件

  • 創建IApplicationThread native binder和AMS進行通訊
  • 收到AMS發來的RPC事件後,創建並保存各個組件相關的數據 ---- 第二個hook點

組件相關數據主要包括兩個

  • 組件所屬包信息和對應的loadedApk - 保存於mPackages
  • 將AMS中逐漸的邏輯主體對象token和真實組件對象一同保存,便於後續跟蹤操作 - 比如Activity相關的保存於mActivities,service相關保存於mServices

還有,ActivityThread的設計本身好像就支持加載多個application,多個application會被保存到mAllApplications中

插件包安裝

DroidPlugin實現了一個簡易的IPluginManagerImpl用於插件APK包的安裝和解析,當然這部分代碼是參考系統的PMS來實現的,主要職責:

  • 插件APK安裝到本地目錄
  • 對插件APK的組件等數據進行解析

插件包解析和加載

  1. 插件包的解析,就是對AndroidManifest的解析,主要通過反射系統的PackageParser來完成
  2. 在Activity啓動前(hook見下面介紹),會調用
 PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
 preLoadApk內部會根據targetActivityInfo包含的包名來判斷LoadedApk是
 否創建,如果未創建,則會通過反射調用ActivityThread的函數來創建插件
 LoadedApk並保存到ActivityThread的mPackages中,接着創建
 PluginClassLoader並設置到LoadedApk對象中
  1. 最後通過反射調用LoadedApk的makeApplication創建插件Application對象並調用onCreate

插件Activity啓動解析

我們先來看下Android常規Activity的啓動流程

  1. 調用Context.startActivity -> ActivityManagerNative -> AMS, AMS通過Intent從PMS拿到ActivityInfo並創建ActivityRecord和token放入前臺ActivityStack,接着按需啓動Activity所屬進程
  2. 進程啓動後,馬上執行入口ActivityThread.main並調用attachApplication將啓動信息反饋到AMS,AMS通過pid找到對應的ProcessRecord並更新其數據
  3. 接着從前臺ActivityStack中拿到棧頂的ActivityRecord,如果其proecssrecord爲null,並且uid和processname跟新創建的ProcessRecord一致,則正式調用app.thread.scheduleLaunchActivity
  4. ActivityThread在scheduleLaunchActivity中創建ActivityClientRecord,用於跟AMS中的ActivityRecord對應,ActivityClientRecord最重要的兩個字段是token和activityinfo,token用於關聯ActivityRecord,activityinfo則包含activity的描述和所屬包等信息
  5. 在scheduleLaunchActivity內部接着發送LAUNCH_ACTIVITY message到mH這個handler,mH收到LAUNCH_ACTIVITY message後的代碼如下:
     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
     //通過activityinfo中包含的application信息創建loaedapk並保存於packageinfo
     r.packageInfo = getPackageInfoNoCheck(
                             r.activityInfo.applicationInfo, r.compatInfo);
     handleLaunchActivity(r, null);
    

理解上面第1和第5步很重要,因爲DroidPlugin的Activity hook就是基於這兩個點來進行的,原理總結如下:

  1. DroidPlugin首先在host app的AndroidManifest預註冊一堆stub
    activity,這裏只列出一部分,詳細的可查看源碼
      .stub.ActivityStub$P00$Standard00
      .stub.ActivityStub$P00$SingleInstance00
      .stub.ActivityStub$P00$SingleInstance01
      .stub.ActivityStub$P00$SingleInstance02
      .stub.ActivityStub$P00$SingleInstance03
      .stub.ActivityStub$P00$SingleTask00
      .stub.ActivityStub$P00$SingleTask01
      .stub.ActivityStub$P00$SingleTask02
      .stub.ActivityStub$P00$SingleTask03
      .stub.ActivityStub$P00$SingleTop00
      .stub.ActivityStub$P00$SingleTop01
      .stub.ActivityStub$P00$SingleTop02
      .stub.ActivityStub$P00$SingleTop03
    
  2. 通過動態代理和反射,hook ActivityManagerNative的接口,這個實現原理網上很多,這裏不再贅述
  3. hook startActivity,相關代碼在IActivityManagerHookHandle.startActivity中
                 ActivityInfo activityInfo = resolveActivity(intent);
                 if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
                     ComponentName component = selectProxyActivity(intent);
                     if (component != null) {
                         Intent newIntent = new Intent();
                         try {
                             ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
                             setIntentClassLoader(newIntent, pluginClassLoader);
                         } catch (Exception e) {
                             Log.w(TAG, "Set Class Loader to new Intent fail", e);
                         }
                         newIntent.setComponent(component);
                         newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                         newIntent.setFlags(intent.getFlags());
    
                         String callingPackage = (String) args[1];
                         if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
                             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
                         args[intentOfArgIndex] = newIntent;
                         args[1] = mHostContext.getPackageName();
                     }
    
    • 根據intent從DroidPlugin的packagemanager中拿到activityinfo(如果已安裝插件包中有匹配的activty)
    • 還是根據intent,根據目標activity的屬性,去匹配一個最合適的stub activity,並將component信息保存到newIntent,同時將intent作爲extra保存到newintent
    • 最後將args中intent替換稱newintent達到偷樑換柱的效果


      經過上面的偷樑換柱後,系統實際上拿到的是newintent,進而啓動stubactivity;DroidPlugin接下去要做的就是,將stubactivity還原成真正要啓動的插件activity,這個是在上面啓動流程第5步中完成的
  4. 上面啓動流程第五部可以看出,ActivityThread在啓動Activity的時候,最重要的兩個參數就是ActivityClientRecord裏的兩個變量intent和activityinfo,activityinfo是用來創建packageinfo(loadedapk), intent是要在創建activity後傳入的,所以DroidPlugin必須要在創建Acivity之前,也就是handleLaunchActivity(msg)之前將這兩個變量替換成原始的插件intent,這就是DroidPlugin Hook mH的目的,下面是hook 也就是handleLaunchActivity的部分代碼
    //PluginCallback.java
    private boolean handleLaunchActivity(Message msg) {
         try {
             Object obj = msg.obj;
             Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
             //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
             stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
             Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
             // 這裏多加一個isNotShortcutProxyActivity的判斷,因爲ShortcutProxyActivity的很特殊,啓動它的時候,
             // 也會帶上一個EXTRA_TARGET_INTENT的數據,就會導致這裏誤以爲是啓動插件Activity,所以這裏要先做一個判斷。
             // 之前ShortcutProxyActivity錯誤複用了key,但是爲了兼容,所以這裏就先這麼判斷吧。
             if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
                 IPackageManagerHook.fixContextPackageManager(mHostContext);
                 ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
                 ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
                 if (targetActivityInfo != null) {
    
                     if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                         targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                     }
    
                     ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                     ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                     if (stubActivityInfo != null) {
                         PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                     }
                     PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                     ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                     setIntentClassLoader(targetIntent, pluginClassLoader);
                     setIntentClassLoader(stubIntent, pluginClassLoader);
    
                     boolean success = false;
                     try {
                         targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                         if (stubActivityInfo != null) {
                             targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                         }
                         success = true;
                     } catch (Exception e) {
                         Log.e(TAG, "putExtra 1 fail", e);
                     }
    
                     if (!success && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                         try {
                             ClassLoader oldParent = fixedClassLoader(pluginClassLoader);
                             targetIntent.putExtras(targetIntent.getExtras());
    
                             targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                             if (stubActivityInfo != null) {
                                 targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                             }
                             fixedClassLoader(oldParent);
                             success = true;
                         } catch (Exception e) {
                             Log.e(TAG, "putExtra 2 fail", e);
                         }
                     }
    
                     if (!success) {
                         Intent newTargetIntent = new Intent();
                         newTargetIntent.setComponent(targetIntent.getComponent());
                         newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                         if (stubActivityInfo != null) {
                             newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                         }
                         FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent);
                     } else {
                         FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
                     }
                     FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
    
                     Log.i(TAG, "handleLaunchActivity OK");
                 } else {
                     Log.e(TAG, "handleLaunchActivity oldInfo==null");
                 }
             } else {
                 Log.e(TAG, "handleLaunchActivity targetIntent==null");
             }
         } catch (Exception e) {
             Log.e(TAG, "handleLaunchActivity FAIL", e);
         }
    
         if (mCallback != null) {
             return mCallback.handleMessage(msg);
         } else {
             return false;
         }
     }
    
    先用intent中拿出之前保存到extra的插件intent
    Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
    
    接着根據targetIntent獲取對應的activityinfo
    ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
    ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
    
    最後將數據寫回到ActivityClientRecord,完成最終的替換
     FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
     FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
    

插件service啓動分析

同樣的,先來看看service的常規啓動流程

  • 調用contextimpl.startService/bindService/stopService -> AMS,AMS對應創建ServiceRecord和token後,通知ActivityThread
  • ActivityThread收到startService後,會創建service並保存到mService map,key爲token,接着調用oncreate
  • ActivityThread接着收到handleServiceArgs, 根據token拿到service,接着調用onStartCommond並傳入intent
  • ActivityThread收到bindservice後,從根據token拿到service,接着調用onbind拿到native binder,接着調用publishService將native binder傳到AMS
 ActivityManagerNative.getDefault().publishService(
                               data.token, data.intent, binder);

Service跟Activity還是存在很大的區別的,service非常獨立,也就是說,系統創建service後,除了調用規定的那些回調,傳遞intent外,剩下就是service自己玩自己的,跟系統一毛錢關係都沒有了

Activity則不同,因爲其涉及到窗口,所以會存在大量的交互,比如WMS,IMS等

對於DroidPlugin來說,插件service的hook,則會簡單很多,只需要用一個stub service做爲代理,在stubservice內部根據傳入的intent去管理插件service對象即可:

.stub.ServiceStub$StubP00$P00

在startservice和bindservice時,只需要把目標sevice緩存stubservice,並將真實的intent作爲extra傳遞到stub service就可以了

  private static ServiceInfo replaceFirstServiceIntentOfArgs(Object[] args) throws RemoteException {
       int intentOfArgIndex = findFirstIntentIndexInArgs(args);
       if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
           Intent intent = (Intent) args[intentOfArgIndex];
           ServiceInfo serviceInfo = resolveService(intent);
           if (serviceInfo != null && isPackagePlugin(serviceInfo.packageName)) {
               ServiceInfo proxyService = selectProxyService(intent);
               if (proxyService != null) {
                   Intent newIntent = new Intent();
                   //FIXBUG:https://github.com/Qihoo360/DroidPlugin/issues/122
                   //如果插件中有兩個Service:ServiceA和ServiceB,在bind ServiceA的時候會調用ServiceA的onBind並返回其IBinder對象,
                   // 但是再次bind ServiceA的時候還是會返回ServiceA的IBinder對象,這是因爲插件系統對多個Service使用了同一個StubService
                   // 來代理,而系統對StubService的IBinder做了緩存的問題。這裏設置一個Action則會穿透這種緩存。
                   newIntent.setAction(proxyService.name + new Random().nextInt());

                   newIntent.setClassName(proxyService.packageName, proxyService.name);
                   newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                   newIntent.setFlags(intent.getFlags());
                   args[intentOfArgIndex] = newIntent;
                   return serviceInfo;
               }
           }
       }
       return null;
   }

接着在stubservice會創建ServcesManager用於插件service管理,所有的stub service回調會同步到ServcesManager裏:

   public int onStart(Context context, Intent intent, int flags, int startId) throws Exception {
       Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
       if (targetIntent != null) {
           ServiceInfo targetInfo = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0);
           if (targetInfo != null) {
               Service service = mNameService.get(targetInfo.name);
               if (service == null) {

                   handleCreateServiceOne(context, intent, targetInfo);
               }
               handleOnStartOne(targetIntent, flags, startId);
           }
       }
       return -1;
   }

看到沒,ServcesManager自己管理mNameService map,service信息則是通過extr中中真實的插件intent來獲得,onbind函數同樣:

 public IBinder onBind(Context context, Intent intent) throws Exception {
        Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
        if (targetIntent != null) {
            ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0);
            Service service = mNameService.get(info.name);
            if (service == null) {
                handleCreateServiceOne(context, intent, info);
            }
            return handleOnBindOne(targetIntent);
        }
        return null;
    }

這兩個函數在mNameService未包含該service實例的時候,都會調用handleCreateServiceOne,通過反射調用ActivityThrea的方法創建service,從而達到調用oncreate的目地

插件receiver分析

在插件apk被啓動的時候,會通過分析查看apk的receiver組件信息,然後動態註冊

插件provider分析

先介紹ContentProvider的實現原理

  • 本質肯定是基於binder,所以每一個ContentProvider都會實現Transport native binder
  • 當我們調用getContentResolve.insert/delete等操作時,前提肯定是需要根據authority來拿到對應ContentProvider綁定的Transport對應binder proxy
  • 拿到binder proxy後,數據連接建立

數據連接建立後,後續跟系統也沒一毛錢關係了,那理論上provider跟service是一樣的,只要能hook數據發送端,接收端用一個stubprovider做代理就可以搞定了

DroidPlugin定義的stubprovider

.stub.ContentProviderStub$StubP00

發送端hook,就是替換binder proxy的過程,看DroidPlugin的getContentProvider的hook代碼:

  @Override
        protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
            if (args != null) {
                final int index = 1;
                if (args.length > index && args[index] instanceof String) {
                    String name = (String) args[index];
                    mStubProvider = null;
                    mTargetProvider = null;

                    ProviderInfo info = mHostContext.getPackageManager().resolveContentProvider(name, 0);
                    mTargetProvider = PluginManager.getInstance().resolveContentProvider(name, 0);
                    //這裏有個很坑爹的事情,就是當插件的contentprovider和host的名稱一樣,衝突的時候處理方式。
                    //在Android系統上,是不會出現這種事情的,因爲系統在安裝的時候做了處理。而我們目前沒做處理。so,在出現衝突時候的時候優先用host的。
                    if (mTargetProvider != null && info != null && TextUtils.equals(mTargetProvider.packageName, info.packageName)) {
                        mStubProvider = PluginManager.getInstance().selectStubProviderInfo(name);
//                        PluginManager.getInstance().reportMyProcessName(mStubProvider.processName, mTargetProvider.processName);
//                        PluginProcessManager.preLoadApk(mHostContext, mTargetProvider);
                        if (mStubProvider != null) {
                            args[index] = mStubProvider.authority;
                        } else {
                            Log.w(TAG, "getContentProvider,fake fail 1");
                        }
                    } else {
                        mTargetProvider = null;
                        Log.w(TAG, "getContentProvider,fake fail 2=%s", name);
                    }
                }
            }
            return super.beforeInvoke(receiver, method, args);
        }

這裏不管是什麼請求,authority都會被改成stub provider的authority,在請求結束後,在將authority關聯contentprovider對應的binder proxy設置成DroidPlugin自己的

Object provider = FieldUtils.readField(invokeResult, "provider");
                    if (provider != null) {
                        boolean localProvider = FieldUtils.readField(toObj, "provider") == null;
                        IContentProviderHook invocationHandler = new IContentProviderHook(mHostContext, provider, mStubProvider, mTargetProvider, localProvider);
                        invocationHandler.setEnable(true);
                        Class<?> clazz = provider.getClass();
                        List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                        Object proxyprovider = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler);
                        FieldUtils.writeField(invokeResult, "provider", proxyprovider);
                        FieldUtils.writeField(toObj, "provider", proxyprovider);
}

接着在IContentProviderHook對發送uri做替換

  if (!mLocalProvider && mStubProvider != null) {
               final int index = indexFirstUri(args);
               if (index >= 0) {
                   Uri uri = (Uri) args[index];
                   String authority = uri.getAuthority();
                   if (!TextUtils.equals(authority, mStubProvider.authority)) {
                       Uri.Builder b = new Builder();
                       b.scheme(uri.getScheme());
                       b.authority(mStubProvider.authority);
                       b.path(uri.getPath());
                       b.query(uri.getQuery());
                       b.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY, authority);
                       b.fragment(uri.getFragment());
                       args[index] = b.build();
                   }
               }
           }

將uri的authority替換成stub provider的,將插件provider的authority保存到Env.EXTRA_TARGET_AUTHORITY這個parameter中

stubprovider實現就很簡單了,根據Env.EXTRA_TARGET_AUTHORITY的值來創建插件provider,接着做代理就好了,這裏不就貼代碼了


下面是contentprovider常規初始化流程,大家可以瞭解下

  • ContextImpl.getContentResolver.insert->ApplicationContentResolver.acquireProvider->ActivityThread.acquireProvider->ActivityManagerNative.getContentProvider->AMS.getContentProvider
  • 接着ActivityThread.scheduleInstallProvider->ActivityThread.installProvider
  • 接着創建ContextProvider實例並獲取內部native binder
  try {
               final java.lang.ClassLoader cl = c.getClassLoader();
               localProvider = (ContentProvider)cl.
                   loadClass(info.name).newInstance();
               provider = localProvider.getIContentProvider();
               if (provider == null) {
                   Slog.e(TAG, "Failed to instantiate class " +
                         info.name + " from sourceDir " +
                         info.applicationInfo.sourceDir);
                   return null;
               }
               if (DEBUG_PROVIDER) Slog.v(
                   TAG, "Instantiating local provider " + info.name);
               // XXX Need to create the correct context for this provider.
               localProvider.attachInfo(c, info);
           } catch (java.lang.Exception e) {
               if (!mInstrumentation.onException(null, e)) {
                   throw new RuntimeException(
                           "Unable to get provider " + info.name
                           + ": " + e.toString(), e);
               }
               return null;
           }

從代碼裏可以看出getIContentProvider返回的native binder纔是contentprovider數據傳輸的核心

  • 接着調用ActivityManagerNative.publishContentProviders將新創建的provider同步到AMS

還有一點很重要,通過AMS.getContentProvider->ActivityThread.acquireProvider,由於ActivityThread處理都是發送消息到mH,所以它是異步的,AMS.getContentProvider如果立即返回,肯定是空的,所以它必須要等待後續ActivityManagerNative.publishContentProviders執行完成後才返回,看AMS.getContentProviderImpl部分代碼:

 //ActivityManagerService.getContentProviderImpl
 //.....前面代碼沒貼
 // Wait for the provider to be published...
        synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    return null;
                }
                try {
                    if (conn != null) {
                        conn.waiting = true;
                    }
                    cpr.wait();
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }
  return cpr != null ? cpr.newHolder(conn) : null;

插件加載獨立性

如果插件都在主進程啓動運行,可能有人會有疑問,LoadedApk會不會亂掉?答案肯定是不會的,因爲這個是DroidPlugin這個實現方案的前提,咱們看LoadedApk的生成代碼

 private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                        : "Loading resource-only package ") + aInfo.packageName
                        + " (in " + (mBoundApplication != null
                                ? mBoundApplication.processName : null)
                        + ")");
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
                if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }

ActivityThread會保存LoadeApk的map,key就是package name,所以各個插件的LoadedApk可以獨立的存在ActivityThread中

插件resource獲取

Android資源獲取依賴

  • resource id,即開發中用到R..
  • 還有就是context.getResource()

由於四大組件和Application這五個入口類的創建使用的是插件的class loader,那他們使用過程中用到的R.java肯定是對應插件的,這個不會有任何問題

不過context本質是ContextImpl對象實例,這個對象不是基於插件的class loader創建的,這個要注意,但是它對插件resource獨立獲取沒任何影響,因爲

  1. context實例跟組件和Application都是一對一創建的,這就導致它不可能跟其他插件混淆
  2. context.getresource本質還是使用插件package res info創建AssertManager,它跟插件也是一對一綁定的

所以,只要完成了插件LoadedApk的創建,組件運行過程中的resource就可以正常獲取

總結

DroidPlugin的設計真的很巧妙,作者能構思出這種方案,對組件的初始化肯定是非常熟悉的,這套插件化方案出來也很多年了,最近看一遍,主要還是想學習作者的實現思路,同時也加深自己對組件初始化相關代碼的理解

組件實現能被偷天換日是基於Android這麼一個設計前提,AMS只是保存組件的邏輯對象主體,ActivityThread只是基於邏輯主體token來創建本地組件對象並做後續跟蹤,這就爲修改本地組件對象提供了可能

不過這種方式對系統潛入太大了,兼容性會比較差

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