安卓插件化课程-第二篇:加载插件apk中的activity

序:

1.本文是安卓插件化课程的第二篇,完整课程链接参见下面链接:

安卓插件化课程-序章_分享+记录-CSDN博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。章节:插件化系列文章主要包含以下几个篇章:1.加载插件apk中的类2.启动插件apk中的activity3.使用插桩的方式启动apk中的activity4.使用插件apk中的资源5.使用插件apk中的so文件6.插件化加载移动apk。7.插件化目前所遇到的https://blog.csdn.net/rzleilei/article/details/122453035?spm=1001.2014.3001.5502

2.主要内容

本篇主要讲的是如何启动一个插件中的Activity(有前提:前提是activity没有引用插件中的资源,并且提前已经在宿主中注册了)。

一:原理简述


1.activity启动流程简述

启动流程不是本篇介绍的主要核心,所以这里简略的说一下启动流程。

1.Activity.startActivity -> Instrumetation.execStartActivity -> 通过binder机制通知AMS。

2.AMS收到通知后,进行一系列的检查,最后也通过binder通知APP去执行创建等一系列的操作。APP端的binder实体类就是ActivityThread中的ApplicationThread。

这一系列的检查中,就包含mainfest的声明检查,本篇中我们先不hook,下一篇再讲。

3.ApplicationThread收到通知后,调用ActivityThread中的方法去完成Activity的创建。

2.系统是如何生成activity的

1.而处理创建activity的方法是handleLaunchActivity,它交给performLaunchActivity来完成最终的创建。

2.我们看一下最终创建的代码,传入classLoader,交给Instrumetation完成Activity的创建。

java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

3.newActivity方法

这里又通过Factory工厂类去完成创建

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);
    }

这里最终的工厂类是AppComponentFactory,对应的方法如下:

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

我可以看到,直接用传入的classloader加载类,然后进行初始化。

所以,我们可以这样认为,最终使用的classloader就是appContext.getClassloader返回的。

4.classLoader来源

 @Override
    public ClassLoader getClassLoader() {
        return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
    }

我们可以看到,classLoader的来源其实是LoadedApk中的getClassLoader()方法。

我们接着看一下LoadedApk类中的getClassLoader方法。

 @UnsupportedAppUsage
    public ClassLoader getClassLoader() {
        synchronized (mLock) {
            if (mClassLoader == null) {
                createOrUpdateClassLoaderLocked(null /*addedPaths*/);
            }
            return mClassLoader;
        }
    }

所以,最终返回的classLoader,就是LoadedApk类中的mClassLoader。

3.如何让系统加载插件的Activity

所以如果我们能替换掉LoadedApk类中的mClassLoader,这样在系统生成activity的时候,就可以通过我们自定义的ClassLoader去加载我们插件中的activity类,这样就可以实现加载插件中activity的目标了。

同时我们自定义的ClassLoader的parent指向PathClassLoader,这样自定义ClassLoader找不到仍会去PathClassLoader里面找,所以宿主的正常使用时也不会有问题。

有了这个理论基础,那我们就开始代码编写去尝试实现了。

4.如何hook类LoadedApk中的mClassLoader

LoaderApk是一个对象,我们如何拿到这个对象呢?

入口方法一定是先找一个静态对象进行hook。这里我们找到的这个静态对象就是ActivityThread中的sCurrentActivityThread,首先通过反射拿到这个ActivityThread对象,然后获取ActivityThread对象的成员变量mPackages的值。

mPackages是一个map,key为包名,value为LoaderApk对象。所以我们只要获取mPackages的对象,然后通过包名,就能拿到LoaderApk对象。而这个对象中的mClassLoader,就是最终加载Activity的。

二:代码编写


1.插件项目中创建Activity

因为没有实现资源的加载,所以这里的activity没有引用任何资源。

package com.xt.appplugin;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;

public class Plugin1Activity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        LinearLayout linearLayout = new LinearLayout(getApplicationContext());
        TextView textView = new TextView(getApplicationContext());
        textView.setText("这是插件类Plugin1Activity");
        linearLayout.addView(textView);
        setContentView(linearLayout);
    }
}

2.在宿主中注册插件的Activity

这里声明就为了通过mainfest注册检查

 

3.编译项目,拷贝apk


4.编写Hook的代码

1.先获取sCurrentActivityThread对象

2.然后获取sCurrentActivityThread对象中mPackage的值。

3.通过包名,获取mPackage这个map中LoadedApk的对象值。

4.然后使用自定义生成的ClassLoader对象替换掉LoadedApk中的mClassLoader

5.通过包名去启动插件中的Activity

if (position == 2) {
            try {
                val classLoader = javaClass.classLoader
                val loadedApkClass = classLoader.loadClass("android.app.LoadedApk")
                val activityThreadClass = classLoader.loadClass("android.app.ActivityThread")


                val activityThreadField =
                    activityThreadClass.getDeclaredField("sCurrentActivityThread")
                activityThreadField.isAccessible = true
                val activityThreadGet = activityThreadField.get(null)//1
                val packageField = activityThreadClass.getDeclaredField("mPackages")
                packageField.isAccessible = true
                val arrayMap = packageField.get(activityThreadGet)//2
                val loadedApkWeak = (arrayMap as ArrayMap<*, *>).get(context.packageName)
                val loadedApkObject = (loadedApkWeak as WeakReference<*>).get()//3
//                替换classLoader

                val mBaseClassLoaderField = loadedApkClass.getDeclaredField("mBaseClassLoader")
                mBaseClassLoaderField.isAccessible = true
                mBaseClassLoaderField.set(loadedApkObject, selfClassLoader)

                val mDefaultClassLoaderField =
                    loadedApkClass.getDeclaredField("mClassLoader")
                mDefaultClassLoaderField.isAccessible = true
                mDefaultClassLoaderField.set(loadedApkObject, selfClassLoader)//4

                //hook了之后启动插件中的activity

                val intent = Intent()
                intent.setClassName(context, "com.xt.appplugin.Plugin1Activity")
                startActivity(intent)


                Log.i("lxltest", "")
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return
        }


5.测试验证

首先点击加载插件,然后点击启动插件apk中的activity(需要mainfest注册)。

接下来我们就可以看到,可以正常启动插件中的activity了。

 

三:要点总结

1.每个进程都会对应一个LoadedApk。

2.ActivityThread的mPackage存放着所有LoadedApk。

3.加载activity的classLoader,就是LoadedApk中的mclassLoader

四。代码地址:


项目地址:

https://github.com/aa5279aa/android_all_demo

插件项目位置:https://github.com/aa5279aa/android_all_demo/tree/master/DemoClient/appplugin

调用类位置:https://github.com/aa5279aa/android_all_demo/blob/master/DemoClient/app/src/main/java/com/xt/client/fragment/DynamicFragment.kt
 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值