Direct-Run-Apk apk免安裝直接啟動原理與實現(一),direct-run-apkapk

來源:互聯網
上載者:User

Direct-Run-Apk apk免安裝直接啟動原理與實現(一),direct-run-apkapk

我想這個應該很多人探討過,我13年的時候就把這個架構做出來了,可以直接運行80%的apk,不過本人比較懶一直沒去寫文章總結其中一些心得,今天給大家分享一下,


類似項目 http://git.oschina.net/lody/Direct-load-apk


首先我們來探討一個 apk 是如何啟動的,

OK,首先你得安裝這個apk,然後你點擊表徵圖,結果apk就啟動了,看到了畫面,完成。


第一個問題:點擊apk表徵圖的時候系統做了什麼事情?

開啟 logcat 建立一個過濾器以 ActivityManager 為 Tag,清空logcat,點擊apk表徵圖,你可以看到下面的日誌:


第一條日誌是啟動Activity,Intent 內容:

action=android.intent.action.MAIN 

category=android.intent.category.LAUNCHER,

所以你明白了為什麼你的主 Activity 需要這樣配置。

第二條日誌是啟動進程,

所以點擊apk圖片後系統做了2件事情:1.啟動一個進程; 2.在這個進程裡啟動主Activity


不過我們今天要討論的是不安裝apk啟動,不安裝怎麼啟動,總得有個入口給我吧,

是的我們需要一個宿主:想象一個檔案管理工具,在列表裡點擊 apk 直接啟動,這個檔案管理工具就是我們的宿主,

那你非要問可以不要宿主嗎?

答案是,不可以,我也沒辦法,沒有入口你怎麼去操作!


下面我們要討論的問題是宿主如何? apk 直接啟動功能。

第二個問題:宿主如何? apk 直接啟動功能

第一個問題裡我們已經闡述了apk如何啟動的,那麼我們需要做的就是類比系統所做的工作。

首先,啟動apk啟動什麼,一個Activity,那麼好我們從未安裝的apk AndroidManifest.xml 解析出啟動Activity,然後調用 startActivity 不就 ok 嗎,對不起,崩潰,Activity 未在 AndroidManifest.xml 配置!


所以我們只能啟動一個再宿主中已經配置的Activity,

細究Activity如何啟動,抓根本,startActivity 最終實現調用的是(不要問我如何找到的,在 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);
這個方法直接調到了 ActivityManagerService (AMS)系統進程,響應完後,再回到應用進程,回調到 ActivityThread 內部 Handler H:

                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;

android.app.ActivityThread.handleLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2217

  android.app.ActivityThread.performLaunchActivity(ActivityClientRecord r, Intent customIntent) line:2077

        Activity activity = null;        try {            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();            activity = mInstrumentation.newActivity(                    cl, component.getClassName(), r.intent);
    public Activity newActivity(ClassLoader cl, String className,            Intent intent)            throws InstantiationException, IllegalAccessException,            ClassNotFoundException {        return (Activity)cl.loadClass(className).newInstance();    }
思路很簡單,通過反射建立 activity 對象,注意哦這是在應用進程,聰明的你可能想到了,啟動一個在宿主配置的activity,回調建立activity對象的時候換成真正的Activity對象不就OK麼

下面我們來探討這個如何?,


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

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

(至於Java動態代理技術自己百度)判斷 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 建立

接下來就是 LoadedApk 幾個關鍵對象的建立:

  • 1.ClassLoader 代碼管理器

android.app.LoadedApk line 318

                mClassLoader =                    ApplicationLoaders.getDefault().getClassLoader(                        zip, libraryPath, mBaseClassLoader);.............            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);            return pathClassloader;
如果使用 PathClassLoader 去建立是不可行的,好在 android 開放了一個 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 對象

  • 3.Application 每個apk啟動建立的第一個組件就是 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 運行所需要的環境就搭建好了,

接下來就是使用偷梁換柱方法把  apk 的主 activity 啟動起來。


當然,隻言片語很難講解這套架構的實現,其中還有很多細節需要處理


不過原理就是這些,偷梁換柱!


今天先講到這,架構源碼到時候會放出來







聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.