由于启动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