Android 插件化框架 Replugin 源碼解讀(三)插件加載

 
 
//com.qihoo360.loader2.PmBase
 
 final void callAttach() {
        //獲取ClassLoader
        mClassLoader = PmBase.class.getClassLoader();
 
        // 掛載
        for (Plugin p : mPlugins.values()) {
            p.attach(mContext, mClassLoader, mLocal);
        }
 
        // 加載默認插件
        if (PluginManager.isPluginProcess()) {
                //默認插件不爲空
            if (!TextUtils.isEmpty(mDefaultPluginName)) {
                //獲取插件
                Plugin p = mPlugins.get(mDefaultPluginName);
                if (p != null) {
                    boolean rc = p.load(Plugin.LOAD_APP, true);
                    if (!rc) {
                        
                    }
                    if (rc) {
                        mDefaultPlugin = p;
                        mClient.init(p);
                    }
                }
            }
        }
    }

上一篇中我們hook住系統的ClassLoader之後就調用callAttach()方法加載默認插件。首先通過設置的插件名字從插件表中獲取插件。是一個Plugin 對象,然後通過Plugin.load 實現加載。

Plugin.load


//com.qihoo360.loader2.Plugin

final boolean load(int load, boolean useCache) {
        PluginInfo info = mInfo;
        //加載插件
        boolean rc = loadLocked(load, useCache);
        // 嘗試在此處調用Application.onCreate方法
       
        if (load == LOAD_APP && rc) {
            callApp();
        }
        // 如果info改了,通知一下常駐
        // 只針對P-n的Type轉化來處理,一定要通知,這樣Framework_Version也會得到更新
        if (rc && mInfo != info) {
            UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
            Tasks.post2Thread(task);
        }
        return rc;
    }

1.這裏通過調用loadLocked 方法來加載插件

  //com.qihoo360.loader2.Plugin

private boolean loadLocked(int load, boolean useCache) {
        //判斷插件是否禁用
        int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
        if (status < PluginStatusController.STATUS_OK) {
            return false;
        }
        //判斷是否加載過,加載過就直接返回
        if (mInitialized) {
            if (mLoader == null) {
                
                return false;
            }
            if (load == LOAD_INFO) {
                boolean rl = mLoader.isPackageInfoLoaded();
               
                return rl;
            }
            if (load == LOAD_RESOURCES) {
                boolean rl = mLoader.isResourcesLoaded();
               
                return rl;
            }
            if (load == LOAD_DEX) {
                boolean rl = mLoader.isDexLoaded();
                
                return rl;
            }
            boolean il = mLoader.isAppLoaded();

            return il;
        }
        mInitialized = true;

        。。。

        // 這裏先處理一下,如果cache命中,省了後面插件提取(如釋放Jar包等)操作
        if (useCache) {
            boolean result = loadByCache(load);
            // 如果緩存命中,則直接返回
            if (result) {
                return true;
            }
        }

        Context context = mContext;
        ClassLoader parent = mParent;
        PluginCommImpl manager = mPluginManager;

      
        String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());

        //創建進程鎖
        ProcessLocker lock = new ProcessLocker(context, lockFileName);
   
       
        long t1 = System.currentTimeMillis();
        //加載插件
        boolean rc = doLoad(logTag, context, parent, manager, load);
        //解鎖
        lock.unlock();
        
        if (!rc) {
            
        }
        if (rc) {
            
            try {
                // 至此,該插件已開始運行
                PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
            } catch (Throwable e) {
                
            }

            return true;
        }

        再鎖一次
        lock = new ProcessLocker(context, lockFileName);
       
        // 刪除優化dex文件
        File odex = mInfo.getDexFile();
        if (odex.exists()) {
           
            odex.delete();
        }


        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // support for multidex below LOLLIPOP:delete Extra odex,if need
            try {
                FileUtils.forceDelete(mInfo.getExtraOdexDir());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e2) {
                e2.printStackTrace();
            }
        }

        t1 = System.currentTimeMillis();
        // 嘗試再次加載該插件
        rc = tryLoadAgain(logTag, context, parent, manager, load);
        
        lock.unlock();
        if (!rc) {
            
            return false;
        }

        try {
            // 至此,該插件已開始運行
            PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
        } catch (Throwable e) {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
            }
        }

        return true;
    }

2.在loadLocked方法中先判斷插件是否被禁用,然後判斷是否被加載過,加載過就返回,再判斷緩存中有沒有,有的話也返回,都沒有就去加載,先創建進程鎖,鎖住進程加載插件過程,調用doLoad 方法加載,加載成功就調用PluginManagerProxy.addToRunningPluginsNoThrows方法添加插件到"當前進程的正在運行插件列表",並同步到Server端。如果失敗就再加載一次插件。

  
  //com.qihoo360.loader2.Plugin

    private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
        if (mLoader == null) {
            // 試圖釋放文件
            PluginInfo info = null;
               //內置插件
            if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
                //判斷是否爲內置插件,如果是內置插件,在檢查是否已經釋放了so庫到指定位置,如果沒有先釋放到指定位置,並重新構造PluginInfo

               。。。。。。
     
                //p-n 插件
            } else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
                //判斷是否爲p-n類型並且未執行安裝插件步驟,如果是p-n類型並且未安裝,先執行插件的安裝操作,並重新構造PluginInfo
              。。。。。。。
              

            } else {
                //
            }

            //
            if (info != null) {
                // 替換
                mInfo = info;
            }

            //插件加載類
            mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
            //加載插件數據
            if (!mLoader.loadDex(parent, load)) {
                return false;
            }

            // 設置插件爲“使用過的”
            // 注意,需要重新獲取當前的PluginInfo對象,而非使用“可能是新插件”的mInfo
            try {
                PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
            } catch (RemoteException e) {
                // 同步出現問題,但仍繼續進行
                if (LOGR) {
                    e.printStackTrace();
                }
            }

            // 若需要加載Dex,則還同時需要初始化插件裏的Entry對象
            if (load == LOAD_APP) {
                // NOTE Entry對象是可以在任何線程中被調用到
                if (!loadEntryLocked(manager)) {
                    return false;
                }
                // NOTE 在此處調用則必須Post到UI,但此時有可能Activity已被加載
                //      會出現Activity.onCreate比Application更早的情況,故應放在load外面立即調用
                // callApp();
            }
        }

        if (load == LOAD_INFO) {
            return mLoader.isPackageInfoLoaded();
        } else if (load == LOAD_RESOURCES) {
            return mLoader.isResourcesLoaded();
        } else if (load == LOAD_DEX) {
            return mLoader.isDexLoaded();
        } else {
            return mLoader.isAppLoaded();
        }
    }

3.這裏如果沒有加載過,先判斷插件類型,如果是內置插件判斷so庫等是否已經釋放。沒有的話就釋放到指定的位置。如果是p-n插件然後創建加載類Loader 通過loadDex方法加載插件。加載成功後設置插件信息爲 "使用過"。然後他通過callApp啓動插件

//com.qihoo360.loader2.Loader;

final boolean loadDex(ClassLoader parent, int load) {
       
          try {
            //獲取PackageManager
            PackageManager pm = mContext.getPackageManager();
            //查看緩存
            mPackageInfo = Plugin.queryCachedPackageInfo(mPath);

            if (mPackageInfo == null) {
                // 緩存沒有PackageInfo 創建一個
                mPackageInfo = pm.getPackageArchiveInfo(mPath,
                        PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
                //插件不存在返回
                if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
                   
                    mPackageInfo = null;
                    return false;
                }
                //指定插件資源路徑
                mPackageInfo.applicationInfo.sourceDir = mPath;
                mPackageInfo.applicationInfo.publicSourceDir = mPath;

                // 添加針對SO庫的加載
                // 此屬性最終用於ApplicationLoaders.getClassLoader,在創建PathClassLoader時成爲其參數
                // 這樣findLibrary可不用覆寫,即可直接實現SO的加載
               
                PluginInfo pi = mPluginObj.mInfo;
                File ld = pi.getNativeLibsDir();
                mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();



                // 緩存表: pkgName -> pluginName
                synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
                    Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
                }

                // 緩存表: pluginName -> fileName
                synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
                    Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
                }

                // 緩存表: fileName -> PackageInfo
                synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
                    Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
                }
            }

            。。。。。。

            // 創建或獲取ComponentList表
           
            mComponents = Plugin.queryCachedComponentList(mPath);
            if (mComponents == null) {
                // ComponentList
                mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);

                // 動態註冊插件中聲明的 receiver
                regReceivers();

                // 緩存表:ComponentList
                synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
                    Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
                }

                /* 只調整一次 */
                // 調整插件中組件的進程名稱
                adjustPluginProcess(mPackageInfo.applicationInfo);

                // 調整插件中 Activity 的 TaskAffinity
                adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
            }
             //如果是加載插件中組件信息返回
            if (load == Plugin.LOAD_INFO) {
                return isPackageInfoLoaded();
            }
            //獲取緩存中插件中的資源
            mPkgResources = Plugin.queryCachedResources(mPath);
            // LOAD_RESOURCES和LOAD_ALL都會獲取資源,但LOAD_INFO不可以(只允許獲取PackageInfo)
            //沒有命中緩存獲取插件中的資源
            if (mPkgResources == null) {
                // Resources
                try {
                    if (BuildConfig.DEBUG) {
                        // 如果是Debug模式的話,防止與Instant Run衝突,資源重新New一個
                        Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
                        mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
                    } else {
                        mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
                    }
                } catch (NameNotFoundException e) {
                    
                    return false;
                }
                if (mPkgResources == null) {
                    
                    return false;
                }
                
                // 緩存表: Resources
                synchronized (Plugin.FILENAME_2_RESOURCES) {
                    Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
                }
            }
            //資源加載在這裏返回
            if (load == Plugin.LOAD_RESOURCES) {
                return isResourcesLoaded();
            }
            //獲取緩存中插件的ClassLoader
            mClassLoader = Plugin.queryCachedClassLoader(mPath);
            //沒有命中緩存
            if (mClassLoader == null) {
                // 先獲取父類加載器
                String out = mPluginObj.mInfo.getDexParentDir().getPath();
                
                if (BuildConfig.DEBUG) {
                   
                    parent = ClassLoader.getSystemClassLoader();
                } else {
                    // 線上環境保持不變
                    parent = getClass().getClassLoader().getParent(); // TODO: 這裏直接用父類加載器
                }
                //設置so 文件路徑
                String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
                
                //創建插件自己的PluginDexClassLoader
                mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
                
                if (mClassLoader == null) {
                   
                    return false;
                }
              
                // 緩存表:ClassLoader
                synchronized (Plugin.FILENAME_2_DEX) {
                    Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
                }
            }
            //加載dex在這裏返回
            if (load == Plugin.LOAD_DEX) {
                return isDexLoaded();
            }

            //創建插件apk使用的Context對象
            mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
           

        } catch (Throwable e) {
           
            return false;
        }

        return true;
    }

4.在loadDex方法中首先獲取插件的mPackageInfo判斷插件是否存在,並且指定插件apk的資源路徑,包括so庫的路徑最後把pluginName  mPath mPackageInfo 緩存起來。

然後創建ComponentList對象  這個對象用來快速獲取四大組件和Application的系統Info的List 每個Plugin對象維護一份ComponentList,且在第一次加載PackageInfo時被生成。

 /**
     * Class類名 - Activity的Map表
     */
    final HashMap<String, ActivityInfo> mActivities = new HashMap<>();

    /**
     * Class類名 - Provider的Map表
     */
    final HashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();

    /**
     * Authority - Provider的Map表
     */
    final HashMap<String, ProviderInfo> mProvidersByAuthority = new HashMap<>();

    /**
     * Class類名 - Service的Map表
     */
    final HashMap<String, ServiceInfo> mServices = new HashMap<>();

    /**
     * Application對象
     */
    ApplicationInfo mApplication = null;

    /**
     * Class類名 - BroadcastReceiver的Map表
     * 注意:是的,你沒有看錯,系統緩存Receiver就是用的ActivityInfo
     */
    final HashMap<String, ActivityInfo> mReceivers = new HashMap<>();

ComponentList在創建的時候解析了AndroidManifest.xml文件獲取四大組件信息生成組件與 IntentFilter 的對應關係並將這些信息和四大組件生成對應表關係並緩存,緩存插件apk的Application信息對象,接着動態註冊插件apk中在AndroidManifest中聲明的receiver,調整插件apk中四大組件的自定義進程名稱爲宿主坑位進程名稱和Activity的TaskAffinity

之後先通過pm.getResourcesForApplication創建插件apk的要使用的Resources對象 然後創建插件自己的ClassLoader對象PathClassLoader。這裏是PluginDexClassLoader 緩存這個ClassLoader對象

最後創建插件用的Context對象 。到這裏加載插件的準備工作都已經做好了,該有的數據都有了,現在再看看第3步中loadEntryLocked方法

//com.qihoo360.loader2.Plugin;

private boolean loadEntryLocked(PluginCommImpl manager) {
        if (mDummyPlugin) {
            
            mLoader.mPlugin = new IPlugin() {
                @Override
                public IModule query(Class<? extends IModule> c) {
                    return null;
                }
            };
        } else {
           //嘗試反射調用插件工程中Entry的create方法
            if (mLoader.loadEntryMethod2()) {
                //執行Entry的create方法
                if (!mLoader.invoke2(manager)) {
                    return false;
                }
            } else if (mLoader.loadEntryMethod(false)) {
                if (!mLoader.invoke(manager)) {
                    return false;
                }
            } else if (mLoader.loadEntryMethod3()) {
                if (!mLoader.invoke2(manager)) {
                    return false;
                }
            } else {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName());
                }
                return false;
            }
        }
        return true;
    }

 

//com.qihoo360.loader2.Loader

final boolean loadEntryMethod3() {
        
        try {
            // PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin";
            //PLUGIN_ENTRY_CLASS_NAME = "Entry";
           
            String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
            Class<?> c = mClassLoader.loadClass(className);

            //PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create"
            mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
        } catch (Throwable e) {
           
        }
        return mCreateMethod2 != null;
    }
 //com.qihoo360.replugin.Entry
 public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
    // 初始化插件框架,就是反射主工程框架中的類或者方法,方便插件工程之後直接調用
    RePluginFramework.init(cl);

    // 初始化主工程傳遞過來的Context和ClassLoader
    RePluginEnv.init(context, cl, manager);

    //返回插件工程中的服務管理Binder對象
    return new IPlugin.Stub() {
        @Override
        public IBinder query(String name) throws RemoteException {
            return RePluginServiceManager.getInstance().getService(name);
        }
    };
}

5.這邊通過反射調用插件工程中的Entry 的create 方法 並執行create 方法。這裏create方法反射了主工程框架中的一些方法,讓插件可以使用宿主的功能,接着就是緩存持有主工程傳遞過來的插件Context對象,這個Context是插件工程自己用的,還通過這個Context獲取了宿主的Context對象,這個對象主要是爲了用來獲取一些宿主中的資源,反射類等一些信息的,還緩存了宿主的ClassLoader對象,也是用來方便反射宿主中的一些類的,最後返回了插件工程中管理服務的Binder對象。具體插件初始化過程會在之後詳細介紹。

   // 確保在UI線程中調用
   
    private void callApp() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            callAppLocked();
        } else {
            // 確保一定在UI的最早消息處調用
            mMainH.postAtFrontOfQueue(new Runnable() {
                @Override
                public void run() {
                    callAppLocked();
                }
            });
        }
    }

6.在load完成之後調用callApp方法創建插件apk 的application 在UI線程中調用callAppLocked方法

 private void callAppLocked() {
        // 獲取並調用Application的幾個核心方法
        if (!mDummyPlugin) {
            // NOTE 不排除A的Application中調到了B,B又調回到A,或在同一插件內的onCreate開啓Service/Activity,而內部邏輯又調用fetchContext並再次走到這裏
            // NOTE 因此需要對mApplicationClient做判斷,確保永遠只執行一次,無論是否成功
            if (mApplicationClient != null) {
                // 已經初始化過,無需再次處理
                return;
            }
             //創建插件的application幷包裝成PluginApplicationClient返回
            mApplicationClient = PluginApplicationClient.getOrCreate(
                    mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);

            if (mApplicationClient != null) {
                //調用Application的AttachBaseContext方法
                mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
                 //調用Application的OnCreate方法
                mApplicationClient.callOnCreate();
            }
        } else {
            
        }
    }

7 創建了插件的appliication 幷包裝成PluginApplicationClient 。然後執行插件application 的 attach和onCreate方法。到這裏插件就加載完畢了,此時雖然沒有插件的組件調用,但是插件的application已經啓動了,也就是說其實插件已經啓動了。

總結

到這裏我們講解了Replugin框架初始化的內容。包含的內容我總結起來畫了一個簡單的圖。

 

 

 

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