1. 程式人生 > >VirtualAPK的使用及外掛載入

VirtualAPK的使用及外掛載入

VirtualAPK的使用

VirtualAPK的使用還是蠻簡單的,根據提供的文件一步一步來就可以了,但是其中有一點需要注意,那就是plugin的打包。plugin是且必須是一個apk檔案,但是我們不能像正常打包流程那樣進行打包,否則會丟擲java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity這個異常。我們需要執行命令列./gradlew clean assemblePlugin或者在AS右邊的Gradle裡點選assemblePlugin

來進行打包。這樣就可以使用VirtualApk來載入Plugin了。

Plugin的載入

LoadedPlugin中實現了Plugin的載入,主要程式碼如下:

    public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
        this.mPluginManager = pluginManager;
        this.mHostContext = context;//主工程的Context
        this.mLocation =
apk.getAbsolutePath();//外掛的路徑 //解析Plugin檔案(Plugin必須是一個apk檔案) this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK); this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData; this.mPackageInfo = new PackageInfo();//儲存plugin的包資訊
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo; this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath(); //簽名信息 if (Build.VERSION.SDK_INT >= 28 || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview try { this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures; } catch (Throwable e) { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); this.mPackageInfo.signatures = info.signatures; } } else { this.mPackageInfo.signatures = this.mPackage.mSignatures; } this.mPackageInfo.packageName = this.mPackage.packageName;//包名 if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {//判斷plugin是否已經載入 throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName); } this.mPackageInfo.versionCode = this.mPackage.mVersionCode; this.mPackageInfo.versionName = this.mPackage.mVersionName; this.mPackageInfo.permissions = new PermissionInfo[0]; this.mPackageManager = createPluginPackageManager();//建立plugin包管理 this.mPluginContext = createPluginContext(null);//建立外掛的context this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR); this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath(); this.mResources = createResources(context, getPackageName(), apk);//建立Resources //建立一個ClassLoader,載入plugin中的class this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader()); tryToCopyNativeLib(apk);//copy外掛的so檔案到主工程 // Cache instrumentations Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>(); for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) { instrumentations.put(instrumentation.getComponentName(), instrumentation.info); } this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);//將mInstrumentationInfos改為只讀 this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]); // Cache activities Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity activity : this.mPackage.activities) { activity.info.metaData = activity.metaData; activityInfos.put(activity.getComponentName(), activity.info); } this.mActivityInfos = Collections.unmodifiableMap(activityInfos);//將mActivityInfos改為只讀 this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]); // Cache services Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>(); for (PackageParser.Service service : this.mPackage.services) { serviceInfos.put(service.getComponentName(), service.info); } this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);//將mServiceInfos改為只讀 this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]); // Cache providers Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>(); Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>(); for (PackageParser.Provider provider : this.mPackage.providers) { providers.put(provider.info.authority, provider.info); providerInfos.put(provider.getComponentName(), provider.info); } this.mProviders = Collections.unmodifiableMap(providers);//將mProviders改為只讀 this.mProviderInfos = Collections.unmodifiableMap(providerInfos); this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]); // Register broadcast receivers dynamically Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity receiver : this.mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); //建立BroadcastReceiver例項 BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); for (PackageParser.ActivityIntentInfo aii : receiver.intents) { this.mHostContext.registerReceiver(br, aii);//將plugin中的靜態BroadcastReceiver轉動態註冊 } } this.mReceiverInfos = Collections.unmodifiableMap(receivers);//將mReceiverInfos改為只讀 this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]); // try to invoke plugin's application invokeApplication();//建立plugin的Application物件並呼叫onCreate方法 }

createResources的主要實現如下

    protected Resources createResources(Context context, String packageName, File apk) throws Exception {
        if (Constants.COMBINE_RESOURCES) {//將plugin的資源與主工程的資源合併
            return ResourcesManager.createResources(context, packageName, apk);
        } else {//為plugin建立單獨的資源管理器
            Resources hostResources = context.getResources();
            AssetManager assetManager = createAssetManager(context, apk);
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
        }
    }

 合併資源的程式碼主要在ResourcesManager中,主要是我們新建立一個包含主專案及plugin專案資源的Resources物件,然後通過反射進行替換。在這裡最主要的問題時由於國內對Android系統進行了修改,所以導致相容性有問題。
createClassLoader主要實現如下

    protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
        File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
        String dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

        if (Constants.COMBINE_CLASSLOADER) {//如果合併,則將plugin中的dex檔案插入到主工程的dex檔案之後
            DexUtil.insertDex(loader, parent, libsDir);
        }

        return loader;
    }

 主要就是為Plugin建立一個ClassLoader來載入dex檔案,如果Constants.COMBINE_CLASSLOADERtrue,則將plugin中的dex檔案插入到主專案的dex列表中,程式碼如下


    public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
        Object baseDexElements = getDexElements(getPathList(baseClassLoader));//獲取主專案的dex列表
        Object newDexElements = getDexElements(getPathList(dexClassLoader));//獲取plugin的dex檔案列表
        Object allDexElements = combineArray(baseDexElements, newDexElements);//合併主工程與plugin的dex檔案,plugin的dex檔案在主工程後面
        Object pathList = getPathList(baseClassLoader);
        Reflector.with(pathList).field("dexElements").set(allDexElements);//將合併後的dex列表設定給主工程

        insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);//
    }

 最後就是通過invokeApplication去建立plugin中的Application並且呼叫其生命週期。其實plugin的載入流程跟Android安裝一個apk的流程有點類似的。可以說是簡化版的安裝apk。

Android外掛化快速入門與例項解析(VirtualApk)