Android插件化探索(四)免安裝運行Activity(下)

轉載請註明本文出自maplejaw的博客(http://blog.csdn.net/maplejaw_
【Android插件化探索(一)類加載器DexClassLoader】
【Android插件化探索(二)資源加載】
【 Android插件化探索(三)免安裝運行Activity(上)】


在上一篇中,我們介紹了兩種免安裝啓動Activity的方法。但是那兩種方法都有缺陷,必須在AndroidManifest.xml中註冊。那麼今天,我們來探索其它幾種不需要在清單文件中註冊的啓動方式。

靜態代理啓動activity

通過前幾篇的探索我們知道,通過DexClassLoader可以加載類,通過AsserManager可以加載資源。但是Activity確有一個令人苦惱的問題——生命週期。
我們知道宿主中的Activity都有生命週期,那我們可不可以藉助宿主Activity來借屍還魂?

首先我們在宿主中定義一個Activity,取名爲ProxyActivity,並且在宿主清單中註冊用來佔坑。這個時候ProxyActivity是有生命週期的,這點毋容置疑。那麼我們現在只需把插件中的Activity當做一個普通的類反射調用即可,既然是一個普通類那麼setContentView,findViewById自然也沒有效果了,所以需要調用ProxyActivity的setContentView。也就是說,其實我們每次啓動的Activity是ProxyActivity,佈局也是加載到ProxyActivity,findViewById也是從ProxyActivity中尋找,然後在ProxyActivity的各個生命週期被調用的時候反射調用插件中的相應方法。

說的可能有點抽象,還是直接上代碼吧,ProxyActivity的代碼如下。

public class ProxyActivity extends Activity {

    public static final String EXTRA_DEX_PATH = "extra_dex_path";
    public static final String EXTRA_ACTIVITY_NAME = "extra_activity_name";

    private String mClass;
    private String mDexPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //獲取插件dex路徑
        mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
        //要啓動的Activity的完整類名
        mClass = getIntent().getStringExtra(EXTRA_ACTIVITY_NAME);
        //加載資源
        loadResources(mDexPath);
        //啓動插件Activity
        performLaunchActivity(savedInstanceState);
    }

    protected void performLaunchActivity(Bundle savedInstanceState) {
        File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
        //初始化classloader
        DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
                dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());

        //注意:以下只是把插件中的Activity當作一個普通的類進行反射調用
        try {
            Class<?> localClass = dexClassLoader.loadClass(mClass);
            Constructor<?> localConstructor = localClass
                    .getConstructor();
            Object instance = localConstructor.newInstance();//初始化插件Acitivity對象。

            //獲取插件Activity的setProxy方法,這個方法是我們事先在插件中約定好的
            Method setProxy = localClass.getMethod("setProxy",
                    Activity.class);
            setProxy.setAccessible(true);
            //調用插件Activity的setProxy方法
            setProxy.invoke(instance, this);//將ProxyActivity對象傳給插件Activity,用於setContentView等等

            //獲取插件Activity中的onCreate方法。
            Method onCreate = localClass.getDeclaredMethod("onCreate", Bundle.class);
            onCreate.setAccessible(true);
            //調用插件Activity中的onCreate方法。
            onCreate.invoke(instance, savedInstanceState);//將savedInstanceState傳給插件
        } catch (Exception e) {
            e.printStackTrace();
        }
    }




    //替換資源。
    private AssetManager mAssetManager;
    private Resources.Theme mTheme;
    protected void loadResources(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();

        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }


    private Resources mResources;
    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }
    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }

    }

然後在插件中定義一個BaseActivity。讓其他Acitivity實現它即可。

public class BaseActivity extends Activity {

    public static final String EXTRA_DEX_PATH = "extra_dex_path";
    public static final String EXTRA_ACTIVITY_NAME = "extra_activity_name";


    protected Activity that;  //指向插件Activity



    /**
     * 將代理Activity傳給插件Activity
     * @param proxyActivity
     */
    public void setProxy(Activity proxyActivity) {
        that = proxyActivity;  
    }  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
       //由於插件Activity已經不是真正意義上的Activity了,這裏屏蔽掉super.onCreate。
    }
    //由於插件Activity已經不是真正意義上的Activity了,只能把佈局給ProxyActivity來顯示
    @Override  
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }  
}

接下來,我們的插件中的Activity就可以這麼寫。

public class TestActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_test);
    }


}

宿主中啓動代碼,如下。

                String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";

                //獲得包管理器
                PackageManager pm = getPackageManager();
                PackageInfo packageInfo=pm.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
                String packageName=packageInfo.packageName;

                //啓動Activity
                Intent intent=new Intent(this,ProxyActivity.class);
                intent.putExtra(ProxyActivity.EXTRA_DEX_PATH,path);
                intent.putExtra(ProxyActivity.EXTRA_ACTIVITY_NAME,packageName+".TestActivity");
                startActivity(intent);

經過測試,我們的插件Activity借屍還魂的被啓動了。當然上面只反射了onCreate方法。爲了使插件Activity具有完整的生命週期,我們還需反射onStart,onResume等等。通常我們會定義一個map來保存,然後再調用即可。

    private HashMap<String,Method> mActivityLifecircleMethods=new HashMap<>();
    protected void instantiateLifecircleMethods(Class<?> localClass) {

        String[] methodNames = new String[] {
                "onRestart",
                "onStart",
                "onResume",
                "onPause",
                "onStop",
                "onDestroy"
        };
        for (String methodName : methodNames) {
            Method method = null;
            try {
                method = localClass.getDeclaredMethod(methodName);
                method.setAccessible(true);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            mActivityLifecircleMethods.put(methodName, method);
        }
        }

     @Override
   protected void onStart() {
        Method method= mActivityLifecircleMethods.get("onStart");
        if(method!=null){
         try {
            method.invoke(mRemoteActivity);
        } catch (Exception e) {
            e.printStackTrace();
       }
        }

        super.onStart();
    }
    //.....
    //省略了部分源碼

但是這種方法有個缺陷,由於插件Activity已經不是真正意義上的Activity了,也就是說原本Activity的findViewById、setContentView和startActivity等等都已經不起作用,只能間接調用ProxyActivity的方法。換言之,如果BaseActivity沒有重寫setContentView使其指向ProxyActivity,那麼其子類將不能使用this語法。全部改用that。that.findViewById,that.setContentView,that.startActivity等等。

替換Instrumentation

上面方法有一個缺陷,由於插件中的Activity已經不是真正意義的Activity,導致其嚴重依賴that語法。雖然說,比起之前的那些方法已經很好用了。但是,有沒有更好用的方法?在介紹其他方法之前,我們來看一下Activity的啓動過程。

startActivity源碼解讀

startActivity開始。

    @Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }

       @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

最終會走startActivityForResult方法。

    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (mParent == null) {
            //Instrumentation執行啓動Activity
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);

            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);

        } else {
            //內部也是調用Instrumentation的execStartActivity
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {

                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

可以看出,startActivityForResult內部調用了Instrumentation的execStartActivity方法。execStartActivity如下。

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        //...
        //省略了部分源碼
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess();
            //調用ActivityManagerNative的startActivity
            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;
    }

內部調用了ActivityManagerNative,getDefault().startActivity,ActivityManagerNative是一個Binder對象,連接着ActivityManagerService。而在ActivityManagerService中最終會調用ActivityStackSupervisor中的startActivityMayWait方法。ActivityStackSupervisor是一個Activity棧管家,其作用不言而喻,即是用來管理Activity的棧的。

 final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
            Bundle options, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {

            //..
            //省略了部分源碼
     int res = startActivityLocked(caller, intent, resolvedType, aInfo,
                    voiceSession, voiceInteractor, resultTo, resultWho,
                    requestCode, callingPid, callingUid, callingPackage,
                    realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
                    componentSpecified, null, container, inTask);
             //..
            //省略了部分源碼
        }

startActivityMayWait方法內部又調用了startActivityLocked,總之經歷了一系列的權限驗證和棧管理,最終調用realStartActivityLocked方法。

   final boolean realStartActivityLocked(ActivityRecord r,
            ProcessRecord app, boolean andResume, boolean checkConfig)
            throws RemoteException {

            //..
            //省略了部分源碼
            //調用ApplicationThread.scheduleLaunchActivity
            app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                    new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
                    task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                    newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

            //..
            //省略了部分源碼
        return true;
    }

從上面可以看出會調用app.thread.scheduleLaunchActivity,那麼app.thread是什麼呢?其實是一個客戶端Binder對象,即ApplicationThread。是在mInstrumentation.execStartActivity中傳遞過去的,不記得回頭看一下源碼。ApplicationThread的相關源碼如下。

        @Override
   public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);
            //Handler發送消息
            sendMessage(H.LAUNCH_ACTIVITY, r);
        }

scheduleLaunchActivity中將相關信息包裝到ActivityClientRecord然後傳到了Handler中,Handler中相關源碼如下。

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    //處理啓動Activity
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
      //..
      //省略了部分源碼

處理啓動相關的代碼在handleLaunchActivity中。


    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          //..
          //省略了部分源碼

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        WindowManagerGlobal.initialize();

        //啓動Activity
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

            if (!r.activity.mFinished && r.startsNotResumed) {

                try {
                    r.activity.mCalled = false;
                    mInstrumentation.callActivityOnPause(r.activity);

                    if (r.isPreHoneycomb()) {
                        r.state = oldState;
                    }
                    if (!r.activity.mCalled) {
                        throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                            " did not call through to super.onPause()");
                    }

                } catch (SuperNotCalledException e) {
                    throw e;

                } catch (Exception e) {
                    if (!mInstrumentation.onException(r.activity, e)) {
                        throw new RuntimeException(
                                "Unable to pause activity "
                                + r.intent.getComponent().toShortString()
                                + ": " + e.toString(), e);
                    }
                }
                r.paused = true;
            }
        } else {

            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {

            }
        }
    }

而真正執行啓動Activity的代碼在performLaunchActivity中。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {


        //..
        //省略了部分源碼

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //初始化Activity對象(傳入classloader,類名,intent)。
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            //如果Application未初始化就先初始化Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);


            if (activity != null) {
                 //初始化ContextImpl
                Context appContext = createBaseContextForActivity(r, activity);
                //初始化標題
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);//設置主題
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    //調用onCreate
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
              //..
              //省略了部分源碼

        return activity;
    }

代碼有點長。核心的地方在這裏mInstrumentation.newActivity,通過Instrumentation來實例化一個Activity。
其實Activity的啓動流程我們可以簡化一下。

  1. Activity中執行startActivity
  2. Instrumentation執行execStartActivity
  3. AMS進行一系列的權限驗證和棧管理。
  4. Instrumentation執行newActivity,實例化Activity。

而且很容易能看出來,啓動插件Activity第3步無法通過。
那麼現在該怎麼辦?既然第3步實在瞞不過去,也就是說必須想辦法讓第3步中的Activity通過驗證之後,我們才能動歪腦筋。
既然這樣,我們的思路跟ProxyActivity是一樣的,搞一個佔坑的Activity。要想啓動插件中的Activity,最後一定要想辦法把它替換成插件Activity。也就是在最後一步動手腳,在Instrumentation執行newActivity時替換成插件Activity。

想法很美好。怎麼實現呢?

實現方式

還記得我們在上一篇中替換ClassLoader和合並DexElement嗎?那我們就故技重施,替換掉Instrumentation,這樣我們就能爲所欲爲了,想想就有點小激動。修改方法如下。

    private void hookInstrumentation(String path){
        try {

            File codeDir=getDir("dex", Context.MODE_PRIVATE);
            //創建類加載器,把dex加載到虛擬機中
            ClassLoader classLoader = new DexClassLoader(path,codeDir.getAbsolutePath() ,null,
                    this.getClass().getClassLoader());



            //獲取ActivityThread的Class
            Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
            //獲取ActivityThread對象
            Method currentActivityThreadMethod=activityThreadCls.getMethod("currentActivityThread");
            Object currentActivityThread= currentActivityThreadMethod.invoke(null);

            // 反射獲取Instrumentation
            Field mInstrumentationField = activityThreadCls.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            //  Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

            //反射修改Instrumentation

            Instrumentation hookInstrumentation = new HookInstrumentation(classLoader);
            mInstrumentationField.set(currentActivityThread, hookInstrumentation);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

可以看出我們替換成了自己需要的HookInstrumentation,HookInstrumentation的代碼如下。

public class HookInstrumentation extends Instrumentation {
    private ClassLoader mClassLoader;
    public HookInstrumentation(ClassLoader classLoader){
        this.mClassLoader=classLoader;
    }
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {

              String cls=intent.getStringExtra(HookUtil.EXTRA_ACTIVITY_NAME);
             if(cls!=null){

              cl=mClassLoader;//替換Classloader
              className = cls//替換className

             }


        return super.newActivity(cl, className, intent);
    }
}

宿主中的啓動代碼如下。

 String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";
                //修改Instrumentation
                hookInstrumentation( path);
                //啓動Activity
                Intent intent=new Intent(this,ProxyActivity.class);
                intent.putExtra(HookUtil.EXTRA_ACTIVITY_NAME,"com.maplejaw.hotplugin.PluginActivity");
                startActivity(intent1);

測試通過。但是這個方法跟上篇同樣存在資源加載問題。上篇我們用了反射修改LoadedApk中的資源目錄。但是那種方法的弊端我們也提過了。所以這裏換種思路。在插件的Activity中加入loadResources即可,一個Activity一個Context。那我們就修改所有插件Activity的資源指向目錄。

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
        String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";
        loadResources(path);
        super.onCreate(savedInstanceState);
    }

當然實際應用中我們不可能每次都去讀SD卡,用map之類的保存即可。
經測試,成功啓動Activity,當然爲了支持插件內部的Activity跳轉,我們還需反射修改execStartActivity方法。

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        //如果是啓動插件,則修改intent
        wrapIntent(who, intent);

        try {
            // 由於這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity", Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("do not support!!!" + e.getMessage());
        }
    }

最後

關於免安裝啓動Activity的方法探索完了。
其中dynamic-load-apk使用了ProxyActivity這種方式,Small使用了修改Instrumentation方式。


本文源碼地址:https://github.com/maplejaw/HotPluginDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章