深入淺出Service外掛化原理

關注公眾號:顧林海
Service外掛化的重點是保證它的優先順序,需要一個真正的Service來實現,當啟動外掛Service時,就會先啟動代理Service,當這個代理Service執行起來後,在它的onStartCommand等方法裡面進行分發,執行外掛Service的onCreate等方法,這種方案叫代理分發。

也就是在啟動外掛Service時替換為代理Service,什麼時候替換?通過startService方法啟動Service會呼叫ContextWrapper的startService方法,如下所示:
//路徑:/frameworks/base/core/java/android/content/ContextWrapper.java public class ContextWrapper extends Context { Context mBase; ... @Override public ComponentName startService(Intent service) { return mBase.startService(service); } ... } 複製程式碼
在ContextWrapper的startService方法中呼叫mBase的startService方法,mBase的型別是Context,而Context是一個抽象類,內部定義了很多方法以及靜態常量,它的具體實現類是ContextImpl,進入ContextImpl的startService方法:
//路徑:/frameworks/base/core/java/android/app/ContextImpl.java @Override public ComponentName startService(Intent service) { warnIfCallingFromSystemProcess(); return startServiceCommon(service, false, mUser); } 複製程式碼
ContextImpl的startService方法中又呼叫了startServiceCommon方法:
//路徑:/frameworks/base/core/java/android/app/ContextImpl.java private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); //註釋1 ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); ... return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } 複製程式碼
註釋1處通過ActivityManager的getService方法獲取ActivityManagerService的代理類IActivityManager,進入ActivityManager的getService方法:
//路徑:/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; } }; 複製程式碼
getService方法通過IActivityManagerSingleton的get方法獲取IActivityManager物件,IActivityManagerSingleton是一個單例類,在create方法中從ServiceManager中獲取一個名叫“activity”的Service引用,同時也是IBinder型別的ActivityManagerService的引用,最後通過IActivityManager.Stub.asInterface方法將它轉換成IActivityManager,看到IActivityManager.Stub.asInterface這段程式碼時可以知道這裡採用的是AIDL方式來實現程序間通訊,也就是說服務端ActivityManagerService會實現IActivityManager.Stub類並實現相應的方法。
繼續回到ContextImpl的startServiceCommon方法:
//路徑:/frameworks/base/core/java/android/app/ContextImpl.java private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); //註釋1 ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); ... return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } 複製程式碼
在註釋1處獲取到ActivityManagerService的代理類IActivityManager,接著通過這個代理類向ActivityManagerService傳送startService的訊息。
將上面的知識點進行總結,如下圖所示:

替換代理Service可以通過Hook IActivityManager生成動態代理類來實現。
Service外掛化需要一個真正的Service來實現,先在AndroidManifest.xml中註冊代理ProxyService:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.book.demo"> ... <application ...> ... <service android:name=".ProxyService" /> </application> </manifest> 複製程式碼
接著啟動外掛Service:
public class MainActivity extends AppCompatActivity { private Button mBtnNormal; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initEvent(); } private void initViews(){ mBtnNormal=findViewById(R.id.btn_normal); } private void initEvent(){ mBtnNormal.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent=new Intent(MainActivity.this,TargetService.class); startService(intent); } }); } } 複製程式碼
這個TargetService用來模擬外掛Service,不能夠直接啟動,因此需要Hook IActivityManager,定義替換IActivityManager的代理類IActivityManagerProxy,程式碼如下:
public class IActivityManagerProxy implements InvocationHandler { private Object mActivityManager; public IActivityManagerProxy(Object activityManager){ this.mActivityManager=activityManager; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("startService".equals(method.getName())){ Intent intent=null; int index=0; for(int i=0,length=args.length;i<length;i++){ if(args[i] instanceof Intent){ index=i; break; } } intent= (Intent) args[index]; Intent proxyIntent=new Intent(); String packageName="com.book.demo"; proxyIntent.setClassName(packageName,packageName+".ProxyService"); //將外掛Service的ClassName儲存起來 proxyIntent.putExtra("TARGET_SERVICE",intent.getComponent().getClassName()); //替換為新建的proxyIntent args[index]=proxyIntent; } return method.invoke(mActivityManager,args); } } 複製程式碼
上述程式碼中通過攔截startService方法,獲取啟動TargetService的Intent,再建立代理Service的Intent,將TargetService的Intent的相關資訊儲存在代理Service的Intent中,最後將啟動的Intent替換成代理Service的Intent,也就是說最後啟動的是ProxyService。
接著用IActivityManagerProxy替換系統的IActivityManager,程式碼如下:
public class HookUtil { public static void hookAMS() { Object defaultSingleton = null; try { if (Build.VERSION.SDK_INT >= 26) { Class<?> activityManagerClass = Class.forName("android.app.ActivityManager"); Field field=activityManagerClass.getDeclaredField("IActivityManagerSingleton"); field.setAccessible(true); defaultSingleton=field.get(null); } else { Class<?> activityManagerNativeClass=Class.forName("android.app.ActivityManagerNative"); Field field=activityManagerNativeClass.getDeclaredField("gDefault"); field.setAccessible(true); defaultSingleton=field.get(null); } Class<?> singletonClass=Class.forName("android.util.Singleton"); Field mInstanceField=singletonClass.getDeclaredField("mInstance"); mInstanceField.setAccessible(true); //獲取IActivityManager Object iActivityManager=mInstanceField.get(defaultSingleton); Class<?> iActivityManagerClazz=Class.forName("android.app.IActivityManager"); Object proxy=Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivityManagerClazz},new IActivityManagerProxy(iActivityManager)); mInstanceField.set(defaultSingleton,proxy); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } 複製程式碼
上述程式碼中主要做了這些事:
- 對版本進行區分,最終獲取的是Singleton型別的IActivityManagerSingleton或者gDefault欄位。
- 獲取Singleton類中的mInstance欄位並獲取系統的IActivityManager。
- 建立代理類IActivityManagerProxy,來替換系統的IActivityManager。
接著在Application中呼叫這個方法:
public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); HookUtil.hookAMS(); } } 複製程式碼
到這裡啟動的Service不是外掛的Service,而是代理的Service,接下來需要在ProxyService中進行代理分發。
public class ProxyService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (null == intent || !intent.hasExtra("TARGET_SERVICE")) { return START_STICKY; } String serviceName = intent.getStringExtra("TARGET_SERVICE"); if (TextUtils.isEmpty(serviceName)) { return START_STICKY; } //=============反射呼叫外掛Service的attach方法=========================== try { //獲取attach方法需要的ActivityThread Class activityThreadClazz = Class.forName("android.app.ActivityThread"); Field sCurrentActivityThread = activityThreadClazz.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThread.setAccessible(true); Object activityThread = sCurrentActivityThread.get(null); //獲取attach方法需要的token Method getActivityThreadMethod = activityThreadClazz.getDeclaredMethod("getApplicationThread"); getActivityThreadMethod.setAccessible(true); Object applicationThread = getActivityThreadMethod.invoke(activityThread); Class iInterfaceClazz = Class.forName("android.os.IInterface"); Method asBinderMethod = iInterfaceClazz.getDeclaredMethod("asBinder"); asBinderMethod.setAccessible(true); Object token = asBinderMethod.invoke(applicationThread); //反射獲取attach方法 Class serviceClazz = Class.forName("android.app.Service"); Method attachMethod = serviceClazz.getDeclaredMethod("attach", Context.class, activityThreadClazz, String.class, IBinder.class, Application.class, Object.class); attachMethod.setAccessible(true); //獲取IActivityManager Object defaultSingleton = null; if (Build.VERSION.SDK_INT >= 26) { Class<?> activityManagerClass = Class.forName("android.app.ActivityManager"); Field field = activityManagerClass.getDeclaredField("IActivityManagerSingleton"); field.setAccessible(true); defaultSingleton = field.get(null); } else { Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative"); Field field = activityManagerNativeClass.getDeclaredField("gDefault"); field.setAccessible(true); defaultSingleton = field.get(null); } Class<?> singletonClass = Class.forName("android.util.Singleton"); Field mInstanceField = singletonClass.getDeclaredField("mInstance"); mInstanceField.setAccessible(true); Object iActivityManager = mInstanceField.get(defaultSingleton); //反射執行外掛Service Service targetService = (Service) Class.forName(serviceName).newInstance(); attachMethod.invoke(targetService, this, activityThread, intent.getComponent().getClassName(), token, getApplication(), iActivityManager); //執行外掛Service的onCreate、onStartCommand方法 targetService.onCreate(); targetService.onStartCommand(intent,flags,startId); } catch (ClassNotFoundException e) { e.printStackTrace(); return START_STICKY; } catch (NoSuchMethodException e) { e.printStackTrace(); return START_STICKY; } catch (NoSuchFieldException e) { e.printStackTrace(); return START_STICKY; } catch (IllegalAccessException e) { e.printStackTrace(); return START_STICKY; } catch (InvocationTargetException e) { e.printStackTrace(); return START_STICKY; } catch (InstantiationException e) { e.printStackTrace(); return START_STICKY; } return START_STICKY; } } 複製程式碼
上訴程式碼主要做了以下幾件事:
- 判斷引數條件不滿足、出現異常和程式碼執行完畢返回START_STICKY,這樣ProxyService會重新建立並執行onStartCommand方法。
- 建立外掛Service,並反射呼叫attach方法。
- 進行代理分發,執行外掛Service的onCreate和onStartCommand方法。
外掛TargetService如下所示:
public class TargetService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Log.e("TargetService","----onCreate-----"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e("TargetService","----onStartCommand-----"); return super.onStartCommand(intent, flags, startId); } } 複製程式碼
執行看是否會在onCreate和onStartCommand方法中列印Log:

