Android插件化开发

一.插件化的基本介绍

插件化apk并不安装,它是使用在宿主app的组件去加载插件的资源上,它有以下几个好处:

1.解决安装包太大;

2.方法655535;

3.动态使用;

插件化的基本实现:

1.占位式 (采用占坑和标准的方式去加载插件)

2.Hook式

1.合并插件和宿主的dex和res

2.新建loadedApk通过动态选择插件和宿主的loaderApk的方式

二.占位式(插装式)插件化

1.占位式Activity

1.插件APK没有安装,没有上下文环境,需要用代理的Activity(占位或叫插装)来加载插件Activity的资源

2.插件Activity的跳转需要依赖宿主的占位Activity来完成入栈出栈的效果。

kotlin 复制代码
//插件代理(占位)Activity
class ProxyActivity : Activity() {

    //占位的Activity 使用plugin Apk的资源文件
    override fun getResources(): Resources {
        return PluginManager.getInstance(this).resources
    }

    //占位Activity 使用Plugin的class 文件
    override fun getClassLoader(): ClassLoader {
        return PluginManager.getInstance(this).dexClassLoader
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val clzName=intent.getStringExtra("clzName")
        val clz :Class<*> = classLoader.loadClass(clzName)
        val constructor = clz.getConstructor() //获取PluginActivity 构造方法
        val activity: ActivityInterface =
            constructor.newInstance() as ActivityInterface //强转成ActivityInterface
        //注入当前Activity
        activity.insertAppContext(this)
        activity.onCreate(savedInstanceState)

    }

    override fun startActivity(intent: Intent) {
        //接受插件Activity传过来的参数,再用自己去跳转。
        val clz=intent.getStringExtra("clzName")
        println("pluginPath ProxyActivity  startActivity :${clz}")
        val proxyIntent=Intent(this,ProxyActivity::class.java)
        proxyIntent.putExtra("clzName",clz)
        super.startActivity(proxyIntent)

    }
}

2.占位式Service

1.使用占位的Activity去启动占位的Service

2.通过占位的service去执行PluginService中的start方法

3.占位式广播

流程参考占位的Service。

4.静态广播的注册(Hook方式)

静态广播的注册时机:

设备启动时,app会重新安装,并且解析androidManifest.xml中的组件,然后自动注册;

app安装的时候,会在data/app中放置目录,并且在data/data/pkgName创建所属目录,然后再data/dalvik-cache中创建虚拟器加载指令的目录。所以我们需要关心的是app安装后的扫描的data/app中的目录,系统会解析这个apk下的AndroidManifest.xml的组件,

我们看下android11的解析源码:

scala 复制代码
//1.PackageParser中加载apk的方法
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
    return parsePackage(packageFile, flags, false /* useCaches */);
}

//2.存放Receiver列表
public final static class Package implements Parcelable {
    ...
     public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
    ...
}
//3.receiver具体的表示
  public final static class Activity extends Component<ActivityIntentInfo> implements Parcelable {


      
  }
 public static abstract class Component<II extends IntentInfo> {
        @UnsupportedAppUsage
        public final ArrayList<II> intents; //IntentFilter
      
 }

//5.全类名的获取
//ActivityInfo 持有了manifest的全类名,我们需要反射这个方法
 @UnsupportedAppUsage
    public static final ActivityInfo generateActivityInfo(Activity a, int flags,
            PackageUserState state, int userId) {
        ActivityInfo ai = new ActivityInfo(a.info);
   
        return ai;
    }

示例代码:

ini 复制代码
public void pareApk() {

        try {
            File file = new File(Environment.getExternalStorageDirectory() + File.separator + "plugin.apk");
            Class mParser = Class.forName("android.content.pm.PackageParser");
            Object object = mParser.newInstance(); //1.获取packageParser对象

            /**
             *     public Package parsePackage(File packageFile, int flags) throws PackageParserException {
             */
            Method method = mParser.getMethod("parsePackage", File.class, int.class);

            Object objectPackage = method.invoke(object, file, PackageManager.GET_ACTIVITIES); //2.获取到解析的包
            //3获取manifest中的receiver清单文件的list
            Field field = objectPackage.getClass().getDeclaredField("receivers");


            ArrayList arrayList = (ArrayList) field.get(objectPackage);

            Class filterClz = Class.forName("android.content.pm.PackageParser$Component");
            Field intentField = filterClz.getField("intents");
            for (Object receiver : arrayList) {
                //获取intent-filter
                ArrayList<IntentFilter> intents = (ArrayList) intentField.get(receiver);
                //我们还有一个任务,就是要拿到android:name=".StaticReceiver"
                // activityInfo.name; == android:name=".StaticReceiver"
                //分析源码如何拿到ActivityInfo
                Class mPackageUserState = Class.forName("android.content.pm.PackageUserState");
                Class mUserHandle = Class.forName("android.os.UserHandle");
                int userId = (int) mUserHandle.getMethod("getCallingUserId").invoke(null);
                /**
                 *执行此方法,就能拿到ActivityInfo
                 *  public static final ActivityInfo generateActivityInfo(ActivityInfo ai, int flags,
                 *  PackageUserState state, int userId)
                 *
                 */
                Method generateActivityInfoMethod = mParser.getMethod("generateActivityInfo", receiver.getClass(),
                        int.class, mPackageUserState, int.class);
                //执行此方法,拿到ActivityInfo
                ActivityInfo mActivityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, receiver,
                        0,
                        mPackageUserState.newInstance(),
                        userId);
                String receiverClassName = mActivityInfo.name; //com.seven.pluginapp.StaticReceiver
                System.out.println("pareApk--->>" + receiverClassName);
                Class mStaticReceiverClass = getDexClassLoader().loadClass(receiverClassName);
                System.out.println("pareApk1--->>" + receiverClassName);
                BroadcastReceiver broadcastReceiver = (BroadcastReceiver) mStaticReceiverClass.newInstance();
                for (IntentFilter intentFilter : intents) {
                    //注册广播
                    context.registerReceiver(broadcastReceiver, intentFilter);

                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

三.Hook式插件化

hook可以帮助我们改变系统的某些功能,动态的添加一些功能。

1.Hook初体验

当我们点击按钮时,会去设置监听事件,我们拦截系统view设置的监听事件,为其加盐后再执行后续操作,就实现了简单的钩子操作。

代码示例:我们通过实例化一个OnClickListener动态代理对象,并且通过反射修改源码的ListenerInfo中的OnClickListener为我们动态代理的对象,这样就可以加盐了。

kotlin 复制代码
/**
     * 动态代理+加反射来hook操作
     * 替换view的OnClickListener 加入自己的操作
     */
fun hookButton(view: Button) {


    //替换Button中的listener 下面是view的设置监听的源码 我们需要用自己的方法设置
    //        public void setOnClickListener(@Nullable OnClickListener l) {
    //            if (!isClickable()) {
    //                setClickable(true);
    //            }
    //            getListenerInfo().mOnClickListener = l;
    //        }
    val listenerInfoClz = Class.forName("android.view.View$ListenerInfo")

    //        ListenerInfo getListenerInfo() {
    //            if (mListenerInfo != null) {
    //                return mListenerInfo;
    //            }
    //            mListenerInfo = new ListenerInfo();
    //            return mListenerInfo;
    //        }
    val listenerInfoObjectMethod = View::class.java.getDeclaredMethod("getListenerInfo")
    listenerInfoObjectMethod.isAccessible=true
    val listenerInfoObject = listenerInfoObjectMethod.invoke(view) //需要ListenerInfo对象
    var listenerInfo: Field =listenerInfoClz.getField("mOnClickListener") //获取默认的属性
    val onClickListener= listenerInfo.get(listenerInfoObject) //需要得到ClickListener对象 也是被代理的对象

    //view.OnClickListener的动态代理
    val proxyOnClickListener = Proxy.newProxyInstance(
        classLoader, arrayOf(OnClickListener::class.java)
    ) { proxy, method, args ->
        //加入自己的逻辑
        view.text = "hook it before click !"
        println("proxy do it-->>>> $args" )
        println("method:$method")
        method.invoke(onClickListener,view)//反射被代理对象的执行onClick方法
    }


    //换成动态代理的对象 当收到点击事件回调onClick事件时,会先执行代理对象的方法
    listenerInfo.set(listenerInfoObject,proxyOnClickListener)

}

2.Hook实现Activity注入

我们启动Activity时需要再Manifest中注册,如果不注册会报错,我们使用Hook技术绕过Manifest的检查,并且再实例化时Hook实例化需要跳转的Activity,从而实现Activity的启动。

代码示例:

ini 复制代码
    @Override
    public void onCreate() {
        super.onCreate();
        try {
            hookAms();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            hookSystemHandler();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 绕过清单检测
     * @throws Exception
     */
    public void hookAms() throws Exception {


        //动态代理
        Class mIActivityManagerClass = Class. forName("android.app.IActivityManager");

        Class mActivityManagerNativeClass2 = Class.forName("android.app.ActivityManagerNative");
        Object mIActivityManager = mActivityManagerNativeClass2.getMethod( "getDefault").invoke(  null);

        Object mIActivityManagerProxy = Proxy.newProxyInstance(
                getClassLoader(),
                new Class[]{mIActivityManagerClass},//要监听的接口
                new AmsInvocationHandler(mIActivityManager));


        Class mActivityManagerNativeClass = Class. forName("android.app.ActivityManagerNative");
        Field gDefaultField = mActivityManagerNativeClass.getDeclaredField( "gDefault");
        gDefaultField.setAccessible(true);//
        Object gDefault = gDefaultField. get(null);

        //替换点
        Class mSingletonClass = Class. forName("android.util.Singleton");
        //获取此字段 mInstance
        Field mInstanceField = mSingletonClass. getDeclaredField( "mInstance");
        mInstanceField.setAccessible(true);//让虚拟机不要检测权限修饰符
        //替换
        mInstanceField.set(gDefault,mIActivityManagerProxy);//替换是需要gDefault


    }

    /**
     * 2.实例化自己的Activity
     * @throws Exception
     */
    public void hookSystemHandler() throws Exception {
        Field mCallbackFiled = Handler.class.getDeclaredField("mCallback");
        mCallbackFiled.setAccessible(true);//
        Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
        Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
        Field mHField = mActivityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        //获取真正对象
        Handler mH = (Handler) mHField.get(mActivityThread);
        mCallbackFiled.set(mH, new ProxyHandlerCallback());//替换增加我们自己的实现代码

    }

    private class ProxyHandlerCallback implements Handler.Callback {

        @Override
        public boolean handleMessage(Message msg) {

            Log.i("HookApp", "handleMessage: " + msg.what);

            if (msg.what == 100) {

                Log.i("HookApp", "---->: " + msg.obj.getClass());
                try {
                    Object obj = msg.obj;
                    //我们要获取之前Hook携带过来的 Proxy
                    Field intentField = obj.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);
                    //获取intent对象,才能取出携带过来的I action Intent
                    Intent intent = (Intent) intentField.get(obj);
                    // actionIntent == TestActivityIntent
                    Intent actionIntent = intent.getParcelableExtra("oldIntent");
                    if (actionIntent != null) {
                        intent.setClass(HookApp.this, HookActivity.class);
                        intentField.set(obj, actionIntent);//把ProxyActivity 换成 HookActivity

                        Log.i("HookApp", "---->: SET--->>> " );
                    }
                } catch (Exception e) {
                    e.printStackTrace();

                }
            }
            return false;
        }
    }

    class AmsInvocationHandler implements InvocationHandler {
        private Object iActivityManagerObject;

        public AmsInvocationHandler(Object iActivityManagerObject) {
            this.iActivityManagerObject = iActivityManagerObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Log.d("HookApp", "AmsInvocationHandler invoke--->>>>>>" + method.getName());

            if ("startActivity".contains(method.getName())) {
                Intent intent = null;
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg instanceof Intent) {
                        intent = (Intent) args[i]; // 原意图,过不了安检
                        index = i;
                        break;
                    }
                }
                Log.d("HookApp", "start ProxyActivity--->>>>>>"+index); //使用代理的绕过安检
                Intent proxyIntent = new Intent(HookApp.this, ProxyActivity.class);
                proxyIntent.putExtra("oldIntent", intent);
                args[index] = proxyIntent;
            }
            return method.invoke(iActivityManagerObject, args);
        }
    }

3.Hook实现插件和宿主dex的合并

1.流程示意图

2.dexLoader初始化

BootClassLoader为java的类加载器;

PathClassLoader为android的dex加载器,它的内部设置的parent(并非父类设置的是BootClassLoader)。

3.loadClass流程

BathDexClassLoader是PathClassLoader的父类

代码示例:hook点为将插件的dex插入的BaseDexClassLoader中的dexPathList中去。

ini 复制代码
public class HookDexApp extends Application {


    @Override
    public void onCreate() {
        super.onCreate();

        try {
             mergePluginDex();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
         mergePluginLayout();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public Resources getResources() {
        Log.d("PluginDexManager", "getResources---->>>>:"+resources );
//        return resources == null ? super.getResources() : resources;
        return PluginDexManager.getInstance(this).getResource();
    }

    @Override
    public AssetManager getAssets() {
        Log.d("PluginDexManager", "getAssets---->>>>:"+assetManager );
       // return assetManager == null ? super.getAssets() : assetManager;

        return PluginDexManager.getInstance(this).getAssetManager();
    }

    private String pluginPath() {
        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "pluginCompose.apk");
        System.out.println("composePluginDex:" + file.getPath());
        Log.d("PluginDexManager", "pluginPath---->>>>:" + file.getPath());
        if (!file.exists()) {
            System.out.println("composePluginDex 插件不存在");
            return null;
        }
        return file.getAbsolutePath();
    }

    /**
     * 1.找到宿主的dexElements
     * 2.找到插件的dexElements
     * 3.合并插件的dexElements到宿主中去
     */

    public void mergePluginDex() throws Exception {
        String path = pluginPath();
        if (TextUtils.isEmpty(path)) {
            return;
        }
        //1.获取宿主的dexElements
        PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();//获取的就是pathClassLoader
        Class<?> baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");

        Field pathListField = baseDexClassLoader.getDeclaredField("pathList");//pathList的属性
        pathListField.setAccessible(true);
        Object mDexPathList = pathListField.get(pathClassLoader);//获取patList

        Field dexelementField = mDexPathList.getClass().getDeclaredField("dexElements"); //获取它的dexElements
        dexelementField.setAccessible(true);
        Object dexElements = dexelementField.get(mDexPathList);

        //2.获取插件的dexElements
        DexClassLoader dexClzLoaderPlugin = new DexClassLoader(path,
                getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(), null,
                getClassLoader());
        Class<?> baseDexClassLoaderPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListFieldPlugin = baseDexClassLoaderPlugin.getDeclaredField("pathList");
        pathListFieldPlugin.setAccessible(true);
        Object objPathListPlugin = pathListFieldPlugin.get(dexClzLoaderPlugin);//获取patList
        Field dexElementPluginField = objPathListPlugin.getClass().getDeclaredField("dexElements"); //DexPathList中的获取它的dexElements
        dexElementPluginField.setAccessible(true);
        Object dexElementsPlugin = dexElementPluginField.get(objPathListPlugin);

        //3.合并
        //根据类型创建数组
        int mainLen = Array.getLength(dexElements);
        int pluginLen = Array.getLength(dexElementsPlugin);
        int mergeLen = mainLen + pluginLen;
        Object newElements = Array.newInstance(dexElements.getClass().getComponentType(),
                mergeLen);
        for (int i = 0; i < mergeLen; i++) {
            if (i<mainLen) {
                //合并数组
                Array.set(newElements, i, Array.get(dexElements, i));
            } else {
                //合并插件
                Array.set(newElements, i, Array.get(dexElementsPlugin, i - mainLen));
            }
        }
        Log.d("PluginDexManager", "pluginLen---->>>>:" + pluginLen+",mainLen:"+mainLen);


        Field dexelementField2 = mDexPathList.getClass().getDeclaredField("dexElements"); //获取它的dexElements
        dexelementField2.setAccessible(true);
        //设置到宿主中去
        dexelementField2.set(mDexPathList, newElements);

        Log.d("PluginDexManager", "set---->>>>newElements: success" );

    }

    private Resources resources;
    private AssetManager assetManager;

    /**
     * @throws Exception
     */
    public void mergePluginLayout() throws Exception {
        String pluginPath = pluginPath();
        if (TextUtils.isEmpty(pluginPath)) {
            return;
        }
        assetManager=AssetManager.class.newInstance();
        //执行此 public final int addAssetPath(String path)方法,才能把插件的路径添加进去
        Method method = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);//类类型
        method.setAccessible(true);
        method.invoke(assetManager, pluginPath);
        Resources r = getResources();//到的是宿主的配置信息
        //实例化此方法 final StringBlock[] ensureStringBlocks()
        Method ensureStringBlocksMethod = assetManager.getClass().getDeclaredMethod("ensureStringBlocks");
        ensureStringBlocksMethod.setAccessible(true);
        ensureStringBlocksMethod.invoke(assetManager);//执行了ensureStringBlocksstring.xmlcolor.xml anim.xml被
        //特殊:专门加载插件资源
        resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());

    }

插件中使用宿主的application的asset和resource

kotlin 复制代码
open class BaseActivity:AppCompatActivity() {

    //使用宿主合并的的asset
    override fun getAssets(): AssetManager {
        if ((application!=null&&application.assets!=null)) {
            return application.assets
        }
        return super.getAssets()
    }
    //使用宿主合并的的resource
    override fun getResources(): Resources {
        if ((application!=null&&application.resources!=null)) {
            return application.resources
        }
        return super.getResources()
    }
}

四.LoadedApk方式实现插件化

ActivityThread启动Activity流程解析

csharp 复制代码
 case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                        //Activity的跳转记录
                     final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                    //获取LoadedApk
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 }

    //从缓存中取,没有则新建一个
  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) {
                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<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }

通过流程分析,我们可以通过新建一个LoadedApk然后注入到ActivityThread的缓存中去,并且我们可以在此之前通过包名的控制当前使用的LoadedApk来决定运行时使用的LoadedApk,示例代码如下:

ini 复制代码
private class ProxyHandlerCallback implements Handler.Callback {
        private Handler mH;
        public ProxyHandlerCallback(Handler mH) {
            this.mH = mH;
        }

        @Override
        public boolean handleMessage(Message msg) {

            Log.i("HookApp", "handleMessage: " + msg.what);

            if (msg.what == 100) {

                Log.i("HookApp", "---->: " + msg.obj.getClass());
                try {
                    Object obj = msg.obj;
                    //我们要获取之前Hook携带过来的 Proxy
                    Field intentField = obj.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);
                    //获取intent对象,才能取出携带过来的I action Intent
                    Intent intent = (Intent) intentField.get(obj);
                    // actionIntent == TestActivityIntent
                    Intent actionIntent = intent.getParcelableExtra("oldIntent");
                    if (actionIntent != null) {

                        /***
                         *我们在以下代码中,对插件和宿主进行区分
                         */
                        Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");
                        activityInfoField.setAccessible(true);//授权
                        ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);
                        //什么时候加载插件的?
                        if (actionIntent.getPackage() == null) {//证明是插件
                            System.out.println("start plugin --->>>>");
                            activityInfo.applicationInfo.packageName = actionIntent.getComponent().getPackageName();
                            System.out.println("start plugin --->>>>" + actionIntent.getComponent().getPackageName());
                            hookGetPackageInfo();
                        } else {//宿主
                            System.out.println("start home --->>>>");
                            activityInfo.applicationInfo.packageName = actionIntent.getPackage();

                        }
                        //实例化自己的真实的
                        intentField.set(obj, actionIntent);//把ProxyActivity 换成真正的
                        Log.i("HookApp", "---->: SET--->>> ");
                    }
                } catch (Exception e) {
                    e.printStackTrace();

                }
            }
            mH.handleMessage(msg);
            // 让系统继续正常往下执行
            // return false; // 系统就会往下执行
            return true; // 系统不会往下执行

        }
    }


/**
     * 自己创造一个LoadedApk.ClassLoader 添加到 mPackages,此LoadedApk 专门用来加载插件里面的 class
     */
    private void customLoadedApkAction() throws Exception {
        File pluginDirFile = getDir("plugin", Context.MODE_PRIVATE);
        File file = new File( pluginDirFile.getAbsoluteFile() + File.separator + "pluginLoaded.apk");
        if (!file.exists()) {
            throw new FileNotFoundException("插件包不存在..." + file.getAbsolutePath());
        }
        String pulginPath = file.getAbsolutePath();


        // mPackages 添加 自定义的LoadedApk
        // final ArrayMap<String, WeakReference<LoadedApk>> mPackages 添加自定义LoadedApk
        Class mActivityThreadClass = Class.forName("android.app.ActivityThread");


        // 执行此方法 public static ActivityThread currentActivityThread() 拿到 ActivityThread对象
        Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);


        Field mPackagesField = mActivityThreadClass.getDeclaredField("mPackages");
        mPackagesField.setAccessible(true);
        // 拿到mPackages对象
        Object mPackagesObj = mPackagesField.get(mActivityThread);


        Map mPackages = (Map) mPackagesObj;


        // 如何自定义一个 LoadedApk,系统是如何创造LoadedApk的,我们就怎么去创造LoadedApk
        // 执行此 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo)
        Class mCompatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
        Field defaultField = mCompatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
        defaultField.setAccessible(true);
        Object defaultObj = defaultField.get(null);


        /**
         * ApplicationInfo 如何获取, APK解析源码分析
         */
        ApplicationInfo applicationInfo = getApplicationInfoAction();


        Method mLoadedApkMethod = mActivityThreadClass.getMethod("getPackageInfoNoCheck", ApplicationInfo.class, mCompatibilityInfoClass); // 类类型
        // 执行 才能拿到 LoedApk 对象
        Object mLoadedApk = mLoadedApkMethod.invoke(mActivityThread, applicationInfo, defaultObj);


        // 自定义加载器 加载插件
        // String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent


        File fileDir = getDir("pulginPathDir", Context.MODE_PRIVATE);


        // 自定义 加载插件的 ClassLoader
        ClassLoader classLoader = new PluginClassLoader(pulginPath,fileDir.getAbsolutePath(), null, getClassLoader());


        Field mClassLoaderField = mLoadedApk.getClass().getDeclaredField("mClassLoader");
        mClassLoaderField.setAccessible(true);
        mClassLoaderField.set(mLoadedApk, classLoader); // 替换 LoadedApk 里面的 ClassLoader


        // 添加自定义的 LoadedApk 专门加载 插件里面的 class


        // 最终的目标 mPackages.put(插件的包名,插件的LoadedApk);
        WeakReference weakReference = new WeakReference(mLoadedApk); // 放入 自定义的LoadedApk --》 插件的
        mPackages.put(applicationInfo.packageName, weakReference); // 增加了我们自己的LoadedApk
    }


    /**
     * 获取 ApplicationInfo 为插件服务的
     * @return
     * @throws
     */
    private ApplicationInfo getApplicationInfoAction() throws Exception {
        // 执行此public static ApplicationInfo generateApplicationInfo方法,拿到ApplicationInfo
        Class mPackageParserClass = Class.forName("android.content.pm.PackageParser");


        Object mPackageParser = mPackageParserClass.newInstance();


        // generateApplicationInfo方法的类类型
        Class $PackageClass = Class.forName("android.content.pm.PackageParser$Package");
        Class mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");


        Method mApplicationInfoMethod = mPackageParserClass.getMethod("generateApplicationInfo",$PackageClass,
                int.class, mPackageUserStateClass);


        File dirFile = getDir("plugin", Context.MODE_PRIVATE);
        File file = new File(dirFile.getAbsoluteFile() + File.separator + "pluginLoaded.apk");
        String pulginPath = file.getAbsolutePath();


        // 执行此public Package parsePackage(File packageFile, int flags)方法,拿到 Package
        // 获得执行方法的对象
        Method mPackageMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class);
        Object mPackage = mPackageMethod.invoke(mPackageParser, file, PackageManager.GET_ACTIVITIES);


        // 参数 Package p, int flags, PackageUserState state
        ApplicationInfo applicationInfo = (ApplicationInfo)
                mApplicationInfoMethod.invoke(mPackageParser, mPackage, 0, mPackageUserStateClass.newInstance());


        // 获得的 ApplicationInfo 就是插件的 ApplicationInfo
        // 我们这里获取的 ApplicationInfo
        // applicationInfo.publicSourceDir = 插件的路径;
        // applicationInfo.sourceDir = 插件的路径;
        applicationInfo.publicSourceDir = pulginPath;
        applicationInfo.sourceDir = pulginPath;
        return applicationInfo;
    }

    /**
     * 自定义classLoader去加载插件
     */
    static class PluginClassLoader extends DexClassLoader {
        /**
         * @param dexPath
         * @param optimizedDirectory
         * @param librarySearchPath
         * @param parent
         */
        public PluginClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
            super(dexPath, optimizedDirectory, librarySearchPath, parent);

        }
    }


    // Hook 拦截此 getPackageInfo 做自己的逻辑
    private void hookGetPackageInfo() {
        try {
            // sPackageManager 替换  我们自己的动态代理
            Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);


            Field sPackageManagerField = mActivityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            final Object packageManager = sPackageManagerField.get(null);


            /**
             * 动态代理
             */
            Class mIPackageManagerClass = Class.forName("android.content.pm.IPackageManager");


            Object mIPackageManagerProxy = Proxy.newProxyInstance(getClassLoader(),


                    new Class[]{mIPackageManagerClass}, // 要监听的接口


                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if ("getPackageInfo".equals(method.getName())) {
                                // 如何才能绕过 PMS, 欺骗系统
                                // pi != null
                                return new PackageInfo(); // 成功绕过 PMS检测
                            }
                            // 让系统正常继续执行下去
                            return method.invoke(packageManager, args);
                        }
                    });




            // 替换  狸猫换太子   换成我们自己的 动态代理
            sPackageManagerField.set(null, mIPackageManagerProxy);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
相关推荐
彭于晏爱编程2 小时前
关于表单,别做工具库舔狗
前端·javascript·面试
风中凌乱的L2 小时前
vue canvas标注
前端·vue.js·canvas
拉不动的猪2 小时前
什么是二义性,实际项目中又有哪些应用
前端·javascript·面试
海云前端12 小时前
Webpack打包提速95%实战:从20秒到1.5秒的优化技巧
前端
烟袅2 小时前
LeetCode 142:环形链表 II —— 快慢指针定位环的起点(JavaScript)
前端·javascript·算法
梦6502 小时前
什么是react?
前端·react.js·前端框架
zhougl9962 小时前
cookie、session、token、JWT(JSON Web Token)
前端·json
Ryan今天学习了吗2 小时前
💥不说废话,带你上手使用 qiankun 微前端并深入理解原理!
前端·javascript·架构
高端章鱼哥2 小时前
前端新人最怕的“居中问题”,八种CSS实现居中的方法一次搞懂!
前端