当应用的apk文件安装到手机系统时,这个文件会被解压后存储到data/dalvik-cache 这个目录下,当app应用程序启动时,如果app所在的进程未创建,则会通过Zygote进程去fork一个子进程作为要启动的app的进程,并创建PathClassLoader。app进程启动后,会通过通过ClassLoader来完成dex文件的加载。PathClassLoader是继承BaseDexClassLoader的,PathClassLaoder加载class的过程是,通过loadClass方法来完成的。PathClassLoader的loadClass方法是继承自父类BaseDexClassLoader的方法。
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//关键代码
c = findClass(name);
}
}
return c;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//关键代码
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
这个方法内部调用了findClass方法,findClass方法又调用DexPathList类型的pathList这个变量的fincClass方法来完成class文件的查找的。下面看看DexPathList类的findClass方法
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
这个方法内部又调用了DexFile类的loadClassBinaryName来完成class文件的加载。
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
loadClassBinaryName方法内部调用了defineClass,而defineClass内部调用了defineClassNative这个本地方法来完成class文件的查找。从DexPathList类的findClass方法可以发现,所有要加载的类都是从dexElements这个数组中进行查找的。所以,想要实现插件化,就需要将插件的dex文件插入到DexPathList类的成员变量dexElements这个数组中,这样类加载器就能加载插件的dex文件了。目前市面上的各种插件化的框架就是基于这个原理。
下面以启动一个插件Activity为例来讲解如何实现插件化。
1.准备一个插件dex文件,我们可以通过在项目中,自己编写一个PlugActivity,然后,编译项目后,会在app\build\intermediates\classes…
下面的某个子目录下面去找到PlugActivity.class文件。然后sdk\build-tools\28.0.3这个目录下,根据包名新建目录,然后将这个PlugActivity.class文件复制到包名目录下。接着打开命令行窗口,进入到C:\tool\Android\sdk\build-tools\28.0.3 这个目录下,
然后执行如下命令:
C:\tool\Android\sdk\build-tools\28.0.3>dx --dex --no-strict --output=C:\tool\And
roid\sdk\build-tools\28.0.3\classes3.dex C:\tool\Android\sdk\build-tools\28.0.3\test\
cn\example\com\androidskill
这个命令行中,output=C:\tool\Android\sdk\build-tools\28.0.3\classes3.dex,这个路径就是生产的dex文件的路径
后面的C:\tool\Android\sdk\build-tools\28.0.3\test\cn\example\com\androidskill这个路径就是PlugActivity.class文件所在的路径。、
执行完这命令后,如果未报错,则会在C:\tool\Android\sdk\build-tools\28.0.3目录下,生产一个classes3.dex文件。
至此,生产了插件dex文件
//PlugActivity代码如下
import test.cn.example.com.util.LogUtil;
public class PlugActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtil.i("插件onCreate");
}
}
2.一般插件都是通过网络下载到手机上,我们这里简化这个下载过程,直接将这个插件classes3.dex文件复制到手机的sd卡中。
3.由于PathClassLoader类加载器默认是加载存储在data/dalvik-cache这个目录下的dex文件的,而DecClassLoader是能够加载app内部路径下的dex文件的,所以,要将sd卡中的插件classes3.dex文件复制到app内部路径中。下面就是将插件classes3.dex文件复制到app内部路径的实现:
private void copyDexFileToInnerPath() throws FileNotFoundException, ClassNotFoundException {
//模拟下载插件dex文件
File dir = getDir(MyConstant.DEX_DIR, Context.MODE_PRIVATE);
LogUtil.i(""+dir);
String name = "classes3.dex";
String filePath = dir.getAbsolutePath()+File.separator+name;
File file = new File(filePath);
if(file.exists()){
file.delete();
}
LogUtil.i(Environment.getExternalStorageDirectory().getAbsolutePath());
File fixFile = new File(Environment.getExternalStorageDirectory(),name);
if(null != fixFile){
LogUtil.i(""+fixFile.getAbsolutePath());
if(!fixFile.exists()){
throw new FileNotFoundException(name+"is not exists");
}
}
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fixFile));
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(file));
int len = 0;
byte[] buffer = new byte[1024];
while (-1!=(len = bis.read(buffer))){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != bos){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != bis){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if(file.exists()){
LogUtil.i("文件复制成功 "+file.getAbsolutePath());
}
//将插件的dex合并到宿主的dexElements数组中
mergeFixDexFile(dir);
}
4.将插件classes3.dex文件复制到app内部路径后,当app启动后,DexClassLoader回去加载插件dex,这时,就会将插件dex中的dex存储到DexClassLoader所属的DexPathList的dexElements中,这样插件的dex还是未插入到PathClassLoader所属的DexPathList的dexElements这个数组中,由于宿主dex文件的加载都是加载的PathClassLoader所属的DexPathList的dexElements中的dex,所以,需要将DexClassLoader所属的DexPathList的dexElements中的dex合并到PathClassLoader所属的DexPathList的dexElements数组中,并将这新的dexElements赋值给PathClassLoader所属的DexPathList的dexElements,这样宿主在加载dex文件时,就能够从PathClassLoader所属的DexPathList的dexElements这个数组中加载插件dex文件。下面就是这个过程的具体实现
//这个方法的传入的目录参数就是插件dex文件在app内部的存储路径
private void mergeFixDexFile(File dir) throws ClassNotFoundException {
//遍历app内部的存储了修复了bug的dex文件
File[] files = dir.listFiles();
File optDirFile = getDir("opt_dex", Context.MODE_PRIVATE);
if(optDirFile.exists()){
optDirFile.delete();
}
optDirFile.mkdirs();
LogUtil.i("修复的dex文件的解压路径 "+optDirFile.getAbsolutePath());
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
for(File f:files){
if(f.getName().startsWith("classes") && f.getName().endsWith(".dex")){
//2.获取DexClassLoader的实例
try {
DexClassLoader dexClassLoader = new DexClassLoader(f.getAbsolutePath(),optDirFile.getAbsolutePath(),"",getClassLoader());
Object dexPathList = FixDexUtils2.getObject(baseDexClassLoaderClazz, "pathList",dexClassLoader);
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
Object pathList = FixDexUtils2.getObject(baseDexClassLoaderClazz, "pathList", pathClassLoader);
Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
Object dexElements = FixDexUtils2.getObject(dexPathListClazz, "dexElements", dexPathList);
Object pathDexElements = FixDexUtils2.getObject(dexPathListClazz, "dexElements", pathList);
int i = Array.getLength(dexElements);
int j = Array.getLength(pathDexElements);
Object element = Array.get(pathDexElements, 0);
//创建新的Elements
Object newElements = Array.newInstance(element.getClass(), i + j);
for(int k=0;k<(i+j);k++){
if(k<i){
Array.set(newElements,k,Array.get(dexElements,k));
}else {
Array.set(newElements,k,Array.get(pathDexElements,k-i));
}
}
//合并完成后,将新的elements赋值给DexPathList的成员变量dexElements
Field dexElementsField = FixDexUtils2.getField(dexPathListClazz, "dexElements");
dexElementsField.setAccessible(true);
dexElementsField.set(pathList,newElements);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
5.完成上面4个步骤,也只能完成了插件dex文件能够被宿主的类加载器加载到,但是执行时,还要对插件中的具体类型的组件进行处理。本示例是启动插件Activity。由于插件Activity未在宿主Manifest文件中声明过,所以,如果直接启动插件Activity则会报
"Unable to find explicit activity class XXX have you declared this activity in your AndroidManifest.xml?"
这个异常是在Instrumentation类的checkStartActivityResult方法中抛出的。所以,要绕过检测,就必须在这个方法之前,用一个已经在Manifest文件中声明的Activity来替换插件Activity,这样就可以绕过Instruement类中checkStartActivityResult方法的检查。至于如何绕过这个检测呢?这就需要从Activity的启动过程找突破点,关于Activity的启动过程可以看这篇文章。
在Activity中启动一个Activity时,会调用到Instrumentation类的execStartActivity方法,下面来看这个方法的具体实现:
http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
// ...
try {
// ...
//关键代码
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//关键代码
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
这个方法内部,提供过AMS来启动一个Activity,并返回一个启动结果result,checkStartActivityResult方法就是根据这个result来抛出相应的异常,从这方法的逻辑中,可以推测真正的检查Activity的合法性,以及其它的检测都是在AMS中完成的,所以,想要绕过AMS对要启动的Activity的检查,就需要在AMS的startActivity方法上做"手脚",或者在Instrumentation类的execStartActivity方法上做"手脚"。AMS对Activity的检查其实是使用了其startActivity方法中传入的intent来做的检查,所以,想要绕过AMS对Activity的检查,就要替换startActivity方法中传入的intent,由于这个intent是从Instrumentation的execStartActivity方法中参数intent传入的,所以,可以有两种方式来改变这个intent。
方式一:就是给Instrumentation设置一个代理,InstrumentationProxy,在这代理类中的execStartActivity方法中,将插件Intent中封装的插件Activity的信息改变为已经在Manifest文件中声明的Activity的信息。
方式二:在AMS的startActivity方法中,也做同样的处理,将包含插件Activity的intent替换成在Manifest文件中声明的Activity的信息。
下面就先按照方式一,Instrumentation类设置代理的方式来实现"偷梁换柱",具体代码如下:
import java.lang.reflect.Field;
//FixDexUtils2 反射工具类
public class FixDexUtils2 {
private FixDexUtils2(){}
public static Field getField(Class clazz,String fieldName) throws NoSuchFieldException {
return clazz.getDeclaredField(fieldName);
}
public static Object getObject(Class clazz,String fieldName,Object obj) throws NoSuchFieldException, IllegalAccessException {
Field field = getField(clazz, fieldName);
field.setAccessible(true);
return field.get(obj);
}
public static void setObject(Class clazz,String fieldName,Object obj,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = getField(clazz, fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
//HookHelper类
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import test.cn.example.com.androidskill.optimize.hotfix.FixDexUtils2;
public class HookHelper {
public static final String PLUG_INTENT = "plug_intent";
public static final String packageName = "test.cn.example.com.androidskill";
public static final String PLUGCLASSNAME = packageName+".hook.PlugActivity";
public static final String BACKUPCLASSNAME = packageName+".hook.BackUpActivity";
public static void hookAMS() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Object singleton = null;
if(Build.VERSION.SDK_INT>=26){
Class<?> activityManagerClazz = Class.forName("android.app.ActivityManager");
singleton = FixDexUtils2.getObject(activityManagerClazz, "IActivityManagerSingleton", null);
}else {
Class<?> actitivytManagerNatvieClazz = Class.forName("android.app.ActivityManagerNative");
singleton = FixDexUtils2.getObject(actitivytManagerNatvieClazz,"gDefault",null);
}
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field mInstanceField = FixDexUtils2.getField(singletonClazz, "mInstance");
mInstanceField.setAccessible(true);
Object iActivityManager = mInstanceField.get(singleton);
Object proxyInstance = Proxy.newProxyInstance(iActivityManager.getClass().getClassLoader(), iActivityManager.getClass().getInterfaces(), new IActivityManagerInvocationHandler(iActivityManager));
mInstanceField.set(singleton,proxyInstance);
}
public static void hookHandler() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Object sCurrentActivityThread = FixDexUtils2.getObject(activityThreadClazz, "sCurrentActivityThread", null);
Handler mH = (Handler) FixDexUtils2.getObject(activityThreadClazz, "mH", sCurrentActivityThread);
//这里要传入Handler.class,如果传入mH.getClass(),则会报NoSuchFieldException异常,因为ActivityThread的mH这个变量是继承自Handler的,
//通过getDeclaredField是无法找到mCallback这个Field的,所以要传Handler.class
// FixDexUtils2.setObject(mH.getClass(),"mCallback",mH,new HCallBack(mH));
FixDexUtils2.setObject(Handler.class,"mCallback",mH,new HCallBack(mH));
}
public static void hookActivityThreadInstrumentation() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Object sCurrentActivityThread = FixDexUtils2.getObject(activityThreadClazz, "sCurrentActivityThread", null);
Instrumentation mInstrumentation = (Instrumentation) FixDexUtils2.getObject(activityThreadClazz, "mInstrumentation", sCurrentActivityThread);
FixDexUtils2.setObject(activityThreadClazz,"mInstrumentation",sCurrentActivityThread,new InstrumentationProxy(mInstrumentation));
}
//代理CallBack
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import test.cn.example.com.androidskill.optimize.hotfix.FixDexUtils2;
import test.cn.example.com.util.LogUtil;
public class HCallBack implements Handler.Callback {
public static final int LAUNCH_ACTIVITY = 100;
public static final String PACKAGENAME = "test.cn.example.com.androidskill";
public static final String BACKUP_CLASSNAME = PACKAGENAME+".hook.BackUpActivity";
private final Handler mHandler;
public HCallBack(Handler handler){
this.mHandler = handler;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY:
Object obj = msg.obj;
LogUtil.e(obj+"");
try {
Intent intent = (Intent) FixDexUtils2.getObject(obj.getClass(), "intent", obj);
if(BACKUP_CLASSNAME.equals(intent.getComponent().getClassName())){
Intent realIntent = intent.getParcelableExtra(HookHelper.PLUG_INTENT);
//还原,将占坑activity还原成插件activity,这种方式还原是配合hook AMS方案
intent.setComponent(realIntent.getComponent());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
mHandler.handleMessage(msg);
return true;
}
}
//InstrumentationProxy类
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import test.cn.example.com.util.LogUtil;
public class InstrumentationProxy extends Instrumentation {
private final Instrumentation mInstrumentation;
public InstrumentationProxy(Instrumentation instrumentation){
this.mInstrumentation = instrumentation;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
LogUtil.i("调用了代理类的execStartActivity");
String className = intent.getComponent().getClassName();
if(HookHelper.PLUGCLASSNAME.equals(className)){
Intent newIntent = new Intent();
newIntent.setClassName(HookHelper.packageName,className);
intent.putExtra(HookHelper.PLUG_INTENT,newIntent);
intent.setClassName(HookHelper.packageName,HookHelper.BACKUPCLASSNAME);
}
try {
Method execStartActivityMethod = mInstrumentation.getClass().getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execStartActivityMethod.setAccessible(true);
return (ActivityResult) execStartActivityMethod.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Activity newActivity(ClassLoader cl, String className,
Intent intent) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Intent parcelableExtra = intent.getParcelableExtra(HookHelper.PLUG_INTENT);
if(null != parcelableExtra && null != parcelableExtra.getComponent()){
String intentName = parcelableExtra.getComponent().getClassName();
if(!TextUtils.isEmpty(intentName)){
//还原,将占坑activity还原成插件activity,这种方式还原是配合hook instrumentation方案
return super.newActivity(cl,intentName,intent);
}
LogUtil.i(parcelableExtra.getComponent()+"");
}
return super.newActivity(cl,className,intent);
}
}
完成了上面的实现步骤后,虽然可以绕过AMS的检查,但是绕过了AMS检查后,如果不对占坑的Activity进行还原,则启动的就是占坑Activity,由于Activity的实例化都是在ActivityThread类的mH这个handler中hadleMessage方法中的
新建一个HookActivity,并给HookActivity的布局添加两个按钮,一个按钮使用hook Instrumentation的方式启动插件Activity,另外一个按钮采用hook AMS的方式启动插件Activity,另外还要新建一个占坑的Activity
下面是占坑Activity的代码
public class BackUpActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtil.i("占坑onCreate");
}
}
记得在Manifest文件中声明:
<activity android:name=".hook.BackUpActivity" android:screenOrientation="portrait"/>
下面是HookActivity的代码和布局
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import test.cn.example.com.androidskill.R;
import test.cn.example.com.androidskill.designpattern.ProxyPatternActivity;
import test.cn.example.com.androidskill.optimize.hotfix.FixDexUtils2;
import test.cn.example.com.androidskill.optimize.hotfix.MyConstant;
import test.cn.example.com.util.LogUtil;
public class HookActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hook);
findViewById(R.id.tv_3).setOnClickListener(this);
findViewById(R.id.tv_4).setOnClickListener(this);
try {
//将插件dex合并到DexPathList类的dexElements这个数组中
copyDexFileToInnerPath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//这里还需要hook handler,因为在开始使用占坑的BackUpActivity来绕过了AMS的检查,但是在实化
//插件activity时,还要将占坑BackUpActivity还原成插件activity
HookHelper.hookHandler();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void copyDexFileToInnerPath() throws FileNotFoundException, ClassNotFoundException {
//模拟下载插件dex文件
File dir = getDir(MyConstant.DEX_DIR, Context.MODE_PRIVATE);
LogUtil.i(""+dir);
String name = "classes3.dex";
String filePath = dir.getAbsolutePath()+File.separator+name;
File file = new File(filePath);
if(file.exists()){
file.delete();
}
LogUtil.i(Environment.getExternalStorageDirectory().getAbsolutePath());
File fixFile = new File(Environment.getExternalStorageDirectory(),name);
if(null != fixFile){
LogUtil.i(""+fixFile.getAbsolutePath());
if(!fixFile.exists()){
throw new FileNotFoundException(name+"is not exists");
}
}
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fixFile));
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(file));
int len = 0;
byte[] buffer = new byte[1024];
while (-1!=(len = bis.read(buffer))){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null != bos){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != bis){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if(file.exists()){
LogUtil.i("文件复制成功 "+file.getAbsolutePath());
}
mergeFixDexFile(dir);
}
private void mergeFixDexFile(File dir) throws ClassNotFoundException {
//遍历app内部的存储了修复了bug的dex文件
File[] files = dir.listFiles();
File optDirFile = getDir("opt_dex", Context.MODE_PRIVATE);
if(optDirFile.exists()){
optDirFile.delete();
}
optDirFile.mkdirs();
LogUtil.i("修复的dex文件的解压路径 "+optDirFile.getAbsolutePath());
Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
for(File f:files){
if(f.getName().startsWith("classes") && f.getName().endsWith(".dex")){
//2.获取DexClassLoader的实例,获取
try {
DexClassLoader dexClassLoader = new DexClassLoader(f.getAbsolutePath(),optDirFile.getAbsolutePath(),"",getClassLoader());
Object dexPathList = FixDexUtils2.getObject(baseDexClassLoaderClazz, "pathList",dexClassLoader);
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
Object pathList = FixDexUtils2.getObject(baseDexClassLoaderClazz, "pathList", pathClassLoader);
Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
Object dexElements = FixDexUtils2.getObject(dexPathListClazz, "dexElements", dexPathList);
Object pathDexElements = FixDexUtils2.getObject(dexPathListClazz, "dexElements", pathList);
int i = Array.getLength(dexElements);
int j = Array.getLength(pathDexElements);
Object element = Array.get(pathDexElements, 0);
Object newElements = Array.newInstance(element.getClass(), i + j);
for(int k=0;k<(i+j);k++){
if(k<i){
Array.set(newElements,k,Array.get(dexElements,k));
}else {
Array.set(newElements,k,Array.get(pathDexElements,k-i));
}
}
//合并完成后,将新的elements赋值给DexPathList的成员变量dexElements
Field dexElementsField = FixDexUtils2.getField(dexPathListClazz, "dexElements");
dexElementsField.setAccessible(true);
dexElementsField.set(pathList,newElements);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_3:
//演示hook AMS 时,记得将MyApplication的attachBaseContext方法中的hookActivityThreadInstrumentation();
//方法注销
try {
//建议放到Application类的attachBaseContext方法中,更好
HookHelper.hookAMS();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Intent intent_plugin = new Intent(this, PlugActivity.class);
intent_plugin.putExtra("data","first plugin test");
startActivity(intent_plugin);
break;
case R.id.tv_4:
//演示hook Instrumentation 时,如果MyApplication的attachBaseContext方法中的
// hookActivityThreadInstrumentation();被注销,记得打开
Intent intent_plugin_2 = new Intent(this, PlugActivity.class);
intent_plugin_2.putExtra("data","first plugin test");
startActivity(intent_plugin_2);
break;
}
}
}
// activity_hook.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/tv_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动一个插件cActivity方式一"/>
<Button
android:id="@+id/tv_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动一个插件cActivity方式二"/>
</LinearLayout>
MyApplication类中的代码:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
hookActivityThreadInstrumentation();
}
private void hookActivityThreadInstrumentation() {
try {
//必须在Activity初始化之前用InstrumentationProxy替换Instrumentation,这样
//所有的Activity和ActivityThread中的mInstrumentation这个成员变量才会都是
//InstrumentationProxy这个对象
HookHelper.hookActivityThreadInstrumentation();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
点击第一个按钮打印日志如下:
I/MY_LOG: IActivityManagerInvocationHandler.java::48::invoke-->>成功骗过了AMS
E/MY_LOG: HCallBack.java::24::handleMessage-->>ActivityRecord{a523b52 token=android.os.BinderProxy@6826e23 {test.cn.example.com.androidskill/test.cn.example.com.androidskill.hook.BackUpActivity}}
I/MY_LOG: PlugActivity.java::13::onCreate-->>插件onCreate
点击第二个按钮打印日志如下:
I/MY_LOG: InstrumentationProxy.java::26::execStartActivity-->>调用了代理类的execStartActivity
E/MY_LOG: HCallBack.java::24::handleMessage-->>ActivityRecord{7049bb4 token=android.os.BinderProxy@59f53dd {test.cn.example.com.androidskill/test.cn.example.com.androidskill.hook.BackUpActivity}}
I/MY_LOG: PlugActivity.java::13::onCreate-->>插件onCreate
本案例,采用了两种方式来启动插件activity,方式一是hook Instrumentation,这种方式,在9.0版本的系统上是可以的,方式二,hook AMS,在9.0系统上是不可行的,因为9.0系统的ActivityTread类的内部类H,中,已经没有LAUNCH_ACTIVITY这个常量了,所以,在还原步骤中,是无法将占坑的activity还原成插件activity的。