Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤
生活随笔
收集整理的這篇文章主要介紹了
Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
第一步、先制做一個有我們需要的圖片資源的APK
如下圖,這里有個about_log.png,我們需要生成apk文件。生成的apk文件如果你不到項目的文件夾里面去取apk,想通過命令放到手機里面去可以快速用下面命令
1)、在手機里面通過包名找到apk路徑,一定不要忘記有 -f
?
adb shell pm list package -f | grep com.example.testclassloader得到如下結果
?
?
package:/data/app/com.example.testclassloader-2/base.apk=com.example.testclassloader2)、把base.apk拉到本地然后改名字,命令如下
?
adb shell pull /data/app/com.example.testclassloader-2/base.apk testClassLoader.apk?
3)、把testClassLoader.apk放到手機里面去,命令如下
?
?
adb shell push testClassLoader.apk /sdcard/?
?
?
4)、去手機文件管理器里面找看是否有testClassLoader.apk文件
?
第二步、獲取為安裝apk包名的信息(假設前提不知道)
我們可以通過這個方法得到
?
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags)?
具體方法如下
?
/** * 獲取未安裝apk的信息 * @param context * @param apkPath apk文件的path * @return */ private Map<String,String> getUninstallApkInfo(Context context, String apkPath) { Map hashMap = new HashMap<String,String>();PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (null != pkgInfo) { ApplicationInfo appInfo = pkgInfo.applicationInfo; String pkgName = appInfo.packageName;//包名 hashMap.put(PKG_NAME, pkgName);} else {Log.d(TAG, "program don't get apk package information");} return hashMap; }?
第三步、獲取未安裝apk(插件)的Resource
?
因為沒有安裝,所以不能得到context,所以我們需要未安裝apk的Resource,我們可以通過反射來獲取,代碼如下
?
/** * @param apkPath * @return 得到對應插件的Resource對象 */ private Resources getPluginResources(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); //反射調用方法addAssetPath(String path)Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class); //將未安裝的Apk文件的添加進AssetManager中,第二個參數是apk的路徑 addAssetPath.invoke(assetManager, apkPath);Resources superRes = this.getResources(); Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return mResources; } catch (Exception e) { e.printStackTrace(); } return null; }?
?
?
第四步、用DexClassLoader加載apk資源文件替換背景
如果你多DexClassLoader用法和原理不熟悉,可以參考我之前的博客 Android插件化開發之DexClassLoader動態加載dex、jar小Demo ?http://blog.csdn.net/u011068702/article/details/53263442 Android插件化開發之動態加載基礎之ClassLoader工作機制 ?http://blog.csdn.net/u011068702/article/details/53248960 代碼如下: /** * 加載apk獲得內部資源,并且替換背景* @param apkDir apk目錄 * @param apkName apk名字,帶.apk * @throws Exception */ private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {//在應用安裝目錄下創建一個名為app_dex文件夾目錄,如果已經存在則不創建,這個目錄主要是最優化目錄,用于緩存dex文件File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); //打印路徑 理論上是/data/data/package/app_dexLog.v(TAG, optimizedDirectoryFile.getPath().toString()); //構建DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader()); //通過使用apk自己的類加載器,反射出R類中相應的內部類進而獲取我們需要的資源idClass<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE); //得到名為about_log的這張圖片字段,這個圖片是為安裝apk里面的圖片Field field = clazz.getDeclaredField(IMAGE_ID); //得到圖片id int resId = field.getInt(R.id.class);//得到插件apk中的Resource Resources mResources = getPluginResources(apkPath);if (mResources != null) { //通過插件apk中的Resource得到resId對應的資源 Drawable btnDrawable = mResources.getDrawable(resId);mLayout.setBackgroundDrawable(btnDrawable); } else {Log.d(TAG, "mResources is null");}} ?第五步、爆出所有代碼(為了詳細點)
package com.chenyu.dexclassloaderapk;import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView;import com.example.dexclassloaderapk.R;import dalvik.system.DexClassLoader;public class MainActivity extends ActionBarActivity {public static final String TAG = "DexClassLoaderApk";public static final String PKG_NAME = "pkgName";public static final String APK_PATH = "testClassLoader.apk";public static final String ADDSSETPATH = "addAssetPath";public static final String DEX = "dex";//這個IMAGE_ID是只我放入手機里面APK 在代碼里面這個圖片的ID,這里我們拿到之后,然后去替換北京圖片public static final String IMAGE_ID = "about_log";public static final String DRAWABLE = ".R$drawable";public TextView mTextView;//背景的布局public RelativeLayout mLayout;public Map<String, String> apkInfo;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final String apkPath = Environment.getExternalStorageDirectory().toString() + File.separator + APK_PATH;mTextView = (TextView)findViewById(R.id.text);mLayout = (RelativeLayout)findViewById(R.id.re_Layout);mTextView.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {//一定要記得加上android.permission.READ_EXTERNAL_STORAGE權限,不然死活都拿不到數據//我就換了一個這個錯誤,如果發現代碼沒問題,網上找也沒問題,這個時候應該思考是不是沒有加權限apkInfo = getUninstallApkInfo(MainActivity.this, apkPath);String packageName = apkInfo.get(PKG_NAME);if (null != packageName) {try {dynamicLoadApk(apkPath, packageName);} catch (Exception e) {e.printStackTrace();Log.i(TAG, "change image fail");}} else {Log.i(TAG, "package is null");}}});}/** * 獲取未安裝apk的信息 * @param context * @param apkPath apk文件的path * @return */ private Map<String,String> getUninstallApkInfo(Context context, String apkPath) { Map hashMap = new HashMap<String,String>();PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (null != pkgInfo) { ApplicationInfo appInfo = pkgInfo.applicationInfo; String pkgName = appInfo.packageName;//包名 hashMap.put(PKG_NAME, pkgName);} else {Log.d(TAG, "program don't get apk package information");} return hashMap; } /** * @param apkPath * @return 得到對應插件的Resource對象 */ private Resources getPluginResources(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); //反射調用方法addAssetPath(String path)Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class); //將未安裝的Apk文件的添加進AssetManager中,第二個參數是apk的路徑 addAssetPath.invoke(assetManager, apkPath);Resources superRes = this.getResources(); Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return mResources; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 加載apk獲得內部資源,并且替換背景* @param apkDir apk目錄 * @param apkName apk名字,帶.apk * @throws Exception */ private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {//在應用安裝目錄下創建一個名為app_dex文件夾目錄,如果已經存在則不創建,這個目錄主要是最優化目錄,用于緩存dex文件File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); //打印路徑 理論上是/data/data/package/app_dexLog.v(TAG, optimizedDirectoryFile.getPath().toString()); //構建DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader()); //通過使用apk自己的類加載器,反射出R類中相應的內部類進而獲取我們需要的資源idClass<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE); //得到名為about_log的這張圖片字段,這個圖片是為安裝apk里面的圖片Field field = clazz.getDeclaredField(IMAGE_ID); //得到圖片id int resId = field.getInt(R.id.class);//得到插件apk中的Resource Resources mResources = getPluginResources(apkPath);if (mResources != null) { //通過插件apk中的Resource得到resId對應的資源 Drawable btnDrawable = mResources.getDrawable(resId);mLayout.setBackgroundDrawable(btnDrawable); } else {Log.d(TAG, "mResources is null");}} } dynamicLoadApk(apkPath, packageName);} catch (Exception e) {e.printStackTrace();Log.i(TAG, "change image fail");}} else {Log.i(TAG, "package is null");}}});}/** * 獲取未安裝apk的信息 * @param context * @param apkPath apk文件的path * @return */ private Map<String,String> getUninstallApkInfo(Context context, String apkPath) { Map hashMap = new HashMap<String,String>();PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); if (null != pkgInfo) { ApplicationInfo appInfo = pkgInfo.applicationInfo; String pkgName = appInfo.packageName;//包名 hashMap.put(PKG_NAME, pkgName);} else {Log.d(TAG, "program don't get apk package information");} return hashMap; } /** * @param apkPath * @return 得到對應插件的Resource對象 */ private Resources getPluginResources(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); //反射調用方法addAssetPath(String path)Method addAssetPath = assetManager.getClass().getMethod(ADDSSETPATH, String.class); //將未安裝的Apk文件的添加進AssetManager中,第二個參數是apk的路徑 addAssetPath.invoke(assetManager, apkPath);Resources superRes = this.getResources(); Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return mResources; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 加載apk獲得內部資源,并且替換背景* @param apkDir apk目錄 * @param apkName apk名字,帶.apk * @throws Exception */ private void dynamicLoadApk(String apkPath, String apkPackageName) throws Exception {//在應用安裝目錄下創建一個名為app_dex文件夾目錄,如果已經存在則不創建,這個目錄主要是最優化目錄,用于緩存dex文件File optimizedDirectoryFile = getDir(DEX, Context.MODE_PRIVATE); //打印路徑 理論上是/data/data/package/app_dexLog.v(TAG, optimizedDirectoryFile.getPath().toString()); //構建DexClassLoaderDexClassLoader dexClassLoader = new DexClassLoader(apkPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader()); //通過使用apk自己的類加載器,反射出R類中相應的內部類進而獲取我們需要的資源idClass<?> clazz = dexClassLoader.loadClass(apkPackageName + DRAWABLE); //得到名為about_log的這張圖片字段,這個圖片是為安裝apk里面的圖片Field field = clazz.getDeclaredField(IMAGE_ID); //得到圖片id int resId = field.getInt(R.id.class);//得到插件apk中的Resource Resources mResources = getPluginResources(apkPath);if (mResources != null) { //通過插件apk中的Resource得到resId對應的資源 Drawable btnDrawable = mResources.getDrawable(resId);mLayout.setBackgroundDrawable(btnDrawable); } else {Log.d(TAG, "mResources is null");}} }點擊TextView內容“換皮膚”來觸發的,當初背景是設置的一個機器人。
第六步:運行項目爆結果照片
點擊換皮護之前背景圖片如下 點擊換圖片之后背景圖片如下 ok,說明獲取到了這種圖片資源,換皮膚成功,這里只是代表換皮膚意思,效果比較丑,不要噴哈。第七步、總結
這樣做資源和宿主分離了,減輕了apk負擔,同時也有解耦和作用,我們手機一些瀏覽器換模式(日和夜)、QQ換皮膚、表情包、線上下載線下維護、是項目更加靈活,可擴展性更好,同時也復習了DexClassLoader和反射相關知識。?
?
?
?
總結
以上是生活随笔為你收集整理的Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android插件化开发之动态加载三个关
- 下一篇: java反射异常之java.lang.N