第一步、先制做一个有我们需要的图片资源的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.testclassloader

2)、把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切换背景皮肤相关推荐

  1. Android插件化开发之DexClassLoader动态加载dex、jar小Demo

    一.温故动态加载ClassLoader机制 如果对Android的ClassLoader加载机制不熟悉,猛戳Android插件化开发动态加载基础之ClassLoader工作机制 http://blog ...

  2. Android插件化开发之AMS与应用程序(客户端ActivityThread、Instrumentation、Activity)通信模型分析

    转载来自:http://blog.csdn.net/qinjuning/article/details/7262769 今天主要分析下ActivityManagerService(服务端) 与应用程序 ...

  3. 插件化基础(二)——加载插件资源

    系列文章目录: 插件化基础(一)--加载插件的类 插件化基础(二)--加载插件资源 插件化基础(三)--启动插件组件 一.了解 Asset 和 Resources 我们加载的资源通常来自 res 和 ...

  4. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )

    文章目录 前言 一.RawDexFile.cpp 中 dvmRawDexFileOpen() 方法分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLoader 加载 ...

  5. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | 查找 DexFile 对应的C代码 | dalvik_system_DexFile.cpp 分析 )

    文章目录 前言 一.查找 DexFile 对应的 C++ 代码 1.根据 Native 文件命名惯例查找 C++ 代码 2.根据方法名查找 二.dalvik_system_DexFile.cpp 源码 ...

  6. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexFile loadDexFile 函数 | 构造函数 | openDexFile 函数 )

    文章目录 前言 一.DexFile.loadDexFile 函数分析 二.DexFile 构造函数分析 三.DexFile.openDexFile 函数分析 前言 上一篇博客 [Android 逆向] ...

  7. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 中根据 File 加载 DexFile | loadDexFile 分析 )

    文章目录 前言 一.根据 File 加载 DexFile 二.DexPathList.loadDexFile 函数分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLo ...

  8. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 构造函数分析 | makeDexElements 函数分析 )

    文章目录 前言 一.DexPathList 构造函数分析 二.DexPathList.makeDexElements 函数分析 三.Element 类分析 前言 上一篇博客 [Android 逆向]整 ...

  9. 【Android 插件化】使用 PluginKiller 帮助应用开发者规避发布的 APK 安装包被作为插件的风险 ( 验证应用是否运行在插件化引擎中 )

    文章目录 前言 一.应用开发者规避 APK 安装包被作为插件 二.检测插件化环境 1.检查 AndroidManifest.xml 清单文件 2.检查 运行时 信息 3.检查生成的目录 4.检查组件 ...

最新文章

  1. 如何将文件放到服务器,如何将服务器文件放到云服务器
  2. html最小化窗口,[转载]js实现窗口(支持拉伸,拖拽,最大化,最小化,滚动
  3. Android 防止快速点击
  4. 告诉大家一个------无敌命令
  5. 【重温基础】2.流程控制和错误处理
  6. react-redux中的持久化数据存储redux-persist
  7. java浏览文件夹_一个用java实现简单的文件浏览器
  8. 数据结构之图的应用:最短路径(Dijkstra、Floyd)
  9. HIS系统两种收费模式比较:前计费和后计费
  10. c语言中srand的作用,C语言中srand(), rand(), time()函数  转载
  11. [BZOJ4811][YNOI2017]由乃的OJ(树链剖分+线段树)
  12. 模拟登录,发送amf类型数据
  13. Android ANR:executing service com.xxx.yyy/cn.jpush.android.service.PushService
  14. java猜拳_Java猜拳小游戏(剪刀、石头、布)
  15. 用Python写一个任务管理器进行工作协调
  16. 给懒懒的Git操作手册
  17. 航拍“中国南北地理分界线” 感受独特景观
  18. BAT机器学习面试1000题系列(详细版)
  19. 用java写出杨辉三角。
  20. C/C++中关于交换(Swap)函数的三种方法

热门文章

  1. iNeuOS工业互联网操作系统部署在华为欧拉(openEuler)国产系统
  2. 为什么 HTTP3.0 使用 UDP 协议?
  3. .Net程序内存泄漏解析
  4. ASP.NET Core 集成 React SPA 应用
  5. 云原生那些顶级开源项目,你都用过哪些?
  6. 你是个失败者,有什么资格说话?
  7. .NET Core微服务开发服务间调用篇-GRPC
  8. ASP.NET Core在Docker下面生成简易验证码
  9. 程序员羽化之路--假如需要一百万个对象
  10. UnitTest in .NET(Part 1)