1. 程式人生 > >Virtual apk外掛化架構分析

Virtual apk外掛化架構分析

其基本原理,就是hook系統的activity service等關鍵元件,當需要啟動外掛內的一些元件式,走自定義邏輯,當啟動本地apk中的元件時,走原生邏輯。

一、系統相關。

在分析滴滴外掛化框架之前,需要弄清楚android系統中應用的啟動流程,以及和系統的通訊排程相關。

有幾個關鍵類需要了解下:ActivityThread.java ApplicationThread.java ActivityManagerService.java Instrumentation.java

啟動流程如下:

在laucher裡面啟動一個新的app時,流程參考老羅文章:

整個應用程式的啟動過程要執行很多步驟,但是整體來看,主要分為以下五個階段:

   一. Step1 -Step 11:Launcher通過Binder程序間通訊機制通知ActivityManagerService,它要啟動一個Activity;

   二. Step 12 -Step 16:ActivityManagerService通過Binder程序間通訊機制通知Launcher進入Paused狀態;

   三. Step 17 -Step 24:Launcher通過Binder程序間通訊機制通知ActivityManagerService,它已經準備就緒進入Paused狀態,於是ActivityManagerService就建立一個新的程序,用來啟動一個ActivityThread例項,即將要啟動的Activity就是在這個ActivityThread例項中執行;

   四. Step 25 -Step 27:ActivityThread通過Binder程序間通訊機制將一個ApplicationThread型別的Binder物件傳遞給ActivityManagerService,以便以後ActivityManagerService能夠通過這個Binder物件和它進行通訊;

   五. Step 28 -Step 35:ActivityManagerService通過Binder程序間通訊機制通知ActivityThread,現在一切備就緒,它可以真正執行Activity的啟動

ActivityManagerService:

這個是系統核心類,幾乎掌管了整個系統啟動相關的事宜。

ActivityThread:

它是一個應用程序的主執行緒,有一個死迴圈來分發程序需要處理的工作。

ApplicationThread:

用途參照啟動流程中的step4.

 

Instrumentation:

裡面的方法如下截圖,基本上管理了所有與Activity的相關互動工作。So,這個類需要被hook



二、原始碼分析。

如果想從主apk裡面啟動外掛apk相關的activity或service,那麼首先必須先把外掛中的相關activity和service載入進來,整個載入流程如下。

 

 

 

針對hook的這幾個系統類,Instrumentation使用了一個子類物件(VAInstrumentation)來代替原本的instrumentation,這個子類裡面做的事情很簡單。複寫了父類的newActivity()(就是當我們建立activity時呼叫的方法),利用try catch捕捉classnotfoundexception,正常情況下說明我們想載入的是外掛apk中的activity,但是預設情況,我們的應用是沒有把外掛apk中的類載入進來的,所以報找不到類的錯誤。So,這個時候,我們就可以去外掛apk中載入對應的activity了。

當需要啟動一個activity時,一般使用startActivity(),那麼就跟隨這個方法,看看其呼叫鏈。

public void startActivityForResult(Intentintent, int requestCode, @Nullable Bundle options) {

        if(mParent == null) {

//在這裡看到呼叫了instrumentation的execStartActivity(),那麼我們就需要針對instrumentation的這個方法進行處理,可以看到在virtualapk程式碼中,確實是這樣處理的。

           Instrumentation.ActivityResult ar =

               mInstrumentation.execStartActivity(

                    this,mMainThread.getApplicationThread(), mToken, this,

                    intent, requestCode, options);

          …………

    }

 

Instrumentation.java

 publicActivityResult execStartActivity(

           Context who, IBinder contextThread, IBinder token, Activity target,

           Intent intent, int requestCode, Bundle options) {

       IApplicationThread whoThread = (IApplicationThread) contextThread;

       Uri referrer = target != null ? target.onProvideReferrer() : null;

        if(referrer != null) {

           intent.putExtra(Intent.EXTRA_REFERRER, referrer);

        }

    …………

       try {

           intent.migrateExtraStreamToClipData();

           intent.prepareToLeaveProcess();

//實際呼叫的是ActivityManagerNative,其實就是系統呼叫ActivityManagerService來啟動activity.

//這個就不深究了。既然啟動activity需要呼叫Instrumentation的這個方法,so 我們需要處理這個方法。

            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;

    }

 

Instrumentation.java

//複寫的父類中的方法.

    publicActivityResult execStartActivity(

           Context who, IBinder contextThread, IBinder token, Activity target,

           Intent intent, int requestCode, Bundle options) {

       mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);

        //null component is an implicitly intent

        if(intent.getComponent() != null) {

           Log.i(TAG, String.format("execStartActivity[%s : %s]",intent.getComponent().getPackageName(),

                   intent.getComponent().getClassName()));

           // resolve intent with Stub Activity if needed

           //啟動一個本地預置的activity,不過在其intent中加入了 plugin標誌位,以及目標包名,目標類名

           //接下來繼續走正常的activity啟動流程,直到newActivity()

           this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);

        }

 

       ActivityResult result = realExecStartActivity(who, contextThread, token,target,

                   intent, requestCode, options);

 

       return result;

 

    }

 

整個呼叫流程如下圖片:

 

 

@Override

    publicActivity newActivity(ClassLoader cl, String className, Intent intent) throwsInstantiationException, IllegalAccessException, ClassNotFoundException {

       try {

//先從主apkclassloader中載入類,注意此時的類名是<activityandroid:name=".B$1" android:launchMode="singleTop"/>,又因為本地是沒有建立這個類的,所以肯定會報找不到類的錯誤           cl.loadClass(className);

        }catch (ClassNotFoundException e) {

//可以看下LoadedPlugin的實現,它是怎樣把外掛apk中的類載入進來的。

           LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);

//從載入的外掛資源中,找到對應的activity

           String targetClassName = PluginUtil.getTargetActivity(intent);

 

           Log.i(TAG, String.format("newActivity[%s : %s]", className,targetClassName));

 

           if (targetClassName != null) {

//plugin.getClassLoader() 已經是載入過外掛資源的classloader,這裡就建立了一個外掛activity例項

                //相當於這個時候就用外掛apk中的activity代替了預置的<activity android:name=".B$1"android:launchMode="singleTop"/>

                //之所以要這樣操作,是因為如果不預置activity,即不在manifest中宣告,那麼就會報activityNotFound的錯誤。

                //所以採取的策略是,先騙過系統,防止出現activityNotFound的錯誤                Activity activity =mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);

               activity.setIntent(intent);

 

               try {

                    // for 4.1+

//通過反射修改mResources為載入了外掛資源的resource

                   ReflectUtil.setField(ContextThemeWrapper.class, activity,"mResources", plugin.getResources());

               } catch (Exception ignored) {

                    // ignored.

               }

 

               return activity;

           }

        }

 

       return mBase.newActivity(cl, className, intent);

    }

 

至此,外掛apk中的activity的啟動就分析完畢了。

 

 

 

 

針對service,是hook了系統的利用動態代理實現的。

 /**

     *hookSystemServices, but need to compatible with Android O in future.

     */

   private void hookSystemServices() {

       try {

           Singleton<IActivityManager> defaultSingleton =(Singleton<IActivityManager>)ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");

           IActivityManager activityManagerProxy =ActivityManagerProxy.newInstance(this, defaultSingleton.get());

 

           // Hook IActivityManager from ActivityManagerNative

            //把singleton的內部的物件用代理物件代替。

           ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(),defaultSingleton, "mInstance", activityManagerProxy);

 

           if (defaultSingleton.get() == activityManagerProxy) {

               this.mActivityManager = activityManagerProxy;

           }

        }catch (Exception e) {

           e.printStackTrace();

        }

    }

看下自己定義的動態代理類。

 

 

 

ActivityManagerProxy.java

@Override

    publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if("startService".equals(method.getName())) {

           try {

               return startService(proxy, method, args);

           } catch (Throwable e) {

                Log.e(TAG, "Start serviceerror", e);

           }

        }else if ("stopService".equals(method.getName())) {

           try {

               return stopService(proxy, method, args);

           } catch (Throwable e) {

               Log.e(TAG, "Stop Service error", e);

           }

        }else if ("stopServiceToken".equals(method.getName())) {

           try {

               return stopServiceToken(proxy, method, args);

           } catch (Throwable e) {

               Log.e(TAG, "Stop service token error", e);

           }

        }else if ("bindService".equals(method.getName())) {

           try {

               return bindService(proxy, method, args);

           } catch (Throwable e) {

               e.printStackTrace();

           }

        }else if ("unbindService".equals(method.getName())) {

           try {

               return unbindService(proxy, method, args);

           } catch (Throwable e) {

               e.printStackTrace();

            }

        }else if ("getIntentSender".equals(method.getName())) {

           try {

               getIntentSender(method, args);

           } catch (Exception e) {

               e.printStackTrace();

           }

        }else if ("overridePendingTransition".equals(method.getName())){

           try {

               overridePendingTransition(method, args);

           } catch (Exception e){

               e.printStackTrace();

           }

        }

 

       try {

           // sometimes system binder has problems.

           return method.invoke(this.mActivityManager, args);

        }catch (Throwable th) {

           Throwable c = th.getCause();

           if (c != null && c instanceof DeadObjectException) {

                // retry connect to systembinder

               IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);

               if (ams != null) {

                    IActivityManager am =ActivityManagerNative.asInterface(ams);

                    mActivityManager = am;

               }

           }

 

           Throwable cause = th;

           do {

               if (cause instanceof RemoteException) {

                    throw cause;

               }

           } while ((cause = cause.getCause()) != null);

 

           throw c != null ? c : th;

        }

 

    }

 

ActivityManagerProxy.java

 

private Object startService(Object proxy, Methodmethod, Object[] args) throws Throwable {

       IApplicationThread appThread = (IApplicationThread) args[0];

       Intent target = (Intent) args[1];

        //先在外掛service集合中查詢,找不到就說明是主apk中的service

       ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);

        //啟動的是主apk中的service

        if(null == resolveInfo || null == resolveInfo.serviceInfo) {

           // is host service

           return method.invoke(this.mActivityManager, args);

        }

 

       return startDelegateServiceForTarget(target, resolveInfo.serviceInfo,null, RemoteService.EXTRA_COMMAND_START_SERVICE);

}

 

wrapperTargetIntent():

private Intent wrapperTargetIntent(Intenttarget, ServiceInfo serviceInfo, Bundle extras, int command) {

        //fill in service with ComponentName

       target.setComponent(new ComponentName(serviceInfo.packageName,serviceInfo.name));

       String pluginLocation =mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();

 

        //start delegate service to run plugin service inside

       boolean local = PluginUtil.isLocalService(serviceInfo);

       Class<? extends Service> delegate = local ? LocalService.class :RemoteService.class;

       Intent intent = new Intent();

//啟動本地代理service

       intent.setClass(mPluginManager.getHostContext(), delegate);

       intent.putExtra(RemoteService.EXTRA_TARGET, target);

       intent.putExtra(RemoteService.EXTRA_COMMAND, command);

       intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);

        if(extras != null) {

           intent.putExtras(extras);

        }

 

       return intent;

}

 

以其中的一個service作為例子來看:

/*

 *Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd.All rights reserved.

 *

 *Licensed under the Apache License, Version 2.0 (the "License");

 * you maynot use this file except in compliance with the License.

 * You mayobtain a copy of the License at

 *

 *http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unlessrequired by applicable law or agreed to in writing, software

 *distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUTWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See theLicense for the specific language governing permissions and

 *limitations under the License.

 */

 

package com.didi.virtualapk.delegate;

 

import android.app.ActivityThread;

import android.app.Application;

import android.app.IActivityManager;

import android.app.IApplicationThread;

import android.app.IServiceConnection;

import android.app.Service;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.os.Binder;

import android.os.Build;

import android.os.IBinder;

import android.util.Log;

 

import com.didi.virtualapk.PluginManager;

importcom.didi.virtualapk.internal.LoadedPlugin;

import com.didi.virtualapk.utils.PluginUtil;

import com.didi.virtualapk.utils.ReflectUtil;

 

import java.lang.reflect.Method;

 

/**

 * @authorjohnsonlee

 */

public class LocalService extends Service {

   private static final String TAG = "LocalService";

 

    /**

     * Thetarget service, usually it's a plugin service intent

     */

    publicstatic final String EXTRA_TARGET = "target";

    publicstatic final String EXTRA_COMMAND = "command";

    publicstatic final String EXTRA_PLUGIN_LOCATION = "plugin_location";

 

    publicstatic final int EXTRA_COMMAND_START_SERVICE = 1;

    publicstatic final int EXTRA_COMMAND_STOP_SERVICE = 2;

    publicstatic final int EXTRA_COMMAND_BIND_SERVICE = 3;

    publicstatic final int EXTRA_COMMAND_UNBIND_SERVICE = 4;

 

 

   private PluginManager mPluginManager;

 

   @Override

    publicIBinder onBind(Intent intent) {

       return new Binder();

    }

 

   @Override

    publicvoid onCreate() {

       super.onCreate();

       mPluginManager = PluginManager.getInstance(this);

    }

 

   @Override

    publicint onStartCommand(Intent intent, int flags, int startId) {

        if(null == intent || !intent.hasExtra(EXTRA_TARGET) ||!intent.hasExtra(EXTRA_COMMAND)) {

           return START_STICKY;

        }

//獲取目標service

       Intent target = intent.getParcelableExtra(EXTRA_TARGET);

       int command = intent.getIntExtra(EXTRA_COMMAND, 0);

        if(null == target || command <= 0) {

           return START_STICKY;

        }

 

       ComponentName component = target.getComponent();

       LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);

        //ClassNotFoundException when unmarshalling in Android 5.1

       target.setExtrasClassLoader(plugin.getClassLoader());

       switch (command) {

           case EXTRA_COMMAND_START_SERVICE: {

               ActivityThread mainThread =(ActivityThread)ReflectUtil.getActivityThread(getBaseContext());

               IApplicationThread appThread = mainThread.getApplicationThread();

               Service service;

 

               if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)){

                    service =this.mPluginManager.getComponentsHandler().getService(component);

               } else {

                    try {

                        service = (Service)plugin.getClassLoader().loadClass(component.getClassName()).newInstance();

 

                        Application app =plugin.getApplication();

                        IBinder token =appThread.asBinder();

                        //反射呼叫serviceattach()

                        Method attach =service.getClass().getMethod("attach", Context.class,ActivityThread.class, String.class, IBinder.class, Application.class,Object.class);

                        IActivityManager am =mPluginManager.getActivityManager();

 

                        attach.invoke(service,plugin.getPluginContext(), mainThread, component.getClassName(), token, app,am);

                        service.onCreate();

                       this.mPluginManager.getComponentsHandler().rememberService(component,service);

                    } catch (Throwable t) {

                        return START_STICKY;

                    }

               }

 

               service.onStartCommand(target, 0,this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());

               break;

           }

           …………

}