Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android插件開發——加載插件

android插件開發——加載插件

編輯:關於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 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(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference(packageInfo));
                }
            }
            return packageInfo;
        }
    }

這裡呢mPackages的類型為:

 final ArrayMap> mPackages
            = new ArrayMap>();

我們不難得出,如果要獲得一個LoadApk對象,要解決兩個問題:1:獲得ApplicationInfo對象。 2:獲得CompatibilityInfo對象

對於ApplicationInfo 官方是這麼解釋的:

/**
 * Information you can retrieve about a particular application.  This
 * corresponds to information collected from the AndroidManifest.xml's
 * <application> 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直接使用默認的就行了,比如我們之前的例子就是(見上文):<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> @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> packages = (Map>)
                    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);

運行效果:
這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved