天天看點

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插件開發——加載插件

源碼