Principio del ④-plug-in de descifrado avanzado de Android

Antes de aprender a utilizar complementos, debe leer los artículos anteriores:

Tecnología de carga dinámica:

Cuando el programa se está ejecutando, algunos archivos ejecutables que no existen en el programa se cargan y ejecutan dinámicamente Con el desarrollo de la tecnología de aplicaciones, la tecnología de carga dinámica deriva gradualmente de dos ramas, hotfix y plug-in;

  • Corrección urgente: se utiliza para corregir errores
  • Complemento: resuelve la gran aplicación, desacopla los módulos funcionales y reutiliza el código de otro apk
Ideas de complementos:

Usa el apk reutilizado como un complemento e insértalo en otro apk. Por ejemplo, habrá una página de pescado salado en Taobao y usará Taobao para drenar el pescado salado. Con la tecnología de complemento, puedes usar directamente el archivo dex en el apk de pescado salado, que lo guarda El costo de desarrollar un conjunto de páginas de pescado salado nuevamente y reducir efectivamente el grado de acoplamiento de la apk de Taobao;

Principio del complemento de actividad:

El propósito de la actividad del complemento es usar directamente la actividad de otra apk, y AMS debe procesar el inicio y la gestión del ciclo de vida de la actividad. La actividad de la otra apk no está registrada en el manifiesto de este proyecto y no debe pasarse, por lo que Debes conectar el proceso startActivity para omitir la verificación de ams. Puedes usar una actividad en boxes en este proyecto. Antes de enviar a ams, reemplaza la actividad del complemento con la actividad en boxes para pasar la verificación de ams. Una vez completada la verificación, se repetirá el inicio real Reemplace la actividad del complemento de nuevo;

paso:
  • Prepare la actividad en este proyecto con anticipación
  • Utilice la actividad en boxes para evitar la verificación de ams
  • Restaurar la actividad del complemento

1. Prepárate para ocupar la actividad

Simplemente prepare una actividad en blanco directamente en el proyecto original, recuerde registrarse en el manifiesto, llámelo SubActivity a continuación

2. Utilice la actividad complementaria para reemplazar la actividad en boxes

Antes de pasar al proceso ams para su verificación, el proceso del usuario pasará por dos clases, Instrumentación e iActivityManager. Ambas clases se pueden utilizar como puntos de enlace. Aquí hay un método para conectar iActivityManager;

2.1 Crear una clase de proxy para puntos de enlace, iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
    
    

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
    
    
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("startActivity".equals(method.getName())){
    
    
            //  首先找到,原本需要启动的插件activity的原始intent
            Intent originIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
    
    
                if (args[i] instanceof Intent){
    
    
                    originIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            //  新建欺骗ams的占坑activity的intent
            Intent fakeIntent = new Intent();
            fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
            //  将真实的intent保存在fakeIntent中用于第三步的还原操作
            fakeIntent.putExtra("real_intent",originIntent);
            //  将fakeIntent写回原来的arges数组中
            args[index] = fakeIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}

El proxy dinámico que se utiliza aquí crea el proxy de iActivityManager. Primero, busque el Intent de la actividad del complemento que se inició originalmente y luego cree un nuevo intent que inicie SubActivity para reemplazarlo;

2.2 Reemplace el iActivityManager original:
    public void hookAMS() throws Exception {
    
    
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }
  • Este método debe llamarse antes de startActivity

3. Restaurar la actividad del complemento

Después de omitir la verificación de ams, también necesitamos iniciar TargetActivity, y después de aprender el mecanismo del controlador, sabemos que el orden de procesamiento del mensaje es juzgar primero si el message.callback actual tiene lógica y ejecutar primero la devolución de llamada; podemos usar Message como un gancho punto

3.1 Cree un CallBack personalizado y reemplace fakeIntent con un intent real antes de que se procese handleMessage
  class MCallBack implements android.os.Handler.Callback {
    
    
        @Override
        public boolean handleMessage(Message msg) {
    
    
            try {
    
    
                Object activityClientRecord = msg.obj;
                // 获取fakeIntent
                Class acrClazz = activityClientRecord.getClass();
                Field intentField = acrClazz.getDeclaredField("intent");
                Intent intent = (Intent) intentField.get(activityClientRecord);
                // 取出targetActivity的Intent
                Intent realIntent = intent.getParcelableExtra("real_intent");
                // 将realIntent的内容设置到fakeIntent
                intent.setComponent(realIntent.getComponent());
                
            } catch (NoSuchFieldException e) {
    
    
                e.printStackTrace();
            } catch (IllegalAccessException e) {
    
    
                e.printStackTrace();
            }
            msg.getTarget().handleMessage(msg);
            return true;
        }
    }
3.2 gancho ActivityThread, modifica el atributo CallBack de H (Handler) del hilo principal,原理参考dispatchMessage方法
    private void hookActivityThread() throws Exception {
    
    
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        Object activityThreadInstance = singleInstanceField.get(null);
        
        Field mHField = activityThreadClass.getDeclaredField("mH");
        Handler handler = (Handler) mHField.get(activityThreadInstance);
        
        // 修改handler 的callback
        Class handlerClazz = handler.getClass();
        Field callbackField = handlerClazz.getDeclaredField("mCallback");
        callbackField.set(handler,new MCallBack());
    }

Hay dos devoluciones de llamada en el mecanismo Handler, una es Handler.mCallback y la otra es Message.callback

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

Cuando se sondea el mensaje en el bucle, se llamará dispatchMessage; si Message.callback no es nulo, la devolución de llamada Runnable se procesará y luego finalizará. Si msg.callback es nula, la mCallback del Handler se ejecutará primero, y de acuerdo con mCallback.handleMessage del Handler. El valor de retorno determina si ejecutar Handler.handleMessage;
根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity

3.3 Gestión del ciclo de vida de la actividad del complemento:

La operación anterior solo habilita la actividad, cómo administrar el ciclo de vida de la actividad del complemento, AMS usa el token para identificar y administrar la actividad, y el enlace del token de actividad del complemento no se ve afectado, por lo que la actividad del complemento tiene un ciclo de vida ;

Principio del complemento de servicio

Implementación de distribución de agentes:

Cuando se inicia el servicio de complemento, el servicio de proxy se iniciará primero y, cuando se ejecuta el servicio de proxy, el servicio de complemento se inicia en su onStartCommand;

paso:
  • El servicio del agente está listo en el proyecto
  • hook iActivityManager inicia el servicio proxy
  • Distribución de agentes:
  1. ProxyService tarda mucho en distribuir el servicio de complemento, por lo que debe devolver START_STICKY ProxyService para volver a crear
  2. Crear servicio de complemento, adjuntar, onCreate;

1. Cree un ProxyService en el proyecto y regístrelo en el manifiesto;

2. Enganche iActivityManager, reemplace TargetService para que se inicie con ProxyService

2.1 Crear un iActivityManagerProxy personalizado
public class IActivityManagerProxy implements InvocationHandler {
    
    

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
    
    
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("startService".equals(method.getName())){
    
    
            Intent targetIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
    
    
                if (args[i] instanceof Intent){
    
    
                    targetIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            Intent proxyIntent = new Intent();
            proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
            proxyIntent.putExtra("target_intent",targetIntent);
            args[index] = proxyIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}
2.2 hook AMS reemplaza el IActivityManager original 同上
 public void hookAMS() throws Exception {
    
    
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }

Siempre que se llame a este código antes de startService, se iniciará el proxyService A continuación, distribuiremos targetService en el proxyService;

2.3 Inicie TargetService en proxyService:
  • Llamar adjuntar para enlazar contexto
  • Llamar a Crear
public class ProxyService extends Service {
    
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    
    
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        try {
    
    
            // 准备attach方法的参数
            Class activityThreadClazz = null;
            activityThreadClazz = Class.forName("android.app.ActivityThread");
            Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
            Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            // activityThread
            Object activityThread = sCurrentActivityThreadField.get(null);
            // applicationThread
            Object applicationThread = getApplicationMethod.invoke(activityThread, null);
            Class iInterFaceClazz = Class.forName("android.os.IInterface");
            Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
            asBinderMethod.setAccessible(true);
            // token
            Object token = asBinderMethod.invoke(applicationThread);
            // iActivityManager
            Class ActivityManagerClazz = ActivityManager.class;
            Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
            Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
            Class singleClazz = IActivityManagerSingleton.getClass();
            Method getMethod = singleClazz.getDeclaredMethod("get");
            Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);

            // targetService
            Class serviceClazz = Class.forName("android.app.Service");
            Service targetService = (Service) serviceClazz.newInstance();

            // attach
            Method attachMethod = serviceClazz.getDeclaredMethod("attach");
            attachMethod.invoke(targetService, this,
                    activityThread, intent.getComponent().getClassName(),
                    token, getApplication(), iActivityManager);
            targetService.onCreate();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return START_STICKY;
    }
}

  • Primero prepare los parámetros necesarios para adjuntar y obtengalos mediante reflexión
  • Llame al método adjunto de targetService
  • Llame al método onCreate de targetService

Pluginización de recursos

Consulte el artículo de skinning aquí: Principios de skinning de Android

Práctica de plug-in de algunos Dong

1. Qué hace el complemento:

El propósito del complemento es utilizar el proyecto del complemento 代码/类y el proyecto principal资源

1.1 El procesamiento de los cuatro componentes principales del complemento:

Cuando se utilizan las cuatro clases de componentes principales de complementos, como el uso de actividades de complementos, se debe realizar un tratamiento especial. Los métodos de tratamiento generales son:

  • enganche AMS, omita la verificación AMS de los cuatro componentes principales, gestione manualmente el ciclo de vida
  • Registre la actividad del complemento directamente en el manifiesto del proyecto principal

El método 1 se caracteriza por una alta dificultad de implementación y una gran flexibilidad, pero con las restricciones de Google sobre las llamadas al sistema que no son sdk, este método puede fallar en el futuro; el
método 2 se caracteriza por una implementación simple, pero no lo suficientemente flexible, y debe estar escrito en el manifiesto. muerto

某东采用的方式2,直接在manifest中写死插件的四大组件注册

1.2 La carga y uso de clases en el complemento:

Cada plug-in se configura con un ClassLoader, el propósito es que toda la clase del plug-in sea cargada por un cargador, y el ClassLoader de todos los plug-ins herede otro ClassLoader en la delegación principal. Este propósito es facilitar la gestión unificada del proyecto principal;

1.3 Reemplazo de DelegateClassLoader:

Reemplace el ClassLoader de LoadedAPK con DelegateClassLoader;

1.4 Referencia a recursos de complementos

Simplemente agregue una ruta a AssetManager, consulte más arriba para obtener más detalles

2. Cómo empaquetar el complemento en el proyecto principal, es decir, cómo integrar el paquete del complemento en el proyecto principal:

  • Coloque el apk del complemento en el directorio de activos y cárguelo a través de assetManager
  • Coloque el sufijo modificado del archivo apk del complemento en .so en el directorio lib / armeabi, y el apk del proyecto principal cargará automáticamente los archivos de este directorio en el directorio data / data / <nombre_paquete> / lib / al instalarlo, y podrá obtenerlo directamente;

某东使用的第二种以so的形式放入lib目录自动加载,因为在运行时去使用AssetManager加载asset资源会影响程序的运行时速度

3. Cómo comunicarse entre el complemento y el proyecto principal

El DeepLinkDispatch de airbnb se tomó principalmente de

DeepLinkDispatch es similar al protocolo de esquema nativo de Android, que se usa para saltar a otra página de la aplicación, como abrir el mapa de Gaode en Meituan o abrir JD en WeChat,

La idea de realización de DeepLinkDispatch: primero agregue una anotación a la Actividad que debe abrirse en el proyecto del complemento, luego llame a los DeepLinkDispatch.startActivityDirect()parámetros establecidos en la anotación de entrada en el proyecto principal y finalmente Context.startActivity()abra la página a través de la api del sistema

4. El futuro de los complementos

Con las restricciones cada vez más estrictas de Google sobre las API del sistema, y ​​ahora se ha dividido en listas negras, listas de color gris oscuro y listas de color gris claro para dar tiempo a que los desarrolladores se ajusten, el complemento no debería tener futuro. Pensemos para qué es el complemento. :

  • Compilación independiente para mejorar la eficiencia del desarrollo
  • Desacoplamiento de módulos, reutilización de código
Solución de Dongdong:

组件化:
El componente tradicional es crear una nueva biblioteca de Android, cambiar entre la aplicación y la biblioteca durante el desarrollo, la depuración y la referencia real. La componentización de Dongdong es hacer que cada componente sea un proyecto separado y luego retener la aplicación y la biblioteca en la estructura del proyecto. Android Library, la biblioteca se usa para implementar funciones de componentes, la aplicación se usa para desarrollo y depuración, y cuando se usa el proyecto principal, depende directamente de la sincronización de Maven desde la nube;

Supongo que te gusta

Origin blog.csdn.net/weixin_46824291/article/details/109648854
Recomendado
Clasificación