天天看点

android插件开发——加载插件

在阅读本博文的时候,我假设你已经阅读了我之前写的几篇。猛击此处

通过前面的几篇博客,我们解决了如何启动一个并没有在ActivityManifest.xml中声明的activity。但是有很多细心的读者私信我说,我们所有的例子里,插件都是和主工程在一起的呀,我们如何从外部加载一个apk或者dex呢?

本节就是解决这个问题。

在学习本节之前,有一些非常重要的概念需要提一下。比如类加载器的概念。

我们知道在java里面,有很多种加载器,如果按层次划分的话,可以分为

android插件开发——加载插件

在加载类的时候,他们采用委托机制,比如,我们自定义的ClassLoader要加载一个类,它首先会委托AppClassLoader去加载,AppClassLoader又会委托ExtClassLoader去加载,而ExtClassLoader呢又去委托BootStrap加载,如果BootStrap加载成功了,那就返回,否则会让ExtClassLoader加载,如果ExtClassLoader也没加载成功,那就让AppClassLoader加载,以此类推,如果到自定义ClassLoader都还没成功加载类,那么就会抛出ClassNotFound异常。这种机制可以很大程度的避免重复加载一个类——子加载器首先尝试让父加载器加载。因而我们不难得出,在自定义一个类加载器的时候,我们还要为其指定一个父类加载器。当然本文并不是讨论这个的。具体的读者可以参阅姜维前辈的博文:姜维

在android中,系统也提供了两个类加载器:DexClassLoader和PathClassLoader

PathClassLoader用于加载/data/app中的apk,也就是已经安装了的apk,所以它就成了系统的默认类加载器。

而对于DexClassLoader呢,他可以用来任意位置的apk/dex/jar文件。

我们看下源码:

/*
 * Copyright (C)  The Android Open Source Project
 *
 * Licensed under the Apache License, Version  (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dalvik.system;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.ZipFile;

/**
 * Provides a simple {@link ClassLoader} implementation that operates on a
 * list of jar/apk files with classes.dex entries.  The directory that
 * holds the optimized form of the files is specified explicitly.  This
 * can be used to execute code not installed as part of an application.
 *
 * The best place to put the optimized DEX files is in app-specific
 * storage, so that removal of the app will automatically remove the
 * optimized DEX files.  If other storage is used (e.g. /sdcard), the
 * app may not have an opportunity to remove them.
 */
public class DexClassLoader extends ClassLoader {

    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * The path lists are separated using the character specified by
     * the "path.separator" system property, which defaults to ":".
     *
     * @param dexPath
     *  the list of jar/apk files containing classes and resources
     * @param dexOutputDir
     *  directory where optimized DEX files should be written
     * @param libPath
     *  the list of directories containing native libraries; may be null
     * @param parent
     *  the parent class loader
     */
    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
        ClassLoader parent) {
        ...
    }
    ...
           

由注释我们看出,第一个参数是,jar/file文件的位置

第二个参数指定存放dex文件的位置

第三个参数用于指定存放原生库的位置(so文件)

第四个参数就是制定一个父类加载器

很简单,但是由于篇幅限制,我们不打算做个demo,我们会把DexClassLoader的使用放到下面我们的例子里。由于不是很复杂,所以这么做也是合情合理

还记得之前的源码分析吗,当AMS做完一切准备工作,让UI线程开始启动一个新的activity之后,ActivityThread便开始加载一个新的activity

android插件开发——加载插件

之后再handleLaunchActivity函数中:

android插件开发——加载插件

调用mInstrumentation.newActivity方法,我们看下函数签名:

android插件开发——加载插件

通过class加载一个类,并且实例化。而这个cl是什么呢,根据上面的代码我们可以知道是r.packageInfo.getClassLoader的返回值,而这个r.packageInfo是在H的handleMessage中被赋值的:

android插件开发——加载插件

我们看下这个field

static final class ActivityClientRecord {
        ...
        LoadedApk packageInfo;
        ...
}
           

而LoadedApk又是什么呢:

/**
 * Local state maintained about a currently loaded .apk.
 * @hide
 */
public final class LoadedApk {
    ...
}
           

它代表了一个apk所对应的内存表示,也就是apk被加载到内存后的信息,比如代码,资源等等。

那么到这里,我们要知道,如果我们要从外部加载一个apk,首先就要获得这个LoadApk对象,因为之后activity的实例化,都会用到LoadApk中的类加载器。因而我们首先要解决的事情就是如何产生一个LoadApk

我们要保证一切万无一失,最好就是模仿android系统的行为,如果我们能和android系统产生一个LoadApk的方式一样,那就做到了万无一失。

回溯上文,一个LoadApk的产生是通过:

r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
           

我们看下函数签名:

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }
           

函数调用了getPackageInfo:

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if (includeCode) {
                //includeCode的值为true 所以必定会调用这个函数
                //它的作用是,先从缓存中获取LoadApk
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;
            //如果并没有缓存 那就产生一个新的实例
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                        : "Loading resource-only package ") + aInfo.packageName
                        + " (in " + (mBoundApplication != null
                                ? mBoundApplication.processName : null)
                        + ")");
                //产生一个新的实例
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != , registerPackage);

                if (mSystemThread && "android".equals(aInfo.packageName)) {
                    packageInfo.installSystemApplicationInfo(aInfo,
                            getSystemContext().mPackageInfo.getClassLoader());
                }

                if (differentUser) {
                    // Caching not supported across users
                } else if (includeCode) {
                    //做下缓存
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }
           

这里呢mPackages的类型为:

final ArrayMap<String, WeakReference<LoadedApk>> mPackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
           

我们不难得出,如果要获得一个LoadApk对象,要解决两个问题:1:获得ApplicationInfo对象。 2:获得CompatibilityInfo对象

对于ApplicationInfo 官方是这么解释的:

/**
 * Information you can retrieve about a particular application.  This
 * corresponds to information collected from the AndroidManifest.xml's
 * &lt;application&gt; tag.
 */
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
    ...
}
           

它是AndroidManifest.xml 标签下的信息集合。

而CompatibilityInfo呢,由名字就可以得出,是一些兼容性信息:

/**
 * CompatibilityInfo class keeps the information about compatibility mode that the application is
 * running under.
 * 
 *  {@hide} 
 */
public class CompatibilityInfo implements Parcelable {
    /** default compatibility info object for compatible applications */
    public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
    };

    ...
}
           

呦,这里有个静态域,用于作为默认兼容信息。我们完全可以获得这个对象啊,那么问题是不是就只剩下获得ApplicationInfo对象了

而这个ApplicationInfo对象是通过调用r.activityInfo.applicationInfo获得的。

r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
           

这个r对象呢,通过我们之前的学习就知道了,是在ActivityStackSupervisor中被创建的

final int startActivityLocked(IApplicationThread caller,
            Intent intent, String resolvedType, ActivityInfo aInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode,
            int callingPid, int callingUid, String callingPackage,
            int realCallingPid, int realCallingUid, int startFlags, Bundle options,
            boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container,
            TaskRecord inTask) {

        ...
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
                requestCode, componentSpecified, this, container, options);
        ...
        return err;
    }
           

这里aInfo是一个很重要的参数。r中很多参数的引用都是来自aInfo中

android插件开发——加载插件

而这个aInfo呢,很简单是在AMS调用ActivityStackSupervisor第一个函数的时候就产生了:

android插件开发——加载插件

回顾之前的学习,这个函数最终是调用PackageManager的getActivityInfo函数来获得一个activity的信息:

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        ...
        synchronized (mPackages) {
            ...
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        new PackageUserState(), userId);
            }
        }
        return null;
    }
           

这里最终调用了PackageParser的generateActivityInfo函数:

public static final ActivityInfo generateActivityInfo(Activity a, int flags,
            PackageUserState state, int userId) {
        ...
        ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
        return ai;
    }
           

这里产生了applicationInfo对象

那么我们只要能修改generateApplication返回值是不是就可以了?很不幸,PackageParser是个善变的类,几乎每次在发布新版本后这个类都会被修改。我们看下DroidPlugin的源码就知道,关于这个类,我们要做非常之多的兼容。。。

android插件开发——加载插件

所以本文都假设你的api是23!

不过还好,generateApplication被重载了很多次,我们可以调用它参数最少的一个:

public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state) {
        return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
    }
           

从函数签名中我们可以看到,我们如果要获得ApplicationInfo,首先就要获得Package对象,还有就是PackageUserState对象

对于Package对象,我们可以通过PackageParse的这个函数获得:

android插件开发——加载插件

而PackageUserState直接使用默认的就行了,比如我们之前的例子就是(见上文):

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        ...
        synchronized (mPackages) {
            ...
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        //这里就是用的默认的
                        new PackageUserState(), userId);
            }
        }
        return null;
    }
           

我们看下源码:

package com.chan.hook.app;

import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Handler;

import com.chan.hook.R;
import com.chan.hook.am.AMSHook;
import com.chan.hook.handle.MessageHook;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

import dalvik.system.DexClassLoader;

/**
 * Created by chan on 16/4/8.
 */
public class HookApplication extends Application {

    private ClassLoader m_classLoader;
    private File m_file;
    private Object m_pluginLoadApk;


    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        init();
    }

    private void init() {

        try {
            setupClassLoader();

            //获得activity thread实例
            Class<?> activityThreadClz = Class.forName("android.app.ActivityThread", false, getClassLoader());
            Method currentActivityThreadMethod = activityThreadClz.getDeclaredMethod("currentActivityThread");
            Object activityThreadObject = currentActivityThreadMethod.invoke(null);

            //获得load apk的缓存信息
            Field packagesField = activityThreadClz.getDeclaredField("mPackages");
            packagesField.setAccessible(true);
            //map 第二个参数是LoadApk的弱引用
            Map<String, WeakReference<?>> packages = (Map<String, WeakReference<?>>)
                    packagesField.get(activityThreadObject);

            //下面就只剩下获得LoadApk了
            //根据 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo)
            //我们要获得ApplicationInfo 和 CompatibilityInfo


            //获得ApplicationInfo
            //通过调用PackageParser generateApplicationInfo方法

            //public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state)
            //分别获取参数的class
            Class<?> packageParserClz = Class.forName("android.content.pm.PackageParser", false, getClassLoader());
            Class<?> packageParserPackageClz = Class.forName("android.content.pm.PackageParser$Package", false, getClassLoader());
            Class<?> packageUserStateClz = Class.forName("android.content.pm.PackageUserState", false, getClassLoader());

            //获取method
            Method generateApplicationInfoMethod = packageParserClz.getDeclaredMethod("generateApplicationInfo",
                    packageParserPackageClz, int.class, packageUserStateClz);

            //获取Package
            Method parsePackageMethod = packageParserClz.getDeclaredMethod("parsePackage", File.class, int.class);
            //第一个参数是插件apk位置  第二个参数0代表解析所有内容
            Object packageObject = parsePackageMethod.invoke(packageParserClz.newInstance(), m_file, );
            //已经获得了application info
            ApplicationInfo applicationInfoObject = (ApplicationInfo) generateApplicationInfoMethod.invoke(null, packageObject, ,
                    packageUserStateClz.newInstance());
            applicationInfoObject.sourceDir = m_file.getAbsolutePath();
            applicationInfoObject.publicSourceDir = m_file.getAbsolutePath();


            //获得CompatibilityInfo
            Class<?> compatibilityInfoClz = Class.forName("android.content.res.CompatibilityInfo", false, getClassLoader());
            Field DEFAULT_COMPATIBILITY_INFO = compatibilityInfoClz.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
            Object compatibilityInfoObject = DEFAULT_COMPATIBILITY_INFO.get(null);

            //获得LoadApk对象
            Method getPackageInfoNoCheckMethod = activityThreadClz.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClz);
            Object pluginLoadApkObject = getPackageInfoNoCheckMethod.invoke(activityThreadObject, applicationInfoObject, compatibilityInfoObject);

            //因为load apk放在一个弱引用中 所以 如果被回收了的话 就前功尽弃了 用一个强引用 防止它被回收
            m_pluginLoadApk = pluginLoadApkObject;
            //替换load apk中的class loader
            Field classLoaderField = pluginLoadApkObject.getClass().getDeclaredField("mClassLoader");
            classLoaderField.setAccessible(true);
            classLoaderField.set(pluginLoadApkObject, m_classLoader);

            //添加一个缓存 第一个参数是插件apk的包名
            packages.put("com.chan.plugin", new WeakReference<>(pluginLoadApkObject));

            //获得ActivityManagerNative
            Class<?> serviceManagerClz = Class.forName("android.app.ActivityManagerNative", false, getClassLoader());
            //获得ActivityManagerNative.getDefault静态方法
            Method getDefaultMethod = serviceManagerClz.getDeclaredMethod("getDefault");

            //获得原始的IActivityManager对象
            Object rawIActivityManagerInterface = getDefaultMethod.invoke(null);
            //我们自己的Hook的对象
            Object hookIActivityManagerInterface = Proxy.newProxyInstance(
                    getClassLoader(),
                    new Class[]{Class.forName("android.app.IActivityManager", false, getClassLoader())},
                    new AMSHook(this, rawIActivityManagerInterface)
            );

            //反射ActivityManagerNative的gDefault域
            Field gDefaultField = serviceManagerClz.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);
            Object gDefaultObject = gDefaultField.get(null);

            //他的类型是Singleton
            Class<?> singletonClz = Class.forName("android.util.Singleton", false, getClassLoader());

            //把他的mInstance域替换掉 成为我们自己的Hook对象
            Field mInstanceField = singletonClz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            mInstanceField.set(gDefaultObject, hookIActivityManagerInterface);

            //获取activity thread的class

            //获得activity thread中的mH域
            Field mHField = activityThreadClz.getDeclaredField("mH");
            mHField.setAccessible(true);
            Object mHObject = mHField.get(activityThreadObject);

            //获得Handler中的mCallback域
            Field handlerCallbackField = Handler.class.getDeclaredField("mCallback");
            handlerCallbackField.setAccessible(true);
            //获得原来的mCallback对象
            Object callbackObject = handlerCallbackField.get(mHObject);

            //设置成我们自己的Callback对象
            Object hookHObject = new MessageHook(callbackObject, getClassLoader());
            handlerCallbackField.set(mHObject, hookHObject);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setupClassLoader() throws IOException {

        //我这里就是简单的吧raw下的apk读取出来 然后存放到文件夹下 这已经和真实的业务场景很像了
        File dir = getDir("plugin", MODE_PRIVATE);
        File file = m_file = new File(dir, "plugin.apk");

        InputStream inputStream = getResources().openRawResource(R.raw.plugin);
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            byte[] bytes = new byte[];
            int length = -;

            while ((length = inputStream.read(bytes)) != -) {
                fileOutputStream.write(bytes, , length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }

        //然后产生一个dex class loader
        //第一个参数是外部apk位置
        //第二个参数是apk要解压的位置
        //第三个参数是原生库位置 我们只是一个简单的插件 所以并没有
        //第四个参数指定父类加载器
        m_classLoader = new DexClassLoader(
                file.getAbsolutePath(),
                getDir("lib", MODE_PRIVATE).getAbsolutePath(),
                null,
                getClassLoader());
    }
}
           

代码有点长 我为了省事并没有把他们分到多个函数里。。。 我觉得按照注释读代码是最好理解的,所以这里读者看注释就行了

之后的代码和之前有所不同,比如我们客户端变成这样invoke插件中的activity了:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.id_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Utils.invokePluginActivity(MainActivity.this, "com.chan.plugin",
                        "com.chan.plugin.MainActivity");
            }
        });
    }
           

而utils中则变成:

/**
 * Created by chan on 16/4/14.
 */
public class Utils {

    public static void invokePluginActivity(Activity activity, String pluginPackage, String pluginClass) {
        Intent intent = new Intent();
        intent.putExtra(Constant.EXTRA_INVOKE_PLUGIN, true);
        intent.setComponent(new ComponentName(pluginPackage, pluginClass));
        activity.startActivity(intent);
    }
}
           

AMSHook的代码:

/**
 * Created by chan on 16/4/13.
 */
public class AMSHook implements InvocationHandler {

    private Object m_base;
    private Context m_context;

    public AMSHook(Context context, Object base) {
        m_base = base;
        m_context = context;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //拦截startActivity方法
        if ("startActivity".equals(method.getName())) {

            //查找原始的intent对象
            Intent raw = null;
            final int size = (args == null ?  : args.length);
            int i = ;
            for (; i < size; ++i) {
                if (args[i] instanceof Intent) {
                    raw = (Intent) args[i];
                    break;
                }
            }

            //看下是否是启动插件中的activity
            if (raw.getBooleanExtra(Constant.EXTRA_INVOKE_PLUGIN, false)) {

                //创建一个新的Intent
                Intent intent = new Intent();

                //把Component替换为StubActivity的 这样就不会被系统检测到  启动一个没有在AndroidManifest.xml
                //中声明的activity
                intent.setComponent(new ComponentName(m_context.getPackageName(),
                        StubActivity.class.getCanonicalName()));

                //保存原始的intent
                intent.putExtra(Constant.EXTRA_RAW_INTENT, raw);

                //替换为新的Intent
                args[i] = intent;
            }
        }

        //还是按往常一样调用各种函数
        return method.invoke(m_base, args);
    }
}
           

变化还是很小的,只不过ctor多了一个参数

而对于MessageHook变化就比较大了:

package com.chan.hook.handle;

import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.chan.hook.StubActivity;
import com.chan.hook.util.Constant;

import java.lang.reflect.Field;

/**
 * Created by chan on 16/4/14.
 */
public class MessageHook implements Handler.Callback {
    private Handler.Callback m_base;
    private static final int LAUNCH_ACTIVITY = ;
    private Field m_intentField;
    private Field m_activityInfo;

    public MessageHook(Object base, ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException {
        m_base = (Handler.Callback) base;

        //获取ActivityClientRecord的class
        Class<?> activityClientRecordClz = Class.forName("android.app.ActivityThread$ActivityClientRecord", false, classLoader);
        //获得它的intent
        m_intentField = activityClientRecordClz.getDeclaredField("intent");
        m_intentField.setAccessible(true);

        m_activityInfo = activityClientRecordClz.getDeclaredField("activityInfo");
        m_activityInfo.setAccessible(true);
    }

    @Override
    public boolean handleMessage(Message msg) {

        //检测到时启动一个activity
        if (msg.what == LAUNCH_ACTIVITY) {
            try {

                //msg.obj是android.app.ActivityThread$ActivityClientRecord对象,请参考前面的源码解析
                Intent intent = (Intent) m_intentField.get(msg.obj);
                ComponentName componentName = intent.getComponent();

                //检测到是启动StubActivity
                if(componentName != null &&
                        componentName.getClassName().equals(StubActivity.class.getCanonicalName())) {

                    //获得之前启动插件的intent
                    Intent raw = intent.getParcelableExtra(Constant.EXTRA_RAW_INTENT);
                    //替换成插件的component
                    intent.setComponent(raw.getComponent());

                    //在Activity Thread中,getPackageInfo这个函数根据activity info的application info
                    // 的包名查找缓存,所以这里要替换掉,不能用宿主的包名
                    //否则就会加载不到类
                    ActivityInfo activityInfo = (ActivityInfo) m_activityInfo.get(msg.obj);
                    activityInfo.applicationInfo.packageName = raw.getComponent().getPackageName();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //之后的操作还是和原来一样
        return m_base != null && m_base.handleMessage(msg);
    }
}
           

这里要替换掉r.activityInfo的applicationInfo的packageName,因为getPackageInfo根据他查找缓存

android插件开发——加载插件

现在开始运行吧:

- :: -/com.chan.hook E/AndroidRuntime: FATAL EXCEPTION: main
                                                             Process: com.chan.hook, PID: 
                                                             java.lang.RuntimeException: Unable to start activity ComponentInfo{com.chan.plugin/com.chan.plugin.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.chan.plugin; is package not installed?
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:)
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:)
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:)
                                                                 at android.os.Handler.dispatchMessage(Handler.java:)
                                                                 at android.os.Looper.loop(Looper.java:)
                                                                 at android.app.ActivityThread.main(ActivityThread.java:)
                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                 at java.lang.reflect.Method.invoke(Method.java:)
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:)
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)
                                                              Caused by: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.chan.plugin; is package not installed?
                                                                 at android.app.LoadedApk.makeApplication(LoadedApk.java:)
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:) 
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:) 
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:) 
                                                                 at android.os.Handler.dispatchMessage(Handler.java:) 
                                                                 at android.os.Looper.loop(Looper.java:) 
                                                                 at android.app.ActivityThread.main(ActivityThread.java:) 
                                                                 at java.lang.reflect.Method.invoke(Native Method) 
                                                                 at java.lang.reflect.Method.invoke(Method.java:) 
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:) 
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:) 
                                                              Caused by: java.lang.IllegalStateException: Unable to get package info for com.chan.plugin; is package not installed?
                                                                 at android.app.LoadedApk.initializeJavaContextClassLoader(LoadedApk.java:)
                                                                 at android.app.LoadedApk.makeApplication(LoadedApk.java:)
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:) 
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:) 
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:) 
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:) 
                                                                 at android.os.Handler.dispatchMessage(Handler.java:) 
                                                                 at android.os.Looper.loop(Looper.java:) 
                                                                 at android.app.ActivityThread.main(ActivityThread.java:) 
                                                                 at java.lang.reflect.Method.invoke(Native Method) 
                                                                 at java.lang.reflect.Method.invoke(Method.java:) 
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:) 
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:) 
           

oh,天哪,奔溃了。。。日志提示是我们并没有安装刚刚的插件apk。。。

我们查找下错误信息吧:

错误提示出现在performLaunchActivity这个函数里面,并且我们也找到了相关的字符串:

android插件开发——加载插件

我们就从这个try块的第一行开始看吧:

android插件开发——加载插件

点进去看下:

android插件开发——加载插件

我们看到,如果mApplication不为空,那么就直接返回,这也对应了平时我们所说的,在一个应用中application只有一个,再往下看。如果当前的包名不是android,那么久要执行第570行的代码,我们进入看下:

android插件开发——加载插件

我们刚好找到了这里对应的错误信息,显然,当执行pm.getPackageInfo后,我们的pi是空的,也就对应了在系统中并没有安装,那么我们就只能hook PM了。

我们找到ActivityThread.getPackageManager去看下

android插件开发——加载插件

又是 静态变量,轻车熟路啊,我们之前已经讲了太多次这种情况如何hook,所以这里不加阐述,直接上代码,当然,还是在HookApplication中:

// 获取ActivityThread里面原始的 sPackageManager
        Field sPackageManagerField = m_activityThreadClz.getDeclaredField("sPackageManager");
        sPackageManagerField.setAccessible(true);
        Object sPackageManager = sPackageManagerField.get(m_activityThreadObject);

        Object hook = Proxy.newProxyInstance(
                getClassLoader(),
                new Class[]{ Class.forName("android.content.pm.IPackageManager",
                        false, getClassLoader())},
                new PMHook(this, sPackageManager));
        sPackageManagerField.set(m_activityThreadObject, hook);
           

运行效果:

android插件开发——加载插件

源码