Android插件化开发之运行未安装apk的activity

1、介绍

我们知道PathClassLoader是一个应用的默认加载器(而且他只能加载data/app/xxx.apk的文件),但是我们加载插件一般使用DexClassLoader加载器,所以这里就有问题了,其实如果对于开始的时候,每个人都会认为很简单,很容易想到使用DexClassLoader来加载Activity获取到class对象,在使用Intent启动

 

2、替换LoadApk里面的mClassLoader

我们知道我们可以将我们使用的DexClassLoader加载器绑定到系统加载Activity的类加载器上就可以了,这个是我们的思路。也是最重要的突破点。下面我们就来通过源码看看如何找到加载Activity的类加载器。加载Activity的时候,有一个很重要的类:LoadedApk.Java,这个类是负责加载一个Apk程序的,我们可以看一下他的源码:

我们知道内部有个mClassLoader成员变量,我们只需要获取它就可以了,因为它不是静态的,所以我们需要先获取LoadApk这个类的对象,我们再去

看看ActivityThread.java这个类

我们可以发现ActivityThread里面有个静态的成员变量sCurrentActivityThread,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构,我们通过反射来获取mClassLoader对象。

如果对ActivityThread.java这个类不熟悉的可以看我这篇博客,http://blog.csdn.net/u011068702/article/details/53207039 (Android插件化开发之AMS与应用程序(客户端ActivityThread、Instrumentation、Activity)通信模型分析)非常重要,是app程序的入口处。

 

3、实现具体代码

     1)我们先需要一个测试apk,然后把这个测试的test.apk,放到手机sdcard里面去,关键代码如下
package com.example.testapkdemo;import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;public class MainActivity extends ActionBarActivity {public static View parentView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (parentView == null) {setContentView(R.layout.activity_main);} else {setContentView(parentView);}findViewById(R.id.button).setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, "我是来自插件", Toast.LENGTH_SHORT).show();}});}public void setView(View view) {this.parentView = view;}
}
效果图如下:

 

接下来是我宿主代码:

ReflectHelper.java  这个是的我的反射帮助类

 

package com.example.dexclassloaderactivity;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;/*** 反射辅助函数* @author**/
public class ReflectHelper {public static final Class<?>[] PARAM_TYPE_VOID = new Class<?>[]{};public static Object invokeStaticMethod(String className, String methodName, Class<?>[] paramTypes, Object...params) {try {Class<?> clazz = Class.forName(className);Method method = clazz.getMethod(methodName, paramTypes);method.setAccessible(true);return method.invoke(null, params);} catch (Exception e) {e.printStackTrace();}return null;}public static Object invokeMethod(String className, String methodName, Class<?>[] paramTypes, Object obj, Object...params) {try {Class<?> clazz = Class.forName(className);Method method = clazz.getMethod(methodName, paramTypes);method.setAccessible(true);return method.invoke(obj, params);} catch (Exception e) {e.printStackTrace();}return null;}public static Object getStaticField(String className, String fieldName) {try {Class<?> clazz = Class.forName(className);Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);return field.get(null);} catch (Exception e) {e.printStackTrace();}return null;}public static Object getField(String className, String fieldName, Object obj) {try {Class<?> clazz = Class.forName(className);Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);return field.get(obj);} catch (Exception e) {e.printStackTrace();}return null;}public static void setStaticField(String className, String fieldName, Object value) {try {Class<?> clazz = Class.forName(className);Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(null, value);} catch (Exception e) {e.printStackTrace();}}public static void setField(String className, String fieldName, Object obj, Object value) {try {Class<?> clazz = Class.forName(className);Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);} catch (Exception e) {e.printStackTrace();}}public static Object createInstance(String className, Class<?>[] paramTypes, Object...params) {Object object = null;try {Class<?> cls = Class.forName(className);Constructor<?> constructor = cls.getConstructor(paramTypes);constructor.setAccessible(true);object = constructor.newInstance(params);}catch (Exception e) {e.printStackTrace();}return object;}/*** Locates a given field anywhere in the class inheritance hierarchy.** @param instance an object to search the field into.* @param name field name* @return a field object* @throws NoSuchFieldException if the field cannot be located*/public static Field findField(Object instance, String name) throws NoSuchFieldException {for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {try {Field field = clazz.getDeclaredField(name);if (!field.isAccessible()) {field.setAccessible(true);}return field;} catch (NoSuchFieldException e) {// ignore and search next}}throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());}/*** Locates a given field anywhere in the class inheritance hierarchy.** @param  cls to search the field into.* @param name field name* @return a field object* @throws NoSuchFieldException if the field cannot be located*/public static Field findField2(Class<?> cls, String name) throws NoSuchFieldException {Class<?> clazz = null;for (clazz = cls; clazz != null; clazz = clazz.getSuperclass()) {try {Field field = clazz.getDeclaredField(name);if (!field.isAccessible()) {field.setAccessible(true);}return field;} catch (NoSuchFieldException e) {// ignore and search next}}throw new NoSuchFieldException("Field " + name + " not found in " + clazz);}/*** Locates a given method anywhere in the class inheritance hierarchy.** @param instance an object to search the method into.* @param name method name* @param parameterTypes method parameter types* @return a method object* @throws NoSuchMethodException if the method cannot be located*/public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)throws NoSuchMethodException {for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {try {Method method = clazz.getDeclaredMethod(name, parameterTypes);if (!method.isAccessible()) {method.setAccessible(true);}return method;} catch (NoSuchMethodException e) {// ignore and search next}}throw new NoSuchMethodException("Method " + name + " with parameters " +Arrays.asList(parameterTypes) + " not found in " + instance.getClass());}
}


MyApplication.java 这个类用实现替换mClassLoader

 

 

package com.example.dexclassloaderactivity;import java.io.File;
import java.lang.ref.WeakReference;import dalvik.system.DexClassLoader;
import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.Log;public class MyApplication extends Application{public static final String TAG = "MyApplication";public static final String AppName = "test.apk";public static int i = 0;public static DexClassLoader mClassLoader;@Overridepublic void onCreate() {Log.d(TAG, "替换之前系统的classLoader");showClassLoader();try {String cachePath = this.getCacheDir().getAbsolutePath();String apkPath = /*Environment.getExternalStorageState() + File.separator*/"/sdcard/"+ AppName;mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader()); loadApkClassLoader(mClassLoader);} catch (Exception e) {e.printStackTrace();}Log.d(TAG, "替换之后系统的classLoader");showClassLoader();}@SuppressLint("NewApi")public void loadApkClassLoader(DexClassLoader loader) {try {Object currentActivityThread  = ReflectHelper.invokeMethod("android.app.ActivityThread", "currentActivityThread", new Class[] {},new Object[] {});String packageName = this.getPackageName();ArrayMap mpackages = (ArrayMap) ReflectHelper.getField("android.app.ActivityThread", "mPackages", currentActivityThread);WeakReference wr= (WeakReference)mpackages.get(packageName);Log.e(TAG, "mClassLoader:" + wr.get());  ReflectHelper.setField("android.app.LoadedApk", "mClassLoader", wr.get(), loader);Log.e(TAG, "load:" + loader);  } catch (Exception e) {Log.e(TAG, "load apk classloader error:" + Log.getStackTraceString(e));  }}/*** 打印系统的classLoader*/public void showClassLoader() {ClassLoader classLoader = getClassLoader();if (classLoader != null){Log.i(TAG, "[onCreate] classLoader " + i + " : " + classLoader.toString());while (classLoader.getParent()!=null){classLoader = classLoader.getParent();Log.i(TAG,"[onCreate] classLoader " + i + " : " + classLoader.toString());i++;}}}
}

然后就是MainActivity.java文件,里面包含了下面另外一种方式,打开activity,所以我把函数  inject(DexClassLoader loader)先注释掉

 

 

package com.example.dexclassloaderactivity;import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;import android.content.Intent;
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.TextView;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;public class MainActivity extends ActionBarActivity{public static final String TAG = "MainActivity";public static final String AppName = "test.apk";public static DexClassLoader mDexClassLoader = null;public static final String APPName = "test.apk";public TextView mText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.text2).setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {try {
//					 inject(MyApplication.mClassLoader);String apkPath = Environment.getExternalStorageDirectory().toString() + File.separator + APPName;Class clazz = MyApplication.mClassLoader.loadClass("com.example.testapkdemo.MainActivity");Intent intent = new Intent(MainActivity.this, clazz);startActivity(intent);finish();} catch (Exception e) {Log.e(TAG, "name:" + Log.getStackTraceString(e));}}});}private void inject(DexClassLoader loader){  PathClassLoader pathLoader = (PathClassLoader) getClassLoader();  try {  Object dexElements = combineArray(  getDexElements(getPathList(pathLoader)),  getDexElements(getPathList(loader)));  Object pathList = getPathList(pathLoader);  setField(pathList, pathList.getClass(), "dexElements", dexElements);  } catch (IllegalArgumentException e) {  e.printStackTrace();  } catch (NoSuchFieldException e) {  e.printStackTrace();  } catch (IllegalAccessException e) {  e.printStackTrace();  } catch (ClassNotFoundException e) {  e.printStackTrace();  }  }  private static Object getPathList(Object baseDexClassLoader)  throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  ClassLoader bc = (ClassLoader)baseDexClassLoader;  return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");  }  private static Object getField(Object obj, Class<?> cl, String field)  throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {  Field localField = cl.getDeclaredField(field);  localField.setAccessible(true);  return localField.get(obj);  }  private static Object getDexElements(Object paramObject)  throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {  return getField(paramObject, paramObject.getClass(), "dexElements");  }  private static void setField(Object obj, Class<?> cl, String field,  Object value) throws NoSuchFieldException,  IllegalArgumentException, IllegalAccessException {  Field localField = cl.getDeclaredField(field);  localField.setAccessible(true);  localField.set(obj, value);  }  private static Object combineArray(Object arrayLhs, Object arrayRhs) {  Class<?> localClass = arrayLhs.getClass().getComponentType();  int i = Array.getLength(arrayLhs);  int j = i + Array.getLength(arrayRhs);  Object result = Array.newInstance(localClass, j);  for (int k = 0; k < j; ++k) {  if (k < i) {  Array.set(result, k, Array.get(arrayLhs, k));  } else {  Array.set(result, k, Array.get(arrayRhs, k - i));  }  }   return result;  }    
}

 

 

这里一定要注意,我们犯了3个错,

1、test.apk的路径写错了,下次写文件路径的时候,我们应该需要加上File file = new File(path); 用file.exist()函数来判断是否存在

2、从sdcard卡里面读取test.apk,没加上权限,   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

3、写了Application,忘了在AndroidManifest.xml文件里面声明。

我们还需要注意要加上开启activity 在AndroidManifest.xml里面注册,切记,希望下次不要再次犯错。

AndroidManifest.xml文件如下:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.dexclassloaderactivity"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="11"android:targetSdkVersion="21" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><applicationandroid:name="com.example.dexclassloaderactivity.MyApplication"android:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" ><activityandroid:name=".MainActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity   android:name="com.example.testapkdemo.MainActivity">  </activity> </application></manifest>


运行结果如下:

 

 

然后这里又出现了插件资源和宿主资源冲突问题,我们后面再来研究。

 

4、合并PathClassLoader和DexClassLoader中的dexElements数组 

我们首先来看一下PathClassLoader和DexClassLoader类加载器的父类BaseDexClassloader的源码:

(这里需要注意的是PathClassLoader和DexClassLoader类的父加载器是BaseClassLoader,他们的父类是BaseDexClassLoader)

这里有一个DexPathList对象,在来看一下DexPathList.java源码:

Elements数组,我们看到这个变量他是专门存放加载的dex文件的路径的,系统默认的类加载器是PathClassLoader,本身一个程序加载之后会释放一个dex出来,这时候会将dex路径放到里面,当然DexClassLoader也是一样的,那么我们会想到,我们是否可以将DexClassLoader中的dexElements和PathClassLoader中的dexElements进行合并,然后在设置给PathClassLoader中呢?这也是一个思路。我们来看代码:

 

/*** 以下是一种方式实现的* @param loader*/
private void inject(DexClassLoader loader){PathClassLoader pathLoader = (PathClassLoader) getClassLoader();try {Object dexElements = combineArray(getDexElements(getPathList(pathLoader)),getDexElements(getPathList(loader)));Object pathList = getPathList(pathLoader);setField(pathList, pathList.getClass(), "dexElements", dexElements);} catch (IllegalArgumentException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}
}private static Object getPathList(Object baseDexClassLoader)throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {ClassLoader bc = (ClassLoader)baseDexClassLoader;return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}private static Object getField(Object obj, Class<?> cl, String field)throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {Field localField = cl.getDeclaredField(field);localField.setAccessible(true);return localField.get(obj);
}private static Object getDexElements(Object paramObject)throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {return getField(paramObject, paramObject.getClass(), "dexElements");
}
private static void setField(Object obj, Class<?> cl, String field,Object value) throws NoSuchFieldException,IllegalArgumentException, IllegalAccessException {Field localField = cl.getDeclaredField(field);localField.setAccessible(true);localField.set(obj, value);
}private static Object combineArray(Object arrayLhs, Object arrayRhs) {Class<?> localClass = arrayLhs.getClass().getComponentType();int i = Array.getLength(arrayLhs);int j = i + Array.getLength(arrayRhs);Object result = Array.newInstance(localClass, j);for (int k = 0; k < j; ++k) {if (k < i) {Array.set(result, k, Array.get(arrayLhs, k));} else {Array.set(result, k, Array.get(arrayRhs, k - i));}}return result;
}

然后运行的时候把MyApplication.java文件里面的函数loadApkClassLoader(mClassLoader);注释掉,然后把MainActivity.java文件里面的inject(MyApplication.mClassLoader)不要注释,运行效果一样。

 

 

总结:

我们在使用反射机制来动态加载Activity的时候,有两个思路:

1>、替换LoadApk类中的mClassLoader变量的值,将我们动态加载类DexClassLoader设置为mClassLoader的值

2>、合并系统默认加载器PathClassLoader和动态加载器DexClassLoader中的dexElements数组

这两个的思路原理都是一样的:就是让我们动态加载进来的Activity能够具备正常的启动流程和生命周期。

我们还没解决资源冲突问题,后面再解决,有点复杂。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

相关文章

理解UI线程——SWT, Android, 和Swing的UI机理

2019独角兽企业重金招聘Python工程师标准>>> 在做GUI的时候, 无论是SWT, AWT, Swing 还是Android, 都需要面对UI线程的问题, UI线程往往会被单独的提出来单独对待, 试着问自己, 当GUI启动的时候, 后台会运行几个线程? 比如 1. SWT 从Main函数启动 2. Swing 从Ma…

C#多线程开发-并发集合中的ConcurrentQueue

前言大家好&#xff0c;我是阿辉。上一篇博文简单介绍了C#中支持并发的数据字典&#xff0c;简单举例说明比较了常规集合与ConcurrentDictionary的读写速度。下来简单介绍其中一个线程安全队列ConcurrentQueue;ConcurrentQueue队列我们不陌生&#xff0c;在数据结构这门课中就有…

一个人动情之后的表现......

1 卖家能有什么坏心思呢&#xff08;via.城与橙与澄&#xff0c;侵删&#xff09;▼2 严重怀疑传了答案▼3 别说我还真没留意到&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 领导说“辛苦了”&#xff0c;你要怎么回答▼5 哦吼&#xff08;素材来源网络&#…

线性代数第五版吉尔伯特课后答_线性代数同济第五版第六章课后习题答案!

搜集 | 整理 | 测试 | 小愉免责声明&#xff1a;以下资源或软件均来自互联网&#xff0c;仅供学习和交流使用&#xff0c;如有侵权请联系删除&#xff0c;请勿用于商业和非法途径等&#xff0c;如有法律纠纷与本人无关&#xff01;本文未经允许&#xff0c;不得转载&#xff0…

sql长整型_SQL 性能优化梳理

先简单梳理下Mysql的基本概念&#xff0c;然后分创建时和查询时这两个阶段的优化展开。1 基本概念简述1.1 逻辑架构第一层&#xff1a;客户端通过连接服务&#xff0c;将要执行的sql指令传输过来第二层&#xff1a;服务器解析并优化sql&#xff0c;生成最终的执行计划并执行第三…

网络的东西南北

前一陣子連續出差, 加上許多的內部會議, 搞的差點想去撞牆把自己搞昏之後就可以休息一下. 但是家中還有嗷嗷待哺的嬰兒需要爸爸幫他洗屁屁, 所以只有咬牙繼續撐下去. 不過這兩個月來, 不過在公司內部還是外部, 我都收到一樣類似的老問題那就是&#xff1a;&#xff08;認識我的…

K8s 中使用 cert-manager 申请免费 Https 证书

K8s 中使用 cert-manager 申请免费 Https 证书Intro最近在尝试将自己的应用从自己用 kind 部署的一个 k8s 集群迁移到 Azure 的 AKS 上&#xff0c;其中一个问题就是 https 证书&#xff0c;原来的 k8s 集群是放在 nginx 后端的并没有直接管理 https 证书&#xff0c;https 证书…

为什么要学数学?因为它真的没用啊!

全世界只有3.14 % 的人关注了爆炸吧知识数学之用无用之用有一天&#xff0c;表妹过来问了我两个问题&#xff1a;数学有什么用&#xff1f;那些深奥的公式对于普通人有什么意义&#xff1f;相信大多数人都有这个疑问&#xff0c;但总是找不到一个标准答案。问老师&#xff0c;他…

UI设计教程-界面设计构图

九宫格构图&#xff0c;圆心点放射形构图&#xff0c;三角形构图&#xff0c;SF字形构图。 1.九宫格网格构图 这种版式主要运用在分类为主的一级页面&#xff0c;起到功能分类的作用。 通常在界面设计中&#xff0c;我们会利用网格在界面进行布局&#xff0c;根据水平方向和垂直…

Android之最好理解的Binder机制

转载&#xff1a;http://weishu.me/2016/01/12/binder-index-for-newer/ Binder学习指南 发表于 2016-01-12 | 92条评论 | 34011次阅读毫不夸张地说&#xff0c;Binder是Android系统中最重要的特性之一&#xff1b;正如其名“粘合剂”所喻&#xff0c;它是系统间各个组件…

sql年月日24小时制_24小时制的「无码」真人秀,令人叫绝

几年前&#xff0c;我们总说互联网给世界带来了巨大的变化。到了现在&#xff0c;没想到自媒体的发展居然也能改变生活。但大家知道吗&#xff0c;其实早在20年前&#xff0c;美国那边就曾因“直播”掀起过不小的风浪——还是尺度挺大的那种。以至于毒师看过以后&#xff0c;整…

HangFire循环作业中作业因执行时间太长未完成新作业开启导致重复数据的问题...

背景HangFire有个很奇怪的现象&#xff0c;就是即使你设置的循环作业是一天一次&#xff0c;但是每次作业执行很长时间&#xff0c;我们假设是1小时&#xff0c;那么差不多在开始执行之后的30分钟之后&#xff0c;如果还在执行job,系统就会自动帮你重新开启了新的job执行。也就…

linux之查看文件大小、文件夹的大小和分区磁盘速度

1 查看文件大小 查看文件大小的命令 ls -l filename 比如: 注意这个 1243870 是 单位是B 2 查看文件夹的大小 查看文件夹的大小,也就是查看文件夹下所有文件的大小总和 先进入这个目录: cd FoldeName 命令如下: du -sh 3 查看分区磁盘速度 命令如下:

再谈“开源软件供应链安全”

| 作者&#xff1a;庄表伟| 编辑&#xff1a;刘雪洁| 设计&#xff1a;周颖| 责编&#xff1a;王玥敏缘起之前写过一篇文章《我所理解的开源软件供应链安全》&#xff0c;当时的情况&#xff0c;还没有出现一些值得探讨的&#xff0c;堪称紧迫的热点事件&#xff0c;所以我也仅…

python 测试框架

2019独角兽企业重金招聘Python工程师标准>>> # -*- encodeing:utf-8 -*- import unittest class WidgetTestCase(unittest.TestCase): def setUp(self): self.size 4 def tearDown(self): self.size 0 def testSize(self): self.assertEqual(self.size, 4) def su…

从抛硬币试验看随机游走定义的基本概念错误

全世界只有3.14 % 的人关注了爆炸吧知识随机游走&#xff08;Random Walk&#xff09;是《随机过程》教科书中用于描述动态随机现象的一种基本随机过程&#xff0c;许多重要的随机过程都可由它派生出来&#xff0c;其理论不仅在随机过程中占有相当重要的地位&#xff0c;而且也…

正则表达式学习手册

效果图<ignore_js_op style"word-wrap: break-word;">详细说明&#xff1a;http://java.662p.com/thread-3932-1-1.html转载于:https://www.cnblogs.com/huasili/p/4152584.html