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.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和反射相关知识。

 

 


 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292461.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ManualResetEvent实现线程的暂停与恢复

背景前些天遇到一个需求&#xff0c;在没有第三方源码的情况下&#xff0c;刷新一个第三方UI&#xff0c;并且拦截到其ajax请求的返回结果。当结果为AVALIABLE的时候&#xff0c;停止刷新并语音提示&#xff0c;否则继续刷新。分析这个需求&#xff0c;发现需要控制一个刷新循环…

浙大哈佛剑桥学者联手破解数学界几十年的谜题,成果登上数学顶刊

全世界只有3.14 % 的人关注了爆炸吧知识转自&#xff1a;量子位作者&#xff1a;边策 萧箫当两个看似“无关”的数学领域发生碰撞&#xff0c;会发生什么&#xff1f;浙江大学研究员、中科大数学系2003级校友叶和溪&#xff0c;与来自剑桥大学、哈佛大学的两位学者一起&#xf…

Hibernate 参数设置一览表

Hibernate 参数设置一览表属性名用途hibernate.dialect一个Hibernate Dialect类名允许Hibernate针对特定的关系数据库生成优化的SQL. 取值 full.classname.of.Dialect hibernate.show_sql输出所有SQL语句到控制台. 有一个另外的选择是把org.hibernate.SQL这个log category设为d…

jsp将鼠标放到那显示信息

将下面的代码&#xff0c;粘贴复制到jsp<body></body>中<A href"#" οnmοuseοver"document.getElementById(aaa).style.display" οnmοuseοut"document.getElementById(aaa).style.displaynone">鼠标移动到这我就出来了&l…

Monkey测试实例

Monkey是Android中的一个命令行工具&#xff0c;可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等)&#xff0c;实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。1…

.NET Core Runtime vs .NET Framework Runtime

在我从 .NET Framework 到 .NET Core 的过渡期间&#xff0c;有一件事情让我很长时间都感到困惑&#xff0c;那就是 Runtime 运行时&#xff0c; 实际上 Runtime 在 .NET Framework 和 .NET Core 中具有不同的含义。.NET Framework 运行时的历史 当有人问我们&#xff0c;什么是…

“玻璃大王”曹德旺捐资100亿办大学!幼年失学的他要打造理工科研究型大学...

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术整理自&#xff1a;量子位&#xff08;作者贾浩楠、鱼羊&#xff09;、青塔等100亿投资&#xff0c;福建省迎来第一个“新型大学”。办学资金&#xff0c;来自生长于福建&#xff0c;在福建发家致富的“玻璃大王”曹德旺…

JAVA配置Tomcat

1.下载tomcat&#xff0c;我jdk是1.8的&#xff0c;网上查了一下&#xff0c;说要安装tomcat8及以上的tomcat 尝试点击&#xff0c;弹出&#xff0c; 2.配置环境 3.安装通过cmd安装 4.点击开启服务 5.输入localhost:8080查看 6.还可以通过tomcat8w.exe来启动窗口&#xff0c;开…

女孩看男孩VS男孩看女孩

1 这波猫粮我吃了&#xff01;2 论一只狗子的自娱自乐精神&#xff01;3 分享一个 “想死你了”的拥抱&#xff01;4 老师&#xff1a;下课铃不代表这节课上完了5 谁怕谁啊&#xff01;6 好像很有道理的样子7 将它们展开会是什么&#xff1f;你点的每个赞&#xff0c;我都认真当…

使用建造者模式创建模拟数据

前言在写测试用例时&#xff0c;我们经常需要创建模拟数据&#xff0c;在C#中常用的方式是使用nuget包Bogus。Bogus可以按照一定规则生成随机数据&#xff0c;示例代码如下&#xff1a;public class User {public string Name { get; set; }public int Age { get; set; }public…

JMeter学习(六)集合点

JMeter也有像LR中的集合点&#xff0c;本篇就来介绍下JMeter的集合点如何去实现。 JMeter里面的集合点通过添加定时器来完成。 注意&#xff1a;集合点的位置一定要在Sample之前. 集合点&#xff1a;简单来理解一下&#xff0c;虽然我们的“性能测试”理解为“多用户并发测试”…

无意间看到的浏览器记录......

1 喵&#xff1a;我是风一样的喵主子...啊错了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 老了谁照顾你&#xff1f;▼3 帮妈妈清理手机垃圾▼4 你现在做什么工作&#xff1f;&#xff08;via.欢声笑语bot&#xff0c;侵删&#xff09;▼5 社会社会&#xf…

linux C/C++开发环境搭建指南

2019独角兽企业重金招聘Python工程师标准>>> Eclipse IDE for C/C developers 就是安装了CDT的Eclipse IDE了。 一、安装基本开发环境 本人想学习一下Linux下的C&#xff0c;C&#xff0b;&#xff0b;程序开发&#xff0c;这几天一直在研究Linux下的C语言编译环境…

网站能拿到其他网站的cookie_如何设计网站能让网站建设的更有吸引力

现在做互联网营销的企业基本都是有自己的企业网站&#xff0c;不过要做好企业网站不能简简单单的套个模板&#xff0c;这样做出来的网站毫无吸引力&#xff0c;做好的网站那么网站的设计的很重要的&#xff0c;换个思维大家想一下&#xff0c;网站的设计就和我们线下开门店装修…

.NET6之MiniAPI(九):基于角色的身份验证和授权

身份验证是这样一个过程&#xff1a;由用户提供凭据&#xff0c;然后将其与存储在操作系统、数据库、应用或资源中的凭据进行比较。 在授权过程中&#xff0c;如果凭据匹配&#xff0c;则用户身份验证成功&#xff0c;可执行已向其授权的操作。 授权指判断允许用户执行的操作的…

flex容器属性(一)

一&#xff0c;概念 flexible box ,意为“弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。 块级布局更侧重于垂直方向&#xff0c;行内布局更侧重于水平方向&#xff0c;于此相对的&#xff0c;弹性盒子布局算法是方向无关的。 块级flex布局&#xff1a; .box{display…

关于最近打的几题斜率优化的总结。加几AC代码。

斜率优化错误总结 网上说很多OJ桑的斜率优化大多都是模板题- -&#xff0c;结果每次都跪Orz。。。在此总结一些常见错误&#xff1a; 1&#xff1a;不得不说斜率优化很多时候计算式很长- -&#xff0c;代码容易错细节- -。 2&#xff1a;其次就是弹队头以及弹队尾的时候大小关系…

iio Engine logoHTML5 应用框架 iio Engine

iio Engine 是一个新的 HTML5 应用开源框架&#xff0c;基于 JavaScript 和 Canvas 开发&#xff0c;集成了 Box2D 在线演示&#xff1a;http://www.huiyi8.com/divcss/转载于:https://www.cnblogs.com/lhrs/p/4138106.html

带圈汉字 在线生成_手写签名在线生成器-手写签名在线生成器可复制

签名设计地址&#xff1a;www.mgs2s.com&#xff08;复制到浏览器打开&#xff09;工具集成签名设计免费版下载&#xff0c;签名设计免费版在线立即生成&#xff0c;简单简体签名设计免费版。最新方便设计公文签名设计颜色保存分享免费版1、所以大家最好写签名的时候&#xff0…