動態(tài)加載:
加載已安裝和未安裝的apk
動態(tài)加載技術(shù)就是使用類加載器加載相應(yīng)的apk思劳、dex呆细、jar(必須含有dex文件)侨嘀,再通過反射獲得該apk臭挽、dex、jar內(nèi)部的資源
(class咬腕、圖片欢峰、color等等)進(jìn)而供宿主app使用。
PathClassLoader 獲取已安裝的apk
linux user id宿主app和插件app的manifest上都定義一個相同的sharedUserId
PackageInfo.sharedUserId
獲取手機(jī)內(nèi)存在的所有插件涨共,沒有找到則提示去下載
/第一個參數(shù)為包含dex的apk或者jar的路徑纽帖,第二個參數(shù)為父加載器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath
(),ClassLoader.getSystemClassLoader());
兩種方式獲取
1、Class<?> clazz = pathClassLoader.loadClass(packageName + ".R$mipmap");//通過使用自身的加載器反射出mipmap類進(jìn)而使用
該類的功能
2举反、//參數(shù):1懊直、類的全名,2火鼻、是否初始化類室囊,3、加載時使用的類加載器
Class<?> clazz = Class.forName(packageName + ".R$mipmap", true, pathClassLoader);
Field field = clazz.getDeclaredField("one");
int resourceId = field.getInt(R.mipmap.class);
創(chuàng)建插件的上下文
Context plugnContext = this.createPackageContext(packageName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE);
DexClassLoader不需要提前安裝apk
首先我們得到事先知道我們的插件apk存放在哪個目錄下魁索,然后分別得到插件apk的信息(名稱融撞、包名等),然后顯示可用的插件粗蔚,最
后動態(tài)加載apk獲得資源
1尝偎、獲取未安裝的apk信息public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags)
? private String[] getUninstallApkInfo(Context context, String archiveFilePath) {
? ? ? ? String[] info = new String[2];
? ? ? ? PackageManager pm = context.getPackageManager();
? ? ? ? PackageInfo pkgInfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
? ? ? ? if (pkgInfo != null) {
? ? ? ? ? ? ApplicationInfo appInfo = pkgInfo.applicationInfo;
? ? ? ? ? ? String versionName = pkgInfo.versionName;//版本號
? ? ? ? ? ? Drawable icon = pm.getApplicationIcon(appInfo);//圖標(biāo)
? ? ? ? ? ? String appName = pm.getApplicationLabel(appInfo).toString();//app名稱
? ? ? ? ? ? String pkgName = appInfo.packageName;//包名
? ? ? ? ? ? info[0] = appName;
? ? ? ? ? ? info[1] = pkgName;
? ? ? ? }
? ? ? ? return info;
2、得到對應(yīng)未安裝apk的Resource對象鹏控,我們需要通過反射來獲得:反射調(diào)用addAssetPath致扯,將未安裝的Apk文件的添加進(jìn)
AssetManager中趁窃,第二個參數(shù)為apk文件的路徑帶apk名
通過得到AssetManager中的內(nèi)部的方法addAssetPath,將未安裝的apk路徑傳入從而添加進(jìn)assetManager中急前,然后通過new Resource把
assetManager傳入構(gòu)造方法中醒陆,進(jìn)而得到未安裝apk對應(yīng)的Resource對象。
? private Resources getPluginResources(String apkName) {
? ? ? ? try {
? ? ? ? ? ? AssetManager assetManager = AssetManager.class.newInstance();
? ? ? ? ? ? Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//反射調(diào)用方法
addAssetPath(String path)
? ? ? ? ? ? //第二個參數(shù)是apk的路徑:Environment.getExternalStorageDirectory().getPath()+File.separator
+"plugin"+File.separator+"apkplugin.apk"
? ? ? ? ? ? addAssetPath.invoke(assetManager, apkDir+File.separator+apkName);//將未安裝的Apk文件的添加進(jìn)AssetManager
中裆针,第二個參數(shù)為apk文件的路徑帶apk名
? ? ? ? ? ? Resources superRes = this.getResources();
? ? ? ? ? ? Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
? ? ? ? ? ? ? ? ? ? superRes.getConfiguration());
? ? ? ? ? ? return mResources;
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? return null;
3刨摩、加載未安裝的apk獲得它的內(nèi)部資源
? /**
? ? * 加載apk獲得內(nèi)部資源
? ? * @param apkDir apk目錄
? ? * @param apkName apk名字,帶.apk
? ? * @throws Exception
? ? */
? ? private void dynamicLoadApk(String apkDir, String apkName, String apkPackageName) throws Exception {
? ? ? ? File optimizedDirectoryFile = getDir("dex", Context.MODE_PRIVATE);//在應(yīng)用安裝目錄下創(chuàng)建一個名為app_dex文件夾
目錄,如果已經(jīng)存在則不創(chuàng)建
? ? ? ? Log.v("zxy", optimizedDirectoryFile.getPath().toString());// /data/data/com.example.dynamicloadapk/app_dex
? ? ? ? //參數(shù):1、包含dex的apk文件或jar文件的路徑世吨,2澡刹、apk、jar解壓縮生成dex存儲的目錄耘婚,3罢浇、本地library庫目錄,一般為
null沐祷,4嚷闭、父ClassLoader
? ? ? ? DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName,
optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());
? ? ? ? Class<?> clazz = dexClassLoader.loadClass(apkPackageName + ".R$mipmap");//通過使用apk自己的類加載器,反射出R
類中相應(yīng)的內(nèi)部類進(jìn)而獲取我們需要的資源id
? ? ? ? Field field = clazz.getDeclaredField("one");//得到名為one的這張圖片字段
? ? ? ? int resId = field.getInt(R.id.class);//得到圖片id
? ? ? ? Resources mResources = getPluginResources(apkName);//得到插件apk中的Resource
? ? ? ? if (mResources != null) {
? ? ? ? ? ? //通過插件apk中的Resource得到resId對應(yīng)的資源
? ? ? ? ? ? findViewById(R.id.background).setBackgroundDrawable(mResources.getDrawable(resId));
? ? ? ? }
首先通過類加載器去加載apk中activity的類并創(chuàng)建一個新對象赖临,然后通過反射去調(diào)用這個對象的setProxy方法和onCreate方法胞锰,
setProxy方法的作用是將activity內(nèi)部的執(zhí)行全部交由宿主程序中的proxy(也是一個activity),onCreate方法是activity的入口兢榨,
setProxy以后就調(diào)用onCreate方法嗅榕,這個時候activity就被調(diào)起來了。