VirtualAPK的使用及外掛載入
阿新 • • 發佈:2018-12-18
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
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_CLASSLOADER
為true
,則將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。