Android 启动插件Activity的原理以及案例演示

当应用的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的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值