El host inicia una Actividad/Servicio que no está declarado en AndroidManifest

Iniciar una actividad de complemento que no está declarada en AndroidManifest

En el artículo anterior, ya podemos iniciar una Actividad en un complemento en el host, pero la Actividad debe declararse en el archivo AndroidManifest del host; de lo contrario, se generará una excepción ActivityNotFound.
Lo que debemos hacer es engañar al superior y al inferior, es decir, en el método startActivity de AMS, usar el StubActivity declarado en el archivo AndroidManifest.xml de la aplicación host para reemplazar temporalmente la Actividad del complemento que realmente está en se iniciará y luego usará el controlador de ActivityThread. En ese caso, reemplace StubActivity nuevamente con la actividad del complemento.

public class AMSHookHelper {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";

    /**
     * Hook AMS
     * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
     */
    public static void hookAMS() throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException,
            IllegalAccessException, NoSuchFieldException {

        // 获取AMN的gDefault对象实例
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityTaskManagerSingleton");
        }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            activityManagerInterface = Class.forName("android.app.IActivityTaskManager");
        }else {
            activityManagerInterface = Class.forName("android.app.IActivityManager");
        }

        Object proxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject, "mInstance",proxy);
    }

    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
     * 不然就真的启动替身了, 狸猫换太子...
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发; 最终调用它的callback
     */
    public static void hookActivityThread() throws Exception {

        // 先获取到当前的ActivityThread对象
        Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

        // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");

        //把Handler的mCallback字段,替换为new MockClass2(mH)
        RefInvoke.setFieldObject(Handler.class,
                mH, "mCallback", new HandlerProxy(mH));
    }
}

La clase AMSProxy es la siguiente:

class AMSProxy implements InvocationHandler {

    private static final String TAG = "MockClass1";
	private static final String stubPackage = "jianqiang.com.hostapp";
    
    Object mBase;

    public AMSProxy(Object base) {
        mBase = base;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.e(TAG, method.getName());

        if ("startActivity".equals(method.getName())) {
            // 只拦截这个方法
            // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱

            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];

            Intent newIntent = new Intent();

            // 这里我们把启动的Activity临时替换为 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }

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

La clase HandlerProxy es la siguiente:

class HandlerProxy implements Handler.Callback {

    Handler mBase;

    public HandlerProxy(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
            case 100:   //for API 28以下
                handleLaunchActivity(msg);
                break;
            case 159: // //for API 28以上
                handleActivity(msg);
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

   private void handleActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj; // ClientTransaction对象

        List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks");
        if(mActivityCallbacks.size() > 0) {
            String className = "android.app.servertransaction.LaunchActivityItem";
            if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                // 找到LaunchActivityItem
                Object object = mActivityCallbacks.get(0);
                // 找到传递过来的intent
                Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent");
                Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
                intent.setComponent(target.getComponent());
            }
        }
    }

    private void handleLaunchActivity(Message msg) {
        // 取出TargetActivity
        Object obj = msg.obj; // ActivityClientRecord

        // 把替身恢复成真身,拿到 ActivityClientRecord 中的intent
        Intent raw = (Intent) RefInvoke.getFieldObject(obj,"intent");
        Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        raw.setComponent(target.getComponent());
    }

}

De esta forma, una Actividad que no está declarada en AndroidManifest.xml se puede iniciar normalmente.

Inicie un servicio complementario que no esté declarado en AndroidManifest

ActivityThread finalmente inicia una actividad a través de instrumentación. ActivityThread no utiliza Instrumentación para iniciar el Servicio, sino que refleja directamente el Servicio y lo inicia. La instrumentación solo brinda servicios a la Actividad.

inicioSolución de servicio

1. Declare varios servicios de marcador de posición en la aplicación host. El número es el mismo que el del servicio en el complemento.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jianqiang.com.hostapp">
	...
    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
			...

        <service android:name="jianqiang.com.hostapp.StubService1"/>
        <service android:name="jianqiang.com.hostapp.StubService2"/>
    </application>

</manifest>
2. Cree un archivo de configuración plugin_config.json en el directorio de Activos del complemento para que el Servicio de marcador de posición del host se corresponda uno a uno con el Servicio en el complemento.
{
  "plugins": [
    {
      "PluginService": "jianqiang.com.plugin1.MyService1",
      "StubService": "jianqiang.com.hostapp.StubService1"
    },
    {
      "PluginService": "jianqiang.com.plugin1.MyService2",
      "StubService": "jianqiang.com.hostapp.StubService2"
    }
  ]
}

Defina un método para analizar el archivo de configuración:

 /**
     * 读取zip文件中某个文件为字符串,参看自Zeus的PluginUtil
     * @param zipFile     压缩文件
     * @param fileNameReg 需要获取的文件名
     * @return 获取的字符串
     */
    public static String readZipFileString(String zipFile,String fileNameReg){
        final int BUF_SIZE = 8192;

        String result = null;
        byte[] buffer = new byte[BUF_SIZE];
        InputStream in = null;
        ZipInputStream zipIn = null;
        ByteArrayOutputStream bos = null;
        try {
            File file = new File(zipFile);
            if (!file.exists()) return null;
            in = new FileInputStream(file);
            zipIn = new ZipInputStream(in);
            ZipEntry entry;
            while (null != (entry = zipIn.getNextEntry())) {
                String zipName = entry.getName();
                if (zipName.equals(fileNameReg)) {
                    int bytes;
                    int count = 0;
                    bos = new ByteArrayOutputStream();

                    while ((bytes = zipIn.read(buffer, 0, BUF_SIZE)) != -1) {
                        bos.write(buffer, 0, bytes);
                        count += bytes;
                    }
                    if (count > 0) {
                        result = bos.toString();
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                closeSilently(in);
                closeSilently(zipIn);
                closeSilently(bos);
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }
3. Fusionar el complemento y el host dex
/**
 * 由于应用程序使用的ClassLoader为PathClassLoader
 * 最终继承自 BaseDexClassLoader
 * 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
 * dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
 * 系统的classLoader就能帮助我们找到这个类
 *
 * 这个类用来进行对于BaseDexClassLoader的Hook
 * 类名太长, 不要吐槽.
 * @author weishu
 * @date 16/3/28
 */
public final class BaseDexClassLoaderHookHelper {

    public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
            throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        // 获取 BaseDexClassLoader : pathList
        Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");

        // 获取 PathList: Element[] dexElements
        Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");

        // Element 类型
        Class<?> elementClass = dexElements.getClass().getComponentType();

        // 创建一个数组, 用来替换原始的数组
        Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

        // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
        Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
        Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
        Object o = RefInvoke.createObject(elementClass, p1, v1);

        Object[] toAddElementArray = new Object[] { o };
        // 把原始的elements复制进去
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        // 插件的那个element复制进去
        System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

        // 替换
        RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
    }
}
4. Adoptar el método de "engañar a los superiores y ocultar a los inferiores"
public class AMSHookHelperForService {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";

    /**
     * Hook AMS
     * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
     */
    public static void hookAMS() throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException,
            IllegalAccessException, NoSuchFieldException {

        // 获取AMN的gDefault对象实例
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        activityManagerInterface = Class.forName("android.app.IActivityManager");

        Object proxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject, "mInstance",proxy);
    }

    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
     * 不然就真的启动替身了, 狸猫换太子...
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发; 最终调用它的callback
     */
    public static void hookActivityThread() throws Exception {

        // 先获取到当前的ActivityThread对象
        Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

        // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");

        //把Handler的mCallback字段,替换为new HandlerProxy(mH)
        RefInvoke.setFieldObject(Handler.class,
                mH, "mCallback", new HandlerProxy(mH));
    }
}

A diferencia de Activity, startService se delega al método startService de ActivityManagerService:

Agregue capturas de startService y stopService en el archivo AMSProxy:

public class AMSProxy implements InvocationHandler {
    private static final String TAG = "AMSProxy";
    private static final String stubPackage = "jianqiang.com.hostapp";

    Object mBase;

    public AMSProxy(Object mBase) {
        this.mBase = mBase;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e(TAG, method.getName());

        if ("startActivity".equals(method.getName())) {
            // 只拦截这个方法
            // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱

            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];

            Intent newIntent = new Intent();

            // 替身Activity的包名, 也就是我们自己的包名
            String stubPackage = "jianqiang.com.hostapp";

            // 这里我们把启动的Activity临时替换为 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }else if("startService".equals(method.getName())){
            // hook startService

            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            // 拿到启动Service时的Intent
            Intent rawIntent = (Intent) args[index];
            // jianqiang.com.plugin1.MyService1
            String rawServiceName = rawIntent.getComponent().getClassName();
            // 拿到插件Service对应的Service  jianqiang.com.hostapp.StubService1
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);
            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);
            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }else if("stopService".equals(method.getName())){
            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }

            //get StubService form UPFApplication.pluginServices
            Intent rawIntent = (Intent) args[index];
            String rawServiceName = rawIntent.getComponent().getClassName();
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);
            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);

            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        } 
        return method.invoke(mBase, args);
    }
}

Después de engañar a AMS, originalmente notificará a la aplicación que inicie StubService, pero necesitamos conectar el objeto mCallback del objeto mH de ActivityThread y aún así interceptar su método handleMessage, pero esta vez intercepta la rama CREATE_SERVICE (valor = 114). Ejecute el método handleCreateService en ActivityThread. Veamos la implementación del código fuente:

public void handleMessage(Message msg) {
    switch (msg.what) {
       ...

        case CREATE_SERVICE:
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        ("serviceCreate: " + String.valueOf(msg.obj)));
            }
            handleCreateService((CreateServiceData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;

        ... 
    }
}
private void handleCreateService(CreateServiceData data) {
	LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        //创建service的context
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //创建Application
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //获取类加载器
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //加载service实例
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
                ...
                //初始化service
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        //调用service的onCreate方法
        service.onCreate();
     } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}

En el método handleCreateService se puede obtener la información del Servicio a iniciar a través de CreateServiceData. Simplemente conecte el valor de data.info.name como servicio complementario.

public class HandlerProxy implements Handler.Callback {
    Handler mBase;

    public HandlerProxy(Handler mBase) {
        this.mBase = mBase;
    }

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 100: // //for API 28以下
                handleLaunchActivity(msg);
                break;
            case 159: // //for API 28以上
                handleActivity(msg);
                break;
            case 114: // CREATE_SERVICE
                handleCreateService(msg);
                break;
        }
        mBase.handleMessage(msg);
        return true;
    }

    private void handleCreateService(Message msg) {
        Object obj = msg.obj;
        ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info");

        String realServiceName = null;

        for (String key : MyApplication.pluginServices.keySet()) {
            String value = MyApplication.pluginServices.get(key);
            if(value.equals(serviceInfo.name)) {
                realServiceName = key;
                break;
            }
        }

        if(TextUtils.isEmpty(realServiceName)){
            // 只有在plugin_config中注册的才不会为空
            return;
        }

        serviceInfo.name = realServiceName;
    }

    private void handleActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj; // ClientTransaction对象

        List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks");
        if(mActivityCallbacks.size() > 0) {
            String className = "android.app.servertransaction.LaunchActivityItem";
            if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                // 找到LaunchActivityItem
                Object object = mActivityCallbacks.get(0);
                // 找到传递过来的intent
                Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent");
                Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
                intent.setComponent(target.getComponent());
            }
        }
    }

    private void handleLaunchActivity(Message msg) {
        // 取出TargetActivity
        Object obj = msg.obj; // ActivityClientRecord

        // 把替身恢复成真身,拿到 ActivityClientRecord 中的intent
        Intent raw = (Intent) RefInvoke.getFieldObject(obj,"intent");
        Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        raw.setComponent(target.getComponent());
    }
}

Esto completa el soporte para startService.

llamar al anfitrión

 public void startUnRegisterService1InPlugin1(View view) {
        try {

            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService1"));
            startService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stopUnRegisterService1InPlugin1(View view) {
        try {
            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService1"));
            stopService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

solución bindService

Simplemente agregue una rama a AMSProxy y engañe a AMS cuando bindService:

else if("bindIsolatedService".equals(method.getName())){
            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }

            Intent rawIntent = (Intent) args[index];
            String rawServiceName = rawIntent.getComponent().getClassName();
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);


            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);

            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }

Entonces puedes llamarlo en el host:

public void bindUnRegisterService1InPlugin1(View view) {
        try {

            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService2"));
            bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("TAG","onServiceConnected   " + name);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("TAG","onServiceConnected   " + name);
        }
    };
    public void unbindUnRegisterService1InPlugin1(View view) {
        try {
            if(null != serviceConnection){
                unbindService(serviceConnection);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Pregunta 1: ¿Por qué no engañar a AMS cuando se desvincula?

Debido a que la sintaxis de desvinculación es unbindService(conn), AMS encontrará el servicio correspondiente según la conexión, por lo que no necesitamos reemplazar MyService2 con StubService2.

Pregunta 2: ¿Por qué no es necesario volver a cambiar StubService2 a MyService2 en MockClass2?

Porque bindService toma primero el método handleCreateService y luego el método handleBindService. StubService2 se volvió a cambiar a MyService2 en el método handleCreateService, por lo que no es necesario cambiar más tarde.

Insertar descripción de la imagen aquí
Código fuente

Supongo que te gusta

Origin blog.csdn.net/jxq1994/article/details/130891267
Recomendado
Clasificación