android插件化(二)----四大组件的加载

https://blog.csdn.net/adminyuqiao/article/details/110690030

上一篇文章说了Android中的普通类的加载方法。
这次说一说四大组件的加载----以activity为例。因为android版本不同,这里以26为准。至于其他版本,需要适配下。流程逻辑没有啥太大区别。

首先咱现在插件apk中添加一个MainActivity。当然先把setContentVIew注释掉,因为用到了资源文件,等下次再说。

 	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
//		就简单打个log吧
        Log.e(TAG, "onCreate: 启动插件的activity" );
    }

大家都知道activity需要在manifest中进行注册才能启动。

  <activity android:name=".MainActivity"/>

但是在宿主的app中不会有插件activity的注册。这咋解决?
这里需要用到Hook技术,所谓hook说直白点就是反射和动态代理。知道这就行了。也许大家有更好的办法,这只是一个思路而已。

 Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test","com.winter.test.MainActivity"));
startActivity(intent);

直接这么写大家肯定知道无法启动,因为没有注册,那么我们可以在我们的宿主中加入一个ProxyActivity并在宿主的manifest中注册。在AMS去检查的时候将我们的intent中的内容换成ProxyActivity。而后再换成插件的activity就可以了。

proxyActivity如下:

public class ProxyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("winter", "onCreate: 我是宿主代理Activity" );
    }
}

这里需要一点activity启动流程和动态代理的基础。如果不太了解希望先去了解下。

画个图,这个思路就是这样
在这里插入图片描述
接下来的步骤就是找哪儿能够让我们做这些个步骤了。

一般找hook点的准则有2点
1.尽量找静态变量或者单利的
2.尽量找public的

知道了目标就开干!
修改启动的activity,其实就是修改intent,所以我们就去启动流程中找intent就好了。

按照启动流程,很容易找到下面代码

Instrumentation.ActivityResult ar = mInstrumentation.
execStartActivity(this, 
mMainThread.getApplicationThread(), 
mToken, this,
intent, requestCode, options);

我们点进去看看,看到这里

 int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

这个ActivityTaskManager.getService.startActivity()就是我要找的hook点,
可以动态代理IActivityTaskManager这个对象,在其startActivity的时候,做点小动作,就是替换intent。是不是就可以实现了。关于动态代理,这就不说了,如果不是很明白,那就先去了解下。

首先获取IActivityManger对象,根据源码

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

我们需要先获取这个Singleton的对象,然后调用其get方法

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

还是一样通过反射获取

Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleTon = singletonField.get(null);

调用get方法

Class<?> singleTonClass = Class.forName("android.util.Singleton");
Field mInstanceFiled = singleTonClass.getDeclaredField("mInstance");
mInstanceFiled.setAccessible(true);
Method getMethod = singleTonClass.getMethod("get");
final Object mInstance = getMethod.invoke(singleTon);

这个mInstance就是我们想要的

而后就是通过动态代理,干点坏事情

 Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{IActivityManager}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //过滤,只有当调用startActivity的时候才去动态代理
                            if ("startActivity".equals(method.getName())) {
                                int index = -1;
                                //获取Intent 对象
                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                    }
                                }
                                //修改intent
                                Intent intent = (Intent) args[index];
                                Intent proxyIntent = new Intent();
                                proxyIntent.setClassName("com.winter.myutils",
                                        "com.winter.myutils.ProxyActivity");
								//我们将原先的intent保存起来,方便之后获取                                
                                proxyIntent.putExtra(TARGET_INTENT, intent);
                                args[index] = proxyIntent;
                            }

                            return method.invoke(mInstance, args);
                        }
                    });

最后别忘了最后一步

 mInstanceFiled.set(singleTon, proxyInstance);

至此,我们就完成了在AMS检查注册之前替换intent的步骤。我们可以运行一下看看,当我们启动插件中的MainActivity

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.winter.test",
                    "com.winter.test.MainActivity"));
startActivity(intent);

可以看到日志
在这里插入图片描述
OK 接下来下一步就是替换回来了。

经过了AMS检查注册,而后我们只要关注经过AMS之后干了啥就可以了,在这中间找我们需要的hook点

我们进入ActivityThread中的ApplicationThread的scheduleLaunchActivity方法

@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
           		...
            sendMessage(H.LAUNCH_ACTIVITY, r);
        }

可以看到这里发送了一个LAUNCH_ACTIVITY的消息。这个H就是一个Handler。接下来我们看handleMessage

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

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

然后就是一步步点进去看,正常的启动流程。不详细描述了
最后走到了这里

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...
       ComponentName component = r.intent.getComponent();
      ...
}

我们找到了我们需要的intent,但是这个intent在ActivityClitentRecord中

static final class ActivityClientRecord {
      	...
        Intent intent;
        ...
}

可以通过源码看到,intent并不是静态的,所以我们需要ActivityClientRecord对象。
细心的同学已经知道了,在上面给出的源码中已经给出了这个对象,
在handleMessage中已经有ActivityClientRecord对象r

 final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

如果我们能拿到msg拿到,是不是就能拿到ActivityClientRecord对象了。
接下来就是分析Handler的源码。emmmm。。这个应该是最基本的,不了解的话我也没办法了。

所以我们要Hook Handler
我们直接看HandleMessage

 /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

看源码可以知道实际上是通过dispatchMessage方法调用了handleMessage。
分析dispatchMessage方法
可以看到,如果mCallback不等于空,是不是就能把msg回调给我们。
再分析上面发送消息的H

 final H mH = new H();

其中H类中并没有构造方法,所以其调用的是Handler中的构造方法

public Handler() {
//第一个参数就是callback
        this(null, false);
}

所以可以看到在这里callback是空的。
所以我们可以写一个接口,实现callback。替换系统的callback。从而拿到msg,而msg的obj就是ActivityClientRecord,所以就能拿到intent了

剩下的事情就是撸码了

首先自己创建一个callback

Handler.Callback callback = msg -> {
            switch (msg.what) {
            	//	通过msg 可以拿到intent
            	//100就是上面LAUNCH_ACTIVITY的值
                case 100:
                    try {
                    //msg.obj 为ActivityClientRecord
                        Field intentField = msg.obj.getClass().getDeclaredField("intent");
                        intentField.setAccessible(true);
                       
                        Intent proxyIntent = (Intent) intentField.get(msg.obj);
                         //第一步保存的intent,替换
                        Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                        if (intent != null) {
                            intentField.set(msg.obj, intent);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
            }
            //必须return false 
            return false;
};

代码没啥好说的。一目了然。

接下来就是进行hook 替换了

要替换系统的callback,因为callback不是静态的,所以首先拿到handler对象就是ActivityThread中的mh,mh也不是静态的,所以要拿到ActivityThread对象。正好在ActivityThread中有这个

 private static volatile ActivityThread sCurrentActivityThread;

正好是静态的,我们就获取这个玩意儿,就是通过反射api的调用,没啥好说的了

Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);

Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);

Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);

最后别忘了

 mCallbackField.set(mH, callback);

至此第二部分的替换也完成了。

运行一下试试。
在这里插入图片描述
OK 大功告成。

PS:这里只是基于api26 。在android10上面不一样,要通过适配,IActivityManager好像变成了IActiivityTaskManager。下面的Handler发送消息的也不一样,应该是159.而不是100.详细代码不写了。这里只是提供一个思路给大家了解插件化这个东西。希望大家喜欢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值