Android插件化的兼容性(中):Android P的适配

Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦。

Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了Instrumentation。

3.1 H类的变身

3.1.1 从Message和Handler说起

对于App开发人员而言,Message和Handler是耳熟能详的两个概念。我们简单回顾一下,一条消息是怎么发送和接收的。

首先,在App启动的时候,会创建ActivityThread,这就是主线程,也叫UI线程。App的入口——main函数,就藏在ActivityThread中,

在main函数中,会创建MainLooper。MainLooper是一个死循环,专门负责接收消息,也就是Message类。

Message类的定义如下,除了耳熟能详的what和obj属性外,还有一个不对App开放的变量target,它是一个Handler:

public final class Message implements Parcelable {
    public int what;
    public Object obj;
     Handler target;

    //以下省略很多代码哦
}

在App进程中,Application和四大组件的每个生命周期函数,都要和AMS进程进行跨进程通信。

1)App进程把数据传给AMS进程,是通过ActivityManagerNative完成的。

2)AMS进程把数据传给App进程,App进程这边接收数据的是ApplicationThread。

ApplicationThread在接收到数据后,会调用sendMessage方法。这就把消息Message对象发送给了MainLooper这个死循环。

在MainLooper死循环中,处理这个Message对象。怎么处理呢,取出它的target字段,这是一个Handler类型的对象,调用这个Handler对象的dispatchMessage方法。

是时候看一下Handler类的结构了,我简化了它的代码,为的是易于理解:

public class Handler {
    final Callback mCallback;

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

    public void handleMessage(Message msg) {
    }

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
}

Handler类中有一个mCallback变量,这个变量是插件化技术的核心。书接上文,MainLooper调用了Handler的dispatchMessage方法,这个方法的逻辑是,要么执行mCallback的handleMessage方法,要么执行Handler类自己的handleMessage方法。

Handler类自己的handleMessage方法,是一个空方法,所以我们一般写一个Handler的子类,然后实现这个handleMessage方法。

在Android系统底层,这个Handler类的子类,就是H类,我们在ActivityThread.java中可以找到这个类。H类的handleMessage方法中,定义了所有消息的分发,如下所示:

public final class ActivityThread {
    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY           = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        public static final int SHOW_WINDOW             = 105;
        public static final int HIDE_WINDOW             = 106;
        public static final int RESUME_ACTIVITY         = 107;
        public static final int SEND_RESULT             = 108;
        public static final int DESTROY_ACTIVITY        = 109;
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int NEW_INTENT              = 112;
        public static final int RECEIVER                = 113;

        //以下省略很多代码

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;

            //以下省略很多代码
            }
        }
    }
}

在H类的handleMessage方法中,会根据msg参数的what值,来判断到底是哪种消息,以及相应的执行什么逻辑,比如说,启动Activity。

在H类中,定义了几十种消息,比如说LAUNCH_ACTIVITY的值是100,PAUSE_ACTIVITY的值是101。从100到109,都是给Activity的生命周期函数准备的。从110开始,才是给Application、Service、ContentProvider、BroadcastReceiver使用的。

至此,我们简单回顾了Android系统内部Message的发送和接收流程。其中比较重要的是:

1)Handler类中有一个mCallback变量。

2)H类中定义了各种消息。

3.1.2 Android P之前的插件化解决方案

在Android P(API level 28)之前,我们做插件化,都是Hook掉H类的mCallback对象,拦截这个对象的handleMessage方法。在此之前,我们把插件中的Activity替换为StubActtivty,那么现在,我们拦截到handleMessage方法,再把StubActivity换回为插件中的Activity,代码如下所示:

class MockClass2 implements Handler.Callback {

    Handler mBase;

    public MockClass2(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
            case 100:
                handleLaunchActivity(msg);
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;

        Object obj = msg.obj;

        // 把替身恢复成真身
        Intent raw = (Intent) RefInvoke.getFieldObject(obj, "intent");

        Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        raw.setComponent(target.getComponent());
    }
}

3.1.3 Android P对Activity消息机制的改造

Android系统升级到P,它重构了H类,把100到109这10个用于Activity的消息,都合并为159这个消息,消息名为EXECUTE_TRANSACTION。

为什么要这么修改呢?相信大家面试Android工程师岗位的时候,都会被问及Activity的生命周期图。这其实是一个由Create、Pause、Resume、Stop、Destory、Restart组成的状态机。按照设计模式中状态模式的定义,可以把每个状态都定义成一个类,于是便有了如下的类图:

就拿LaunchActivity来说吧,在Android P之前,是在H类的handleMessage方法的switch分支语句中,编写启动一个Activity的逻辑:

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;

            //以下省略很多代码
            }
        }

在Android P中,启动Activity的这部分逻辑,被转移到了LaunchActivityItem类的execute方法中。

public class LaunchActivityItem extends ClientTransactionItem {

    @Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client);
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
    }
}

从架构的角度来说,这次重构的效果很好。使用状态模式,使得Android这部分代码,就是OOP的了。我们把写在H类的handleMessage方法中的switch分支语句,拆分到很多类中,这就符合了五大设计原则中的开闭原则,宁肯有100个类,每个类有100行代码,也不要有一个一万行代码的类。好处是,当我想改动Resume这个状态的业务逻辑时,我只要在ResumeActivityItem类中修改并进行测试就可以了,影响的范围很小。

但是这次重构也有缺点,OOP的缺点就是代码会让人看不懂,因为只有在运行时才知道到底实例化的是哪个类,这让原本清晰的Android Activity消息逻辑变得支离破碎。

按照这个趋势,四大组件之一的Service,它在H类中也有很多消息,也是有很多生命周期函数,Android的下个版本,极有可能把Service也重构为状态模式。

3.1.4 Android P针对于H的Hook

Android P把H类中的100-109这10个消息都删除了,取而代之的是159这个消息,名为EXECUTE_TRANSACTION。

这就导致我们之前的插件化解决方案,在Android P上是不生效的,会因为找不到100这个消息,而不能把StubActiivty换回为插件中的Activity。为此,我们需要拦截159这个消息。拦截后,我们又要面对如何判断当前这个消息到底是Launch,还是Pause或者Resume。

关键在于H类的handleMessage方法的Message参数。这个Message的obj字段,在Message是159的时候,返回的是ClientTransacion类型对象,它内部有一个mActivityCallbacks集合:

public class ClientTransaction implements Parcelable, ObjectPoolItem {

      private List<ClientTransactionItem> mActivityCallbacks;

}

这个mActivityCallbacks集合中,存放的是ClientTransactionItem的各种子类对象,比如LaunchActivityItem、DestoryActivityListItem。我们可以判断这个集合中的值,发现有某个元素是LaunchActivityItem类型的,那么就相当于捕获到了启动Activity的那个消息。

定位到LaunchActivityItem类的对象,它内部有一个mIntent字段,里面存放的就是要启动的Activity名称,目前值是StubActivity。在这里把它替换为真正要启动的插件Activity,代码如下所示:

class MockClass2 implements Handler.Callback {

    Handler mBase;

    public MockClass2(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
            case 100:   //for API 28以下
                handleLaunchActivity(msg);
                break;
            case 159:   //for API 28
                handleActivity(msg);
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

    private void handleActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj;

        List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks");
        if(mActivityCallbacks.size() > 0) {
            String className = "android.app.servertransaction.LaunchActivityItem";
            if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                Object object = mActivityCallbacks.get(0);
                Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent");
                Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
                intent.setComponent(target.getComponent());
            }
        }
    }
}

3.2 Instrumentation的变身

在Android P之前,Instrumentation的newActivity方法。逻辑如下:

    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
}

到了Android P,则改写了Instrumentation类的部分逻辑。它会在newActivity方法中,检查Instrumentation的mThread变量,如果为空,就会抛出一个异常:

public class Instrumentation {
    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        String pkg = intent != null && intent.getComponent() != null
                ? intent.getComponent().getPackageName() : null;
        return getFactory(pkg).instantiateActivity(cl, className, intent);
    }

    private AppComponentFactory getFactory(String pkg) {
        if (pkg == null) {
            Log.e(TAG, "No pkg specified, disabling AppComponentFactory");
            return AppComponentFactory.DEFAULT;
        }
        if (mThread == null) {
            Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
                    + " disabling AppComponentFactory", new Throwable());
            return AppComponentFactory.DEFAULT;
        }
        LoadedApk apk = mThread.peekPackageInfo(pkg, true);
        // This is in the case of starting up "android".
        if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
        return apk.getAppFactory();
    }
}

我们在本书第5章介绍给一种Hook方案,拦截Instrumentation类的execStartActivity方法,如下所示:

public class HookHelper {

    public static void attachContext() throws Exception{
        // 先获取到当前的ActivityThread对象
        Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");

        // 拿到原始的 mInstrumentation字段
        Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread, "mInstrumentation");

        // 创建代理对象
        Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);

        // 偷梁换柱
        RefInvoke.setFieldObject(currentActivityThread, "mInstrumentation", evilInstrumentation);
    }
}

public class EvilInstrumentation extends Instrumentation {

    private static final String TAG = "EvilInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public EvilInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        Log.d(TAG, "XXX到此一游!");

        // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        Class[] p1 = {Context.class, IBinder.class,
                IBinder.class, Activity.class,
                Intent.class, int.class, Bundle.class};
        Object[] v1 = {who, contextThread, token, target,
                intent, requestCode, options};

        return (ActivityResult) RefInvoke.invokeInstanceMethod(
                mBase, "execStartActivity", p1, v1);
    }
}

这段代码,我们把系统原先的Instrumentation替换成EvilInstrumentation,在Android P以下的系统是可以运行的,但是在Android P上就会抛出Uninitialized ActivityThread, likely app-created Instrumentation的异常,显然这是因为EvilInstrumentation的mThread为空导致的。

想要解决这个问题,就必须重写EvilInstrumentation中的newActivity方法,如下所示:

public class EvilInstrumentation extends Instrumentation {
    //省略了部分代码

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {

        return mBase.newActivity(cl, className, intent);
    }
}

这样编码,即使是EvilInstrumentation,在执行newActivity方法的时候,也会执行原先Instrumentation的newActivity方法,Instrumentation的mThread字段不是null,所以就不会抛出上述的异常信息了。

原文地址:https://www.cnblogs.com/Jax/p/9521305.html

时间: 2024-10-28 05:58:24

Android插件化的兼容性(中):Android P的适配的相关文章

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

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

android插件化-apkplug中OSGI服务基本原理-08

我们提供 apkplug 下OSGI使用demo 源码托管地址为 http://git.oschina.net/plug/OSGIService 一 OSGI与android Service 异同点 OSGI服务与android Service概念差不多也是Service ,Client 关系. android Service接口  --service.AIDL OSGI接口                --java interface 所以android 进程间通信Service只能传递序列

Android插件化开发---运行未安装apk中的Service

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法.其实一般的插件开发主要也就是加载个Activity,读取一些资源图片之类的.但是总有遇到特殊情况的时候,比如加载Service. 要动态加载Service,有两种思路:一是通过NDK的形式,将Service通过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现

Android插件化(二):使用DexClassLoader动态加载assets中的apk

Android插件化(二):使用DexClassLoader动态加载assets中的apk 简介 上一篇博客讲到,我们可以使用MultiDex.java加载离线的apk文件.需要注意的是,apk中的类是加载到当前的PathClassLoader当中的,如果apk文件过多,可能会出现ANR的情况.那么,我们能不能使用DexClassLoader加载apk呢?当然是可以的!首先看一下Doc文档. A class loader that loads classes from .jar and .apk

自己动手写Android插件化框架

最近在工作中接触到了Android插件内的开发,发现自己这种技术还缺乏最基本的了解,以至于在一些基本问题上浪费不少时间,如插件Context和主工程Context的区别,权限必须在主工程申明等,因此花了点时间了解了一下插件的历史,并写了两个Demo作为总结.本文旨在通过两个实例直观的说明插件的实现原理以加深对插件内开发的理解,因此不会深入探讨背景和原理,代码也尽量专注于核心逻辑. 原理与背景 Android插件化从技术上来说就是如何启动未安装的apk(主要是四大组件)里面的类,主要问题涉及如何加

android插件化-apkplug从宿主启动插件Activity-06

插件是一个apk文件它存在自己的Activity界面和UI显示,本节将讲解如何配置插件的启动Activity以及怎样从宿主启动它. 一 配置插件apk的对外启动Activity (内部activity不需要配置) 与普通app不同,插件AndroidManifest.xml配置在apkplug框架中是无效的,我们需要在plugin.xml里面配置才能被apkplug所识别 具体设置属性为 Bundle-Activity="xxx.xxx.xxx.Activity" 只有设置为Bundl

Android插件化(使用Small框架)

github: https://github.com/cayden/MySmall Android插件化(使用Small框架) 框架源代码 1. Create Project File->New->New Project... 1.1 Configure your new project 假设宿主包名为com.example.mysmall 设置Application name为MySmall 改动Company Domain为com.example.mysmall 这步是个技巧,在Step3

android插件化-获取apkplug框架已安装插件-03

上一篇文章成功的将apkplug框架嵌入了应用中并且启动 链接http://www.apkplug.com/blog/?post=10 这一篇文章实现如何获取所有已安装插件 一 获取框架的SystemBundle的上下文BundleContext apkplug框架启动会自动创建一个SystemBundle, 它是框架的第一个插件不可停止和卸载,通过它我们可以与apkplug和其他插件通信 FrameworkInstance.getSystemBundle() 便可以获取到SystemBundl

Android 插件化 动态升级

最新内容请见原文:Android 插件化 动态升级 不少朋友私信以及 Android开源交流几个 QQ 群 中都问到这个问题,这里简单介绍下 1.作用 大多数朋友开始接触这个问题是因为 App 爆棚了,方法数超过了一个 Dex 最大方法数 65535 的上限,从这个介绍中也知道可以通过多个 Dex 来解决这个问题,因而便有了插件化的概念,将一个 App 划分为多个插件(Dex或相关格式)常用的其他解决方法还包括:删无用代码,用 H5 代替部分逻辑,买付费版的 Proguard插件化的其他作用包括