Carga dinámica de APK de complementos

Conceptos relacionados con los complementos:

Entender el concepto de acuerdo a la diferencia entre componenteización y plug-in

La diferencia entre la creación de componentes y el complemento

Componentización: es dividir una aplicación en varios módulos, y cada módulo es un módulo. Durante el proceso de desarrollo, podemos hacer que estos componentes dependan entre sí o depurar algunos componentes por separado, pero cuando se publique la versión final, estos componentes se fusionarán. en un APK unificado.

Complemento: dividiendo la aplicación completa en muchos módulos, cada módulo es un APK (cada módulo de componentes es una biblioteca), y el APK del host y el APK del complemento se empaquetan por separado cuando finalmente se empaquetan, y el APK del complemento es descargado dinámicamente Enviar al host APK .

Ventajas del complemento

  • Reduzca el tamaño del APK instalado y descargue módulos bajo demanda
  • Actualizar complementos dinámicamente
  • El host y los complementos se compilan por separado para mejorar la eficiencia del desarrollo del equipo
  • El número de solución supera los 65535 problemas

Comparación de marcos de complementos 

Idea de implementación de complemento 

 El uso de complementos inevitablemente tendrá apk de host y apk de complemento. Si desea usar elementos de complemento en el host, se enfrentará a los siguientes tres problemas

  • Cómo cargar recursos
  • Cómo cargar clases dinámicamente
  • Cómo iniciar el componente

¿Cómo cargar los recursos del complemento? (Carga dinámica de res)

Cuando el apk del complemento se carga dinámicamente, no seguirá el proceso normal de inicialización de la aplicación y los archivos de recursos no se cargarán en los recursos del apk del host y deberán cargarse manualmente.

Implemente un recurso para obtener los recursos del complemento. Antes de crear el recurso, primero creamos una instancia de un objeto AssetManager, luego llamamos al método addAssetPath a través de la reflexión, establecemos la dirección de nuestro apk del complemento y, finalmente, pasamos Resources (AssetManager assets , DisplayMetrics metrics, Configuration config) para crear un nuevo recurso (el recurso solo contiene el recurso res en el apk del complemento)

/**
     * 加载资源
     *
     * @return
     */
    public Resources loadResources(Context context) throws Exception {
        AssetManager pluginAssets = AssetManager.class.newInstance();
        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        String apkPath = context.getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk";
        addAssetPathMethod.invoke(pluginAssets, apkPath);
        addAssetPathMethod.setAccessible(true);
        Resources pluginRes = new Resources(pluginAssets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        return pluginRes;
    }

Unifique la entrada de getResource, la aplicación hereda ContextWrapper, ContextWrapper tiene el método getResources, necesitamos reescribir

public class MyApplication extends Application {
    private Resources resources;

    @Override
    public void onCreate() {
        super.onCreate();
        PluginManager pluginManager = PluginManager.getInstance(this);
        try {
            this.resources = pluginManager.loadResources();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    @Override
    public Resources getResources() {
        // 插件资源不为空就用插件的,否则用默认的
       return resources == null ? super.getResources(): resources;
    }
}

De manera similar, para la actividad, debe escribir una clase base para anular getResources y obtenerla de la aplicación de manera uniforme.

Cómo cargar clases dinámicamente

Esto implica la carga dinámica de clases. Los cargadores de clases comúnmente utilizados son:

  • BootClassLoader: Se utiliza para cargar las clases comunes del sistema cuando se inicia el sistema, y ​​la clase interna de ClassLoader.

  • PathClassLoader: Carga clases de sistema y clases de aplicación, generalmente no recomendado para desarrolladores.

  • DexClassLoader: cargue archivos dex y apk o jar que contengan archivos dex. También se admite la carga desde la tarjeta SD, que también

    Significa que DexClassLoader no puede cargar archivos relacionados con dex cuando la aplicación no está instalada. Por lo tanto, es revisión

    La base de la tecnología compleja y enchufable.

Mencione brevemente el mecanismo de delegación principal de la carga de clases.

La delegación parental no es una relación padre-subclase en el nivel de herencia de Java. Por ejemplo, las clases principales de PsathClassLoader y DexClassLoader son ambas BaseDexClassLoader y son hermanas.

Ventajas del Mecanismo de Delegación Parental

  • Evite la carga repetida, si se carga, lea directamente desde el caché.
  • Es más seguro y evita que los desarrolladores modifiquen las clases del sistema.

Para contenido específico relacionado con el cargador de clases, vaya a la Guía de desarrollo de complementos de Android - Class Loader_Helan Pig's Blog-CSDN Blog 

Puede saber que el DexClassLoader (String dexPath, String OptimizedDirectory, String librarySearchPath, ClassLoader parent) que se usará aquí es
* dexPath simplemente complete la ubicación del archivo que contiene dex (directorio en la aplicación)
* OptimizedDirectory Esta es la ruta donde se almacenará el caché sobrevivir después de que se cargue el dex. La máquina virtual Dalvik utilizada debajo de Android 5.0 generará un caché de "filename.dex" en el directorio optimizado, mientras que Android 5.0 y superior generará avena en el directorio del mismo nivel que el apk del parche porque usa el tiempo de ejecución de Art y genere "filename.apk.cur.prof para almacenar el caché
* librarySearchPath c, biblioteca c++, en la mayoría de los casos null es suficiente
* parent El cargador principal del cargador, generalmente el cargador de la clase de ejecución actual.

//加载dex
        File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
        //dex -> odex缓存路径
        File odexPath = this.getDir("cache_plugin", Context.MODE_PRIVATE);
        //使用类加载器加载dex
        DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), odexPath.getAbsolutePath(), null, getClassLoader());
        try {
            Class<?> aClass = dexClassLoader.loadClass("com.example.myapplication.plugin.ToastUtils");
            Method showInfo = aClass.getMethod("showInfo", Context.class);
            showInfo.setAccessible(true);
            showInfo.invoke(aClass.newInstance(), PluginActivity.this);
        } catch (Exception e) {
            e.printStackTrace();
        }

Necesita agregar permisos de memoria de lectura y escritura. 

Sin embargo, aunque el método anterior puede cargar complementos externos, el objeto DexClassLoader debe obtenerse cada vez que se llama. Aunque podemos escribirlo como un modo singleton, desde la perspectiva de la escritura de código, es problemático obtener el DexClassLoader del dex externo cada vez. Y si hay muchos complementos de dex o apk externos en una aplicación, ¿los programadores deben recordar qué clases hay en cada dex? Esto es obviamente poco realista. Así que hay que hacerlo de otra manera.

------------------- Lazy, lo siguiente se transfiere desde, después de ver esto, puede ir directamente a la guía de desarrollo de complementos de Android de Blogger - Tecnología Hook (1) [Texto largo]_Desarrollo de complemento de enlace de Android_Blog de sueños-Blog de CSDN

Cargue el dex externo en los dexElements de la aplicación host

Para saber Appdónde se carga la clase en el host, debemos comenzar desde el cargador de clases. Aquí OtherActivityse explica getClassLoader()cómo comenzar a rastrear. Como se muestra abajo:

inserte la descripción de la imagen aquí

El contexto aquí es una clase abstracta, y el método getClassLoader es un método abstracto, por lo que necesitamos encontrar su clase de implementación ContextImpl. Su código fuente se puede ver en el enlace: ContextImpl.java.

// ContextImpl
final LoadedApk mPackageInfo;
@Override
public ClassLoader getClassLoader() {
    return mPackageInfo != null ?
        mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}

Por lo tanto, debe comprender dónde se asigna la variable mPackageInfo:

// ContextImpl
static ContextImpl createSystemContext(ActivityThread mainThread) {
    LoadedApk packageInfo = new LoadedApk(mainThread);
    ContextImpl context = new ContextImpl(null, mainThread,
            packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
    context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
            context.mResourcesManager.getDisplayMetrics());
    return context;
}

En otras palabras, este LoadedApk está vinculado al hilo principal. Aquí continúe mirando el método getClassLoader:

// LoadedApk.java
public ClassLoader getClassLoader(){
    synchronized (this) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null);
        }
        return mClassLoader;
    }
}

En cuanto al método createOrUpdateClassLoaderLocked, ClassLoader.getSystemClassLoader() en realidad se usa para obtener un cargador de clases.
Este método eventualmente llamará al método ClassLoader.createSystemLoader(), que es el siguiente:

private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

En otras palabras, lo que realmente se devuelve es un cargador de clases PathClassLoader. Es decir, después de llamar al método getClassLoader() en el archivo ContextImpl, lo que se devuelve es un cargador de clases PathClassLoader. Busque el archivo fuente de PathClassLoader.java: PathClassLoader.java. De un vistazo, las funciones de esta clase provienen básicamente de su clase padre BaseDexClassLoader, porque tiene solo dos métodos de construcción. Así que aquí puede ver el archivo fuente de BaseDexClassLoader.java.

El código de este archivo también es relativamente simple, de la siguiente manera:

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
    ...
}

Se puede ver en el código anterior que la búsqueda en realidad se busca desde su campo de atributo DexPathList. Es decir, en realidad es necesario seguir buscando el código fuente de esta clase aquí, porque hay muchos códigos en la clase DexPathList.java, por lo que aquí solo veremos el método pathList.findClass.

// DexPathList
public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

Observando el código anterior, podemos saber que cuando se ejecuta findClass, en realidad se busca en dexElements. Y este dexElements se define directamente como una matriz:

elemento privado [] dexElements;

Entonces, si podemos agregar los dexElements en el archivo dex o apk del complemento externo a los dexElements de la aplicación host, podemos completar la expectativa.

Es posible que desee expresar la lógica anterior con un diagrama de secuencia:

Luego, se puede escribir una clase de herramienta correspondiente para completar los pasos anteriores. el código se muestra a continuación:

public class LoadUtils {

    private static String pluginPath = "/sdcard/plugin-debug.apk";

    public static void init(Context context) {
        if(context == null) return;
        try {
            // 获取应用程序App的dexElements
            PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
            Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field dexPathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
            dexPathListField.setAccessible(true);
            Object dexPathListValue = dexPathListField.get(classLoader);
            Field dexElementsField = dexPathListValue.getClass().getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);
            Object dexElementsValue = dexElementsField.get(dexPathListValue);

            // 获取外部插件的dexElements
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
                    context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
                    null, context.getClassLoader());

            Object pluginDexPathListValue = dexPathListField.get(dexClassLoader);
            Object pluginDexElementsValue = dexElementsField.get(pluginDexPathListValue);

            // 合并两个dexElements
            int appDexElementsLength = Array.getLength(dexElementsValue);
            int pluginDexElementsLength = Array.getLength(pluginDexElementsValue);
            int newLength = appDexElementsLength + pluginDexElementsLength;

            Class<?> componentType = dexElementsValue.getClass().getComponentType();
            Object newArray = Array.newInstance(componentType, newLength);
            System.arraycopy(dexElementsValue, 0, newArray, 0, appDexElementsLength);
            System.arraycopy(pluginDexElementsValue, 0, newArray, appDexElementsLength, pluginDexElementsLength);

            // 设置新的内容到app的PathList中的Elements[]
            dexElementsField.set(dexPathListValue, newArray);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

Entonces el método de carga correspondiente es:

public class OtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        LoadUtils.init(this);

        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.ToastUtils");
            Method showInfo = aClass.getMethod("showInfo", Context.class);
            showInfo.setAccessible(true);
            showInfo.invoke(aClass.newInstance(), OtherActivity.this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Puede usar directamente el PathClassLoader obtenido en el contexto para la reflexión.

Cómo iniciar el componente

Al cargar las clases en el apk externo anterior, lo que cargamos es solo una clase ordinaria. En Android, los cuatro componentes principales tienen ciertas particularidades, ya que todos deben registrarse en el archivo de manifiesto. Para la actividad o el servicio en el complemento externo, es imposible registrarse en la aplicación host, por lo que necesitamos una forma de omitir la verificación de la información de configuración en el archivo de manifiesto cuando se carga el sistema.

Hook startActivity

Primero debe averiguar qué sucedió después de startActivity (intención). Entonces, lo primero que debe comprender es que AMS (ActivityManagerService) literalmente significa el servicio de administración de Actividad, pero de hecho, los cuatro componentes principales están bajo su administración.

AMS es principalmente responsable de la puesta en marcha, conmutación, programación de los cuatro componentes principales del sistema y la gestión y programación de los procesos de aplicación. Sus responsabilidades son similares a las de los módulos de gestión y programación de procesos en el sistema operativo. Cuando se inicia el proceso de inicio o se inicia el componente, la solicitud se pasará a AMS a través del mecanismo de comunicación de Binder y AMS realizará un procesamiento unificado.

Dado que AMS administra los cuatro componentes principales, ¿por qué no conectar directamente las funciones deseadas en la capa de AMS? Porque si Hook se puede realizar en la capa AMS, obviamente es un programa de virus, por lo que no está permitido hacerlo en Android. Por lo tanto, nuestros puntos de enlace solo pueden estar en los cuatro componentes principales, porque al menos debemos asegurarnos de que solo el programa actual se vea afectado y que otros programas no se vean afectados.

Aquí hay un ejemplo de ActivityA iniciando ActivityB:

  • ActivityA envía un mensaje a AMS para iniciar ActivityB;
  • AMS guarda la información de ActivityB y, al mismo tiempo, AMS verifica si ActivityB está registrado en el archivo de manifiesto y continúa con los siguientes pasos si está registrado;
  • ActivityA duerme, AMS notifica a ActivityThread para iniciar ActivityB;

从startActivity(intent);Inicio, puede ver el siguiente proceso de llamada: (¡El código fuente aquí es la versión de api 25, que es 7.1!)

inserte la descripción de la imagen aquí
Es decir, a través del método startActivity(intent);que eventualmente solicitará Activityla clase startActivityForResult, se pueden ver dos variables miembro en el método:

mInstrumentation  // Instrumentation
mMainThread   // ActivityThread

// 对应逻辑摘要
Instrumentation.ActivityResult ar =
    mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
    if (ar != null) {
mMainThread.sendActivityResult(
        mToken, mEmbeddedID, requestCode, ar.getResultCode(),
        ar.getResultData());
}

 Así que primero mire los métodos Instrumentationen esta clase execStartActivity.

// Instrumentation
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

 En el método execStartActivity, la actividad de destino se iniciará a través del método ActivityManagerNative.getDefault().startActivity(). Y ActivityManagerNative.getDefault() finalmente devuelve un objeto IActivityManager, que a menudo se denomina objeto AMS. Si desea continuar para ver cómo se obtuvo este AMS, continuamos rastreando:

// ActivityManagerNative
static public IActivityManager getDefault() {
	return gDefault.get();
}

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        if (false) {
            Log.v("ActivityManager", "default service binder = " + b);
        }
        IActivityManager am = asInterface(b);
        if (false) {
            Log.v("ActivityManager", "default service = " + am);
        }
        return am;
    }
};

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }

    return new ActivityManagerProxy(obj);
}
Y para la clase genérica singleton Singleton aquí:
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;
        }
    }
}

Del código anterior, podemos saber que AMSla definición aquí es un objeto singleton, este singleton se Singleton<T>modifica con el anterior, y el método de creación real se newespecifica por el tiempo. Y gDefaultdurante la creación de , IBindery se utilizan ActivityManagerProxypara convertir.

Obtener el objeto de la instancia de AMS

Este objeto singleton gDefault se puede obtener a través de la reflexión. Luego, obtenga la mInstance definida como genérica, es decir, el objeto AMS. Luego puede interceptar el método startActivity llamado por AMS a través de un proxy dinámico. Entonces aquí puede simplemente obtener el objeto AMS a través de la reflexión. Dado que AMS se obtiene a través de getDefault() en el archivo ActivityManagerNative.java, aquí está:
 

public class HookAMSUtils {

    public static void getActivityManagerService() {
        try {
            Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
            Field getDefault = aClass.getDeclaredField("gDefault");
            getDefault.setAccessible(true);

            // 获取静态的gDefault对象
            Object getDefaultObj = getDefault.get(null);
            // 而实际上AMS在单例Singleton中
            Class<?> singletonClazz = Class.forName("android.util.Singleton");
            Field mInstance = singletonClazz.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            Object amsObj = mInstance.get(getDefaultObj); // AMS
            Log.e("TAG", "getActivityManagerService: " + amsObj.toString());

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

AMSEs decir, el singleton  contenido en el Hook puede usarse para obtener AMSel objeto.

El diagrama de tiempo del proceso analizado anteriormente se puede expresar como:

inserte la descripción de la imagen aquí Hook startActivity

Después del análisis lógico anterior, sabemos que cuando uno Activityinicia el otro , eventualmente se alcanzará el método Activityde acuerdo con una serie de llamadas . Aquí pegamos el código correspondiente nuevamente:AMSstartActivity

// Instrumentation
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

Según el tipo de valor de retorno de getDefault() en ActivityManagerNative, podemos saber fácilmente que el objeto AMS obtenido anteriormente es un objeto de interfaz IActivityManager.

Y si necesitamos hacer trampa en AMS, es decir, omitir la verificación de registro de los cuatro componentes principales en el archivo de manifiesto. Aquí debe usar el modo de proxy dinámico y luego interceptar el método startActivity de AMS.

Crear objeto proxy AMS

 Aquí está la interfaz de proxy IActivityManager.java. Entonces, usar un proxy dinámico obtendrá muchos métodos al invocar, y aquí solo necesitamos startActivity, que se define como:

// IActivityManager.java
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
         String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
         ProfilerInfo profilerInfo, Bundle options) throws RemoteException;

Entonces podemos interceptar este método startActivity en el método de proxy dinámico. Para lograr el propósito de omitir la verificación del archivo de manifiesto, podemos registrar una actividad de proxy en el archivo de manifiesto por adelantado y luego reemplazar el objeto de actividad en el método startActivity interceptado. Por ejemplo el siguiente código:

public class HookAMSUtils {

    public static final String ORIGIN_INTENT = "ORIGIN_INTENT";

    public static void getActivityManagerService(Context context, Class<? extends Activity> proxyActivityClazz) {
        try {
            Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
            Field getDefault = aClass.getDeclaredField("gDefault");
            getDefault.setAccessible(true);

            // 获取静态的gDefault对象
            Object getDefaultObj = getDefault.get(null);
            // 而实际上AMS在单例Singleton中
            Class<?> singletonClazz = Class.forName("android.util.Singleton");
            Field mInstance = singletonClazz.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            Object amsObj = mInstance.get(getDefaultObj); // AMS
            Log.e("TAG", "getActivityManagerService: " + amsObj.toString());

            // 创建AMS的代理对象
            Class<?> aClass1 = Class.forName("android.app.IActivityManager");
            // 得到AMS的代理对象
            Object amsProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{aClass1}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 代理方法处理
                    Log.e("TAG", "invoke: startActivity");
                    if (method.getName().equals("startActivity")) {
                        // 查找参数,找到Intent对象
                        int index = 0;
                        for (int i = 0; i < args.length; i++) {
                            if (args[i] instanceof Intent) {
                                index = i;
                                break;
                            }
                        }
                        // 拿到意图
                        Intent oldIntent = (Intent) args[index];
                        String name = oldIntent.getStringExtra("NAME");
                        Log.e("TAG", "invoke: " + name);
                        // 创建一个新的意图,将这个旧的意图添加到新的意图中
                        Intent newIntent = new Intent(context, proxyActivityClazz);
                        // 将旧的意图放入到新的意图中
                        newIntent.putExtra(ORIGIN_INTENT, oldIntent);
                        // 设置startActivity的意图对象为新的意图
                        args[index] = newIntent;
                    }
                    return method.invoke(amsObj, args);
                }
            });
            // 将AMS代理对象设置为原本的AMS对象,
            // 也就是设置ActivityManagerNative.java中属性字段gDefault的值为代理对象
            mInstance.set(getDefaultObj, amsProxy);

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

Por supuesto, debe crear una clase ProxyActivity registrada en el archivo de manifiesto. Luego prueba en MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LoadUtils.init(this);
        HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
            Log.e("TAG", "onCreate: " + aClass.getName());
            Intent intent = new Intent(MainActivity.this, aClass);
            intent.putExtra("NAME", "123");
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

resultado:

inserte la descripción de la imagen aquí
Es decir, el salto aquí es reemplazado por el objeto Actividad del proxy. Por lo tanto, todavía tenemos que reemplazar el objetivo original com.weizu.plugin.MainActivity en alguna parte. Por supuesto, reemplazar la actividad del proxy aquí con la MainActivity original debe realizarse en ActivityThread. El diagrama de tiempo de este proceso se puede expresar como:

inserte la descripción de la imagen aquí
De la figura anterior, podemos saber que Hanlder se usa en ActivityThread para enviar mensajes. Entonces podemos manejar la interfaz de devolución de llamada del controlador para reemplazar la actividad. Por lo tanto, el primer paso es obtener el objeto de instancia de ActivityThread y luego establecer el método de procesamiento de mensajes como nuestro propio método. ActivityThread define una referencia estática propia, por lo que es relativamente fácil obtener el objeto. El código correspondiente es:

 

public static void hookActivityThreadToLaunchActivity(){
    try {
        // 得到ActivityThread的对象
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object activityThreadValue = sCurrentActivityThreadField.get(null);

        // 找到Handler,即mH
        Field mHField = activityThreadClazz.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mHValue = mHField.get(activityThreadValue);

        // 重新赋值
        Class<?> handlerClazz = Class.forName("android.os.Handler");
        Field mCallBackField = handlerClazz.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);
        mCallBackField.set(mHValue, new HandlerCallBack());

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

// 因为在ActivityThread中通过Handler来接受消息,
// 所以这里为了替换,就实现其回调接口

private static class HandlerCallBack implements Handler.Callback{

    @Override
    public boolean handleMessage(Message message) {
        // 处理消息
        if(message.what == 100) { // H.LAUNCH_ACTIVITY
            handleLaunchActivity(message);
        }

        return false;
    }

    private void handleLaunchActivity(Message message) {
        try {
            // 得到ActivityClientRecord r对象
            Object r = message.obj;
            // 而在得到ActivityClientRecord中就存储着传进来的Intent意图对象
            // 所以可以先获取到意图,然后修改意图对象
            Field intentField = r.getClass().getDeclaredField("intent");
            // 取出intent的值
            intentField.setAccessible(true);
            Intent newIntent = (Intent) intentField.get(r);
            // 从这个newIntent得到真正的意图
            Intent oldIntent = newIntent.getParcelableExtra(ORIGIN_INTENT);
            Log.e("TAG", "handleLaunchActivity: " + oldIntent.toString());
            if(oldIntent != null){
                // 设置r中的intent为当前的这个oldIntent
                intentField.set(r, oldIntent);
            }

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

Luego use al llamar:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LoadUtils.init(this);
        HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
        HookAMSUtils.hookActivityThreadToLaunchActivity();
    }

    // onClick
    public void jump(View view){
        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
            Log.e("TAG", "onCreate: " + aClass.getName());
            Intent intent = new Intent(MainActivity.this, aClass);
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Puede hacer clic en el cuadro de texto y luego saltar.

Supongo que te gusta

Origin blog.csdn.net/u013773608/article/details/130184857
Recomendado
Clasificación