Android 插件化之——启动和停止插件Service

由于启动service后,再次启动这个service时,是不会再次创建这个service的实例的,所以,如果有多个插件service要启动,如果只是按照启动插件activity的思想,只使用一个占坑的service,是不能满足需求的,因为,按照启动插件activity思想,每次启动一个插件activity,如果启动的activity是标准启动模式,则每次都会创建一个新的activity,而service,多次启动,只会创建一个实例。但是,除开硬件限制,应用中可以启动的service的数量是没有限制的,这样如果要启动的service很多,则需要在宿主的manifest中,预先写很多的占坑的servcie,这样处理,感觉也不是很理想。有没有一种更好的方式启动插件service呢?当然是有的,就是代理转发方式,它的原理就是,在宿主app的manifest文件中,只需声明一个代理的service,宿主中,每次启动插件service时,只需要使用hook,将要启动的插件service变换成代理的service,这样真实启动的就是这个代理servcie,并在启动的intent中,添加要启动的插件的className信息,当代理service的onStartCommand方法执行时,就可以通过onStartCommand方法的intent参数,获取到插件servcie的className,在代理service的onStartCommand中,创建插件service,并通过反射调用插件service的attach方法,并代用插件service的onCreate方法和onStartCommand方法。下面就按照这个思路,实现启动插件service的演示示例。
首先要确定将要启动的插件service变换成代理service的hook点,通过下面的分析,可以确定hook点。
启动Service,都是调用了ContextWrapper类的startServcie,ContextWrapper的startService方法内部,又调用了ContextImpl类的startService,ContextImpl类的startService方法内部又调用了startServiceCommon方法,下面看看这个方法的具体实现:


http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/app/ContextImpl.java

 private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess(this);
    // 关键代码
        ComponentName cn = ActivityManager.getService().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), requireForeground,
                        getOpPackageName(), user.getIdentifier());
        ...
        return cn;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

这个方法内部,通过ActivityManager.getService()获取到一个IActivityManager类型的对象,并调用这个对象的startService方法。下面看看ActivityManager类的getService()方法的具体实现:


http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/app/ActivityManager.java

public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
};

IActivityManagerSingleton这个实例对象是通过创建一个匿名对象的方式赋值的,下面看看Singleton这个类


http://androidxref.com/8.1.0_r33/xref/frameworks/base/core/java/android/util/Singleton.java

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

这个类是个抽象类,通过创建匿名对象的方式,实现了create这个抽象方法,这样,mInstance这个成员变量就被赋值。所以在ActivityManager类中的静态变量IActivityManagerSingleton创建时,实现了Sigleton的create方法时,在create方法中,创建了 IActivityManager类型的对象am,并赋值给了Singleton内部的这个对象。后面,通过 IActivityManagerSingleton的get方法获取的就是这个IActivityManager类型的对象。从Singleton类的get方法可以看出,这个方法是获取的对象是个单例的。所以,ActivityManager类中的静态成员变量IActivityManagerSingleton,在任何地方调用get方法获取的都是同一个IActivityManager类型的对象。熟悉adil方法进行进程间通信的读者应该知道,这个返回的 IActivityManager类型的对象其实就是system_server进程的ActivityManagerService在app进程的一个代理对象。想要找个启动servcie的稳定可靠的hook点,hook 这个IActivityManager对象是一个不错的选择。因为这个对象是单例的,并且,system_server进程的启动servcie的入口就是通过这 IActivityManager代理对象发起的。所以这个 IActivityManager的startService方法,是一个很好的hook点。
下面是具体hook AMS的具体代码实现:
//HookHelper.java 工具类

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import android.os.Handler;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;

import test.cn.example.com.androidskill.optimize.hotfix.FixDexUtils2;
import test.cn.example.com.util.LogUtil;

public class HookHelper {
    public static final String PLUG_INTENT = "plug_intent";
    public static final String PACKAGENAME = "test.cn.example.com.androidskill";
    public static final String PLUGCLASSNAME = PACKAGENAME+".hook.PlugActivity";
    public static final String BACKUPCLASSNAME = PACKAGENAME+".hook.BackUpActivity";
    public static void hookAMS() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Object singleton = null;
        if(Build.VERSION.SDK_INT>=26){
            Class<?> activityManagerClazz = Class.forName("android.app.ActivityManager");
            singleton = FixDexUtils2.getObject(activityManagerClazz, "IActivityManagerSingleton", null);
        }else {
            Class<?> actitivytManagerNatvieClazz = Class.forName("android.app.ActivityManagerNative");
            singleton = FixDexUtils2.getObject(actitivytManagerNatvieClazz,"gDefault",null);
        }

        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField = FixDexUtils2.getField(singletonClazz, "mInstance");
        mInstanceField.setAccessible(true);
        Object iActivityManager = mInstanceField.get(singleton);

        Object proxyInstance = Proxy.newProxyInstance(iActivityManager.getClass().getClassLoader(), iActivityManager.getClass().getInterfaces(), new IActivityManagerInvocationHandler(iActivityManager));

        mInstanceField.set(singleton,proxyInstance);
    }

    public static Object getIActivityManager() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Object iActivityManagerSingleton = null;
        if(Build.VERSION.SDK_INT>=26){
            Class<?> activityManagerClazz = Class.forName("android.app.ActivityManager");
            iActivityManagerSingleton = FixDexUtils2.getObject(activityManagerClazz, "IActivityManagerSingleton", null);
        }else {
            Class<?> actitivytManagerNatvieClazz = Class.forName("android.app.ActivityManagerNative");
            iActivityManagerSingleton = FixDexUtils2.getObject(actitivytManagerNatvieClazz,"gDefault",null);
        }
        if(null != iActivityManagerSingleton){
            Class<?> sigletonClazz = Class.forName("android.util.Singleton");
            Object iActivityManager = FixDexUtils2.getObject(sigletonClazz, "mInstance", iActivityManagerSingleton);
            return iActivityManager;
        }
        return null;

    }
}

IActivityManagerInvocationHandler.java

import android.content.Intent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import test.cn.example.com.androidskill.hook.service.ProxyService;
import test.cn.example.com.util.LogUtil;

public class IActivityManagerInvocationHandler implements InvocationHandler {
    private final Object mActivityManager;

    public IActivityManagerInvocationHandler(Object activityManager){
        this.mActivityManager = activityManager;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("startActivity")){
//            ActivityManagerService类中的startActivity方法的10个参数
//            startActivity(IApplicationThread caller, String callingPackage,
//                    Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
//            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions)
            Intent intent = null;
            int index = -1;
            String packageName = "test.cn.example.com.androidskill";
            String plugClassName = packageName+".hook.PlugActivity";
            LogUtil.i("args.length      "+args.length);
            for (int i = 0; i < args.length; i++) {
//                LogUtil.i(args[i]+"");
                if(args[i] instanceof Intent){
                    Intent tempIntent = (Intent)args[i];
                    if(null !=tempIntent.getComponent() && plugClassName.equals(tempIntent.getComponent().getClassName())){
                        index = i;
                        break;
                    }
                }
            }
            if(-1 !=index){
                //实际要启动的intent
                intent = (Intent) args[index];
                //用占坑的BackUpActivity来通过AMS的检查
                Intent backupIntent = new Intent();

                //test.cn.example.com.androidskill.hook.BackUpActivity
                backupIntent.setClassName(packageName,packageName+".hook.BackUpActivity");
                backupIntent.putExtra(HookHelper.PLUG_INTENT,intent);
                args[index] = backupIntent;
                LogUtil.i("成功骗过了AMS");
            }
        }else if("startService".equals(method.getName())){
            //startService
            int index = -1;
            String packageName = "test.cn.example.com.androidskill";
            //test.cn.example.com.androidskill.hook.servcie.PlugService
            String plugServiceClassName = packageName+".hook.service.PlugService";
            String proxyServiceClassName = packageName+".hook.service.ProxyService";
            Intent proxyIntent = null;
            for (int i = 0; i < args.length; i++) {
                if(args[i] instanceof Intent){
                    index = i;
                    break;
                }
            }

            if(-1 != index){
                Intent plugIntent = (Intent) args[index];
                if(null !=plugIntent.getComponent() && plugServiceClassName.equals(plugIntent.getComponent().getClassName())){
                    LogUtil.i(plugServiceClassName);
                    proxyIntent = new Intent();
                    proxyIntent.setClassName(packageName,proxyServiceClassName);
                    proxyIntent.putExtra(HookHelper.PLUG_INTENT,plugIntent);
                }
                //这里添加一个判断,防止类名写错时,导致args中的intent这个参数是null,导致崩溃
                args[index] = proxyIntent==null?plugIntent:proxyIntent;
            }
        }else if("stopService".equals(method.getName())){
            int index = -1;
            for (int i=0;i<args.length;i++){
                if(args[i] instanceof Intent){
                    index = i;
                    break;
                }
            }
            if(index != -1){
                Intent rawIntent = (Intent) args[index];
                return ProxyService.stopPlugService(rawIntent);
            }
        }
        return method.invoke(mActivityManager,args);
    }
}

通过上面的代理,完成了启动插件service时,变成启动代理service时的功能,接着在代理service的onStartCommand方法中,创建插件service实例,并调用插件service的attach方法,onCreate,onStartCommand方法。下面是代理Service类的具体实现,记得在宿主manifest文件中,声明代理service。
ProxyService.java


import android.app.Application;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.Nullable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import test.cn.example.com.androidskill.hook.HookHelper;
import test.cn.example.com.androidskill.optimize.hotfix.FixDexUtils2;
import test.cn.example.com.util.LogUtil;

public class ProxyService extends Service {
    private static HashMap<String,Service> mServices = new HashMap<>();
    @Override
    public void onCreate() {
        super.onCreate();
        LogUtil.i("代理servcie onCreate");

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtil.i("代理servcie onStartCommand   startId="+startId);
        //获取intent中插件servcie的className,构建插件servcie实例,并调用插件servcie的onCreate方法
        if(intent.hasExtra(HookHelper.PLUG_INTENT)){
            Intent parcelableExtra = intent.getParcelableExtra(HookHelper.PLUG_INTENT);
            String className = parcelableExtra.getComponent().getClassName();
            LogUtil.i(className);
            Service service = mServices.get(className);
            if(null == service){
                try {
                    service = (Service) Class.forName(className).newInstance();
                    //目前创建的servcie实例是没有上下文的,需要调用其attach方法,才能让这个service拥有上下文环境
//                public final void attach(
//                        Context context,
//                        ActivityThread thread, String className, IBinder token,
//                        Application application, Object activityManager)

                    Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
                    Object sCurrentActivityThread = FixDexUtils2.getObject(activityThreadClazz, "sCurrentActivityThread", null);
                    Object mAppThread = FixDexUtils2.getObject(activityThreadClazz, "mAppThread", sCurrentActivityThread);
                    Class<?> iInterfaceClazz = Class.forName("android.os.IInterface");
                    Method asBinderMethod = iInterfaceClazz.getDeclaredMethod("asBinder");
                    asBinderMethod.setAccessible(true);
                    IBinder token = (IBinder) asBinderMethod.invoke(mAppThread);
                    Object iActivityManager = HookHelper.getIActivityManager();
                    Method attachMethod = Service.class.getDeclaredMethod("attach", Context.class,sCurrentActivityThread.getClass(),
                            String.class,IBinder.class, Application.class, Object.class);
                    attachMethod.setAccessible(true);
                    attachMethod.invoke(service,this,sCurrentActivityThread,className,token,getApplication(),iActivityManager);
                    service.onCreate();
                    mServices.put(className,service);
                    service.onStartCommand(intent,flags,startId);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }else {
                service.onStartCommand(intent,flags,startId);
            }

        }

        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @android.support.annotation.Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtil.i("onDestroy");
    }

    public static int stopPlugService(Intent intent){
        if(null == intent){
            return 0;
        }
        if(null == intent.getComponent()){
            return 0;
        }
        String className = intent.getComponent().getClassName();
        LogUtil.i(className);
        Service service = mServices.get(className);
        LogUtil.i(service+"");
        if(null == service){
            return 0;
        }
        service.onDestroy();
        mServices.remove(className);
        return 1;
    }
}

完成上述步骤后,还要准备插件service,插件service,可以通过在本地先编写一个PlugService,具体代码如下:
PlugService.java文件

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import test.cn.example.com.util.LogUtil;

public class PlugService extends Service {
    private ScheduledExecutorService scheduledExecutorService;
    @Override
    public void onCreate() {
        super.onCreate();
        LogUtil.i("插件servcie onCreate");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                LogUtil.i("插件servcie执行定时任务");
            }
        };
        scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(runnable,1L,2L, TimeUnit.SECONDS);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtil.i("插件servcie onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @android.support.annotation.Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtil.i("插件  onDestroy");
        scheduledExecutorService.shutdown();
    }
}

然后编译项目,生成PlugService.class文件后,在项目的app/build/intermediates/classes/…下面的某个具体的子目录下面,找到编译后的PlugService.class文件和PlugService$1.class文件(注意:这里还有一个PlugService$1.class文件)。找到后, 按照PlugService所在的项目的包名,新建一个文件夹目录,将PlugService.class文件和PlugService$1.class文件(注意:这里还有一个PlugService$1.class文件)放入到新建的目录下,然后,将这个新建的文件夹复制到Android/sdk/build-tools/28.0.3目录下(也就是build-tools目录下的,具体某个版本的目录下),然后打开一个命令行窗口,输入如下命令:

dx --dex --no-strict --output=C:\tool\Android\sdk\build-tools\28.0.3\classes3.dex  C:\tool\Android\sdk\build-tools\28.0.3\test\cn\example\com\androidskill

以上输入的是本示例的命令,读者,自己需要按照自己的项目的路径,调整整个命令。这个命令行中的output=后的面的路径,就是生产dex文件的路径,其中classes3.dex,就是指定生产的dex的具体名称(这个名称,可以所以定义),后面的 C:\tool\Android\sdk\build-tools\28.0.3\test\cn\example\com\androidskill 这个路径,是PlugSerivce.class所在的路径。
输入完命令行后,回车,如果不报任何错误,则会在 C:\tool\Android\sdk\build-tools\28.0.3目录下,生成classes3.dex文件。然后,将这个classes3.dex文件复制到手机的sd卡的根目录下(本示例是复制在sd卡的根目录下,也可以指定其他目录)。至此,完成了插件dex文件的创建工作。(注意:一定要在项目的目录下的文件夹下寻找生成的class文件,在AS中找,会漏掉PlugService$1.class文件,这样后续生成的classes3.dex文件就会缺少PlugService$1.class文件,这样当执行PlugService的定时任务时,就会报find not class PlugService$1.class 这个文件的错误,坑了2个小时)

在项目自定义的Application的onAttachBaseContext方法中,调用HookHelper.hookAMS()方法,这样,整个启动插件service的步骤才算真正完成。最后,使用下面的方式启动插件service

Intent intent = new Intent();
try {
    Class<?> plugServiceClazz = Class.forName(HookHelper.PACKAGENAME + ".hook.service.PlugService");
    intent.setClass(this, plugServiceClazz);
    startService(intent);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

接着,删除项目中的PlugSerice.java文件,然后,重新编译,打包项目,安装到手机上,然后在任何一个activity中,执行上面的启动插件service的代码。过几秒,然后在停止插件service,停止插件service代码如下:

Intent intent_2 = new Intent();
try {
    Class<?> plugServiceClazz = Class.forName(HookHelper.PACKAGENAME + ".hook.service.PlugService");
    intent_2.setClass(this, plugServiceClazz);
    stopService(intent_2);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

打印结果如下:

10-21 : ProxyService.java::24::onCreate-->>代理servcie onCreate
10-21 : ProxyService.java::30::onStartCommand-->>代理servcie onStartCommand   startId=1
10-21 : PlugService.java::23::onCreate-->>插件servcie onCreate
10-21 : PlugService.java::36::onStartCommand-->>插件servcie onStartCommand
10-21 : PlugService.java::27::run-->>插件servcie执行定时任务
10-21 : PlugService.java::27::run-->>插件servcie执行定时任务
10-21 : PlugService.java::27::run-->>插件servcie执行定时任务
10-21 : PlugService.java::27::run-->>插件servcie执行定时任务
10-21 : PlugService.java::27::run-->>插件servcie执行定时任务
10-21 : PlugService.java::50::onDestroy-->>插件  onDestroy
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值