Direct-Run-Apk apk免安裝運行原理與實現(一)

源碼:https://github.com/Yichou/apkrunner

 

想解決任何問題之前都得追溯根源

那麼我們來看看 apk 是如何啓動的,

首先你得安裝這個apk,完了在Launcher點擊圖標,然後apk就啓動了,

辣麼:點擊apk圖標系統做了工作?

我們從logcat觀察, 新建一個過濾器以 ActivityManager 或者 system_process 爲 Tag,清空logcat,點擊apk圖標,大概可以看到下面的日誌:

從日誌可以看出有3個步驟,

1、啓動Activity,

Intent 內容:action=android.intent.action.MAIN  category=android.intent.category.LAUNCHER,

很熟悉吧,每個Android程序主 Activity 都需要配置這2個屬性,系統也是通過這2個屬性來查找入口activity,如果你沒配置category=android.intent.category.LAUNCHER 系統就不會在桌面創建圖標

2、啓動進程,

啓動進程以運行activity,每一個app都有獨立的進程(當然不同app也可以共用一個進程,這是後話)

3、渲染UI,

就是解析 layout 生成界面並展示,然後你就看到了畫面

 

瞭解了步驟,那麼我們就需要模仿系統幹這些事情

一、創建進程

這個很簡單,我們只需要給 activity或者service配置 android:process=":app0" 參數,系統就會創建新的進程來啓動這個 activity,不然就是在app主進程啓動。android:taskAffinity=".QApp0" 參數是配置獨立任務,就是在系統任務切換界面會多出一個窗口

假設我們調用了 startActivity(this, com.apkrunner.ProxyActivity0)

二、運行Activity,

這個我們需要從apk的 AndroidManifest.xml 解析出 LAUNCHER Activity(詳見代碼),假設我們解析到:demo.LauncherActivity (後面會用)

解析到了直接調用 startAcvity(new Intent(this, demo.LauncherActivity.class)) 嗎?

NO,你一定遇到過,如果你忘記在 AndroidManifest.xml 添加Activity,啓動會崩潰

想要解決這個問題,還是隻有一個辦法,追本溯源,瞭解系統如何啓動 Activity

startActivity 最終實現調用的是(用 Eclipse 跟着源碼一步步走下去就能找到,在 android.app.Instrumentation  1419 行)

            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, null, options);

ActivityManagerNative 通過IPC 調到 system_process ActivityManagerService 服務,(此處省略1w行)系統處理完後回調app進程,最終到 ActivityThread 內部 Handler :

android.app.ActivityThread line 1190

                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

友情提示:觀看此文時請打開源碼,跟隨腳步)我們現在到了 handleLaunchActivity 

現在又到了 performLaunchActivity(ActivityClientRecord r, Intent customIntent) 

關鍵步驟來了,

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
android.app.Instrumentation newActivity line 1057
    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

這個代碼很簡單吧,就是 loadClass newInstance,反射法加載 Activity 的class,在創建一個對象,這些都是在 app 進程做的事情,既然是在 app 進程,那麼我們就完全可以控制裏面的調用參數,將 className 替換成 demo.LauncherActivity

知道了原理,那麼如何實現呢?

 

首先,我們啓動 Activity 需要傳一個 Intent 對象,AMS用來查找Activity 組件,performLaunchActivity 方法的 r 參數裏面有一個 intent 對象,這個intent 就是startActivity()方法傳遞的(這一點非常關鍵),

1.使用動態代理攔截 ActivityManagerNative 的 startActivity 方法:

判斷 Intent 參數,如果要啓動的 Activity 是未安裝的apk的,那麼把他換成宿主已聲明的,

 

	private Intent makeProxy(Intent oIntent, String proxyClass) {
		Intent intent = new Intent();
		intent.setClassName(ApkRunner.getShellPkg(), proxyClass);
		intent.putExtra(FLAG_PROXY, true);
		intent.putExtra(KEY_INTENT, oIntent);
		
		/**
		 * 加標誌過去會導致一些莫名的問題,我們就默認給他啓動一個好了 2014-4-3
		 */
//		intent.addFlags(oIntent.getFlags());
		
		return intent;
	}

把原始的 Intent 作爲一個參數存儲到 Intent ,

 

2.攔截 ActivityThread H 的 handleMessage(Message msg)方法:

用反射替換 Handler 的 callback 對象。

H 原本的 callback 對象是 null ,所以你的 callback  -> boolean handleMessage(Message msg) 要返回 false,讓系統調用原始版本。

在 LAUNCH_ACTIVITY 消息替換 (ActivityClientRecord r) r.activityInfo 和 r.intent 

activityInfo 對象的作用,看這行 line 2808

r.packageInfo = getPackageInfo(aInfo.applicationInfo

這行代碼創建了一個 LoadedApk 對象,這個對象非常關鍵,一個 apk 加載之後所有信息都保存在此對象(比如:DexClassLoader、Resources、Application),一個包對應一個對象,以包名區別,而 ActivityThread 裏設計可以緩存N個LoadedApk,以包名爲key存儲在一個Map裏。看 getPackageInfo 方法的部分代碼:

 

                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
                if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }

所以,我們需要替換 r.activityInfo ,activityInfo 使用 PackageManager getPackageArchiveInfo 創建
 

到了這裏你可能發現了,一個apk運行時有3大關鍵要素

Context Resource ClassLoader

分別是,上下文環境,資源管理器,類管理器

上下文環境通用的,

資源管理器我們需要用未安裝 apk 去創建

類管理器可以用 DexClassLoader,下面我們來一一分解

1、ClassLoader 

 

系統做法是 android.app.LoadedApk line 318

        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

		String tmpPath = mApkPath;
		

		
		FileUtils.createDir(appLibPath);
		
		mClassLoader = new DexClassLoader(tmpPath, 
				appLibPath,
				appSoPath, 
				baseParent);

用 DexClassloader,所以我們要在 LoadedApk 創建後用反射替換掉 mClassLoader 對象

 

2、Resources 

android.app.LoadedApk line 318

    public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir,
                    Display.DEFAULT_DISPLAY, null, this);
        }
        return mResources;
    }
        AssetManager assets = new AssetManager();
        if (assets.addAssetPath(resDir) == 0) {
            return null;
        }
...
        r = new Resources(assets, dm, config, compatInfo, token);

所以你知道如何創建一個 apk 的 Resources 了,對的,關鍵點就是 assets.addAssetPath(resDir)

創建好 Resources 後,替換 LoadedApk 的 mResources 對象

 

到這裏準備工作似乎已經妥當,

那麼 app啓動創建的第一個組件就是 Application

android.app.LoadedApk line 486

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

Application對象搞定後,我們模擬調用一下 onCreate() 方法,OK 大功告成,一個 apk 運行所需要的環境就搭建好了,

 

是不是感覺沒講完?

的確是沒講完,

不過大體流程已介紹,

剩下的結合源碼領悟吧

 



 

 

 

 

發佈了72 篇原創文章 · 獲贊 72 · 訪問量 57萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章