android插件开发——加载插件

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

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

本节就是解决这个问题。

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

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

在加载类的时候,他们采用委托机制,比如,我们自定义的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) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (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-2.0
 *
 * 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

之后再handleLaunchActivity函数中:

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

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

我们看下这个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) != 0, 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中

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

回顾之前的学习,这个函数最终是调用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的源码就知道,关于这个类,我们要做非常之多的兼容。。。


所以本文都假设你的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的这个函数获得:

而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, 0);
            //已经获得了application info
            ApplicationInfo applicationInfoObject = (ApplicationInfo) generateApplicationInfoMethod.invoke(null, packageObject, 0,
                    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[1024];
            int length = -1;

            while ((length = inputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, 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 ? 0 : args.length);
            int i = 0;
            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 = 100;
    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根据他查找缓存

现在开始运行吧:

04-17 04:30:10.186 3686-3686/com.chan.hook E/AndroidRuntime: FATAL EXCEPTION: main
                                                             Process: com.chan.hook, PID: 3686
                                                             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:2298)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:144)
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                 at android.os.Looper.loop(Looper.java:135)
                                                                 at android.app.ActivityThread.main(ActivityThread.java:5221)
                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                 at java.lang.reflect.Method.invoke(Method.java:372)
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
                                                              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:563)
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2216)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:144)
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                 at android.os.Looper.loop(Looper.java:135)
                                                                 at android.app.ActivityThread.main(ActivityThread.java:5221)
                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                 at java.lang.reflect.Method.invoke(Method.java:372)
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
                                                              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:409)
                                                                 at android.app.LoadedApk.makeApplication(LoadedApk.java:555)
                                                                 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2216)
                                                                 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
                                                                 at android.app.ActivityThread.access$800(ActivityThread.java:144)
                                                                 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
                                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                 at android.os.Looper.loop(Looper.java:135)
                                                                 at android.app.ActivityThread.main(ActivityThread.java:5221)
                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                 at java.lang.reflect.Method.invoke(Method.java:372)
                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

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

我们查找下错误信息吧:

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

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

点进去看下:

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

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

我们找到ActivityThread.getPackageManager去看下

又是 静态变量,轻车熟路啊,我们之前已经讲了太多次这种情况如何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);

运行效果:

源码

时间: 2024-09-29 15:43:02

android插件开发——加载插件的相关文章

Android插件化(三)加载插件apk中的Resource资源

Android加载插件apk中的Resource资源 简介 如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了.总结如下: 1.新建一个AssetManag

Android动态加载那些事儿

基础 1.Java 类加载器 类加载器(class loader)是 Java?中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式.加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi?中的应用. 2.反射原理 Java 提供的反射機制允許您於執行時期動態載入類別.檢視類別資訊.生成物件或操作生成的物件,要舉反射機制的一個應用實例,就

Android动态加载框架DL的架构与基本原理解析

转载请注明出处,本文来自[ Mr.Simple的博客 ]. 我正在参加博客之星,点击这里投我一票吧,谢谢~ 前言 最近这一两年,Android App使用插件化技术开发的数量越来越大,其实还是业务地快速膨胀导致,需求越来越多,App越来越臃肿.虽然手机的内存空间不断地的增大,但是太大的安装包给用户也造成了心理压力.于是大家都会想到插件化的开发方式,把App做成一个平台,而不是一个独立的app.平台上可以集成各种各样的功能,功能模块也插件的形式添加进来,这些插件不需要安装,只需要用户按需下载到某个

Android动态加载jar/dex

http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html 前言 在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病. 声明 欢迎转载,但请保留文章原始出处:) 博客园:http:/

Android动态加载技术三个关键问题详解

编者按:InfoQ开设新栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注.本文节选自任玉刚著<Android开发艺术探索>中的章节“Android的动态加载技术”,探讨了Android动态加载的三个关键问题. 动态加载技术(也叫插件化技术)在技术驱动型的公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块.动态加载是一项很复杂的技术,这里主要介绍动态加载技术中

android动态加载(二)

上一篇说了android中的动态加载,即在android工程中动态加载经过dx操作以后的jar文件和没有安装的apk文件,今天我们来看看怎么执行已经安装的apk中的类中的方法. 所以,我们会需要两个工程,一个是plugone,这个是我们暴露给外面的方法的一个android工程.另外一个我们暂且给他起名为useplugone吧. 先来看看plugone工程,我们在plugone工程中有这样一个类,用来暴露给调用者一个方法: package com.example.plugone; public c

Android动态加载代码技术

Android动态加载代码技术 在开发Android App的过程当中,可能希望实现插件式软件架构,将一部分代码以另外一个APK的形式单独发布,而在主程序中加载并执行这个APK中的代码. 实现这个任务的一般方法是: // 加载类cls Context pluginContext = mainContext.createPackageContext(PLUGIN_PKG, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE)

Android动态加载Activity原理

activity的启动流程 加载一个Activity肯定不会像加载一般的类那样,因为activity作为系统的组件有自己的生命周期,有系统的很多回调控制,所以自定义一个DexClassLoader类加载器来加载插件中的Activity肯定是不可以的. 首先不得不了解一下activity的启动流程,当然只是简单的看一下,太详细的话很难研究清楚. 通过startActivity启动后,最终通过AMS进行跨进程回调到ApplicationThread的scheduleLaunchActivity,这时

Android异步加载全解析之Bitmap

Android异步加载全解析之Bitmap 在这篇文章中,我们分析了Android在对大图处理时的一些策略--Android异步加载全解析之大图处理  戳我戳我 那么在这篇中,我们来对图像--Bitmap进行一个更加细致的分析,掌握Bitmap的点点滴滴. 引入 Bitmap这玩意儿号称Android App头号杀手,特别是3.0之前的版本,简直就是皇帝般的存在,碰不得.摔不得.虽然后面的版本Android对Bitmap的管理也进行了一系列的优化,但是它依然是非常难处理的一个东西.在Androi