插件化解析

一、定義&理解

插件:Plug-in又稱add-in、addon或add-on,也稱外掛,它是一種遵循一定規范的應用程序接口編寫出來的程序。
我的理解就是:插件是一個程序的輔助或者擴展功能模塊,對程序來說插件可有可無,但它能給予程序一定的額外功能。

二、背景

android插件化的概念始于2012年,出自于免安裝的想法。發展到后來,android應用程序更多的需要依賴一些第三方庫,比如地圖sdk、分享sdK、支付sdk等等,導致安裝包變得越來越大,單是依賴這些sdk,安裝包可能就會額外的增加20-30M的大小;當需要新增功能時,不得不重新更新整個安裝包。所以這時插件化的需求就變得更為更為突出。
插件化主要是解決的是減少應用程序大小、免安裝擴展功能。一般具一定規模的程序應用插件化才有意義。

三、原理

插件本身是一個獨立的apk文件,要實現插件動態加載運行,需要了解apk的運行機制。java虛擬機運行的是class文件,使用ClassLoader加載;而Android虛擬機運行的是dex文件使用的是DexClassLoader,而資源是存在xml文件中,所以就會涉及到資源文件的加載過程,所以要實現插件的動態加載運行,首先就需要解決類文件、資源、庫等的加載。

1、類加載

android運行的是dex文件對應的類加載器是PathClassLoader和DexClassLoader,PathClassLoader和DexClassLoader均繼承自BaseDexClassLoader,BaseDexClassLoader又繼承自ClassLoader。
我們來看下DexClassLoader基類BaseDexClassLoader的構造函數:

    /**
     * @param dexPath 目標類所在的apk或jar文件路徑,類加載器將從這個apk或jar文件路徑查找目標類
     * @param optimizedDirectory 從apk中解壓出dex文件,經過odex優化之后,將解壓和優化后的dex文件存放到optimizedDirectory 目錄,下次直接從這個目錄中的dex文件加載目標類。注意從API26開始此參數已經廢棄
     * @param librarySearchPath  目標類所使用的c/c++庫路徑
     * @param parent 父類ClassLoader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
         // 解壓、加載dex、資源文件,保存存放dex、資源文件、庫文件路徑等
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }

問題1:為什么要傳父類的ClassLoader,有什么用?
PathClassLoader的構造函數:

 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
 }

從PathClassLoader的注釋(自己看下PathClassLoader源碼的注釋)可以知道PathClassLoader是用于加載系統和應用內部類的。
問題2:為什么PathClassLoader是用于加載系統和應用內部的?有什么依據?

DexClassLoader的構造函數:

public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        // 注意API26開始optimizedDirectory設置為空,API26之前是new File(optimizedDirectory)
        super(dexPath, null, librarySearchPath, parent);
}

從DexClassLoader的注釋自己看下DexClassLoader源碼的注釋)可以知道,DexClassLoader可以加載從指定路徑(包括Sd卡)加載apk、jar、dex文件,實現動態加載功能,所以插件化類的加載就是通過DexClassLoader實現。
我們來看下類加載的過程:

# ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先檢查類是否已經加載過(可以防止重復加載,提高效率)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 未加載過,則優先從父類的ClassLoader加載
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 在從根ClassLoader加載,android中返回空
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                   //父類加載失敗,則嘗試調用自身的findClass方法加載
                    c = findClass(name);
                }
            }
            return c;
    }

通過上面類加載的過程可以知道類的加載優先使用父類的ClassLoader進行加載,如果父類加載失敗,才通過自身去加載,這個過程即使有個術語,叫雙親委派。為什么要這樣做呢?主要是為了防止重復加載,提高效率。(這里回答了問題1)

2、資源加載

android中四大組件獲取資源一般都是通過getResources()得到Resources對象,通過Resources對象來獲取各種資源(文本、字體、顏色等),而Resources中獲取各種資源實際上都是通過AssetManager來實現的。我們來看下getString的代碼實現過程:

#Resources.java
public String getString(@StringRes int id) throws NotFoundException {
        return getText(id).toString();
}
public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
}

通過上面的代碼知道Resources的實現類是ResourceImpl類,getAssets()返回的是AssetManager,所以也就證實了資源的加載實際是通過AssetManager來加載的。

3、android四大組件加載

上面講述了類和資源的加載原理,由于Android中的四大組件均有相應的生命周期,由系統管理,所以如果單純的只是通過類加載器來加載一個組件,是無法實現生命周期相關的功能。
要怎么實現插件中的組件也具有相應的生命周期呢?下面我們以Activity為實例來講解
首先先要知道什么時候、如何加載Activity并創建Activity實例,并且什么時候創建Resources對象、如何將Resources與Activity建立關系的。
我們通過Activity的創建過程來一步一步來揭開謎底:

#ActivityThread.java
//啟動Activity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //...省略
       // 生成Context的實現類
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            //...省略
        }
        //...省略
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
        //...省略
    }

#Instrumentation.java
public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
}

// ContextImpl.java類加載器
@Override
public ClassLoader getClassLoader() {
        return mClassLoader != null ? mClassLoader : (mPackageInfo != null ?       mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}

#ClassLoader.java
public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
}
static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
} 

private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

通過上面的代碼我們知道:

  • Context的實現類是ContextImpl類
  • Activity實例是在Instrumentation中的newActivity中通過ClassLoader的實現類加載創建的。
  • 加載Activity使用的ClassLoader是PathClassLoader。(回答了問題2)

問題3:資源對象是什么時候創建的?
問題4:如何與Activity建立聯系的呢?
我們先看下Activity中getResouces()的代碼實現:

    #Activity.java
    @Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }

通過getResouces()方法知道,Activity中Resources對象是通過Context獲取的,而上面Activity的創建過程中Context是通過Activity的attach方法進行綁定的(回答了問題4)。
既然知道了Reources是通過Context的獲取的,那么我們看下Context實現類ContextImpl中Resources的創建過程:

#ActivityThread.java
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
  //...省略
  ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
  //...省略
}

#ContextImpl.java
static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
      //...省略
      //創建ContextImpl實例
      ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);
      //...省略
      // 創建ResourcesManager 對象
      final ResourcesManager resourcesManager = ResourcesManager.getInstance();

        // Create the base resources for which all configuration contexts for this Activity
        // will be rebased upon.
        //通過ResourcesManager 對象生成Resources對象并賦值給Context中的Resources
        context.setResources(resourcesManager.createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
      //...省略
}

#ResourcesManager.java
public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
       //...省略
      //獲取或者創建Resources對象
      return getOrCreateResources(activityToken, key, classLoader);
       //...省略
}

private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
      //...省略
      ResourcesImpl resourcesImpl = createResourcesImpl(key);
      //...省略
}

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        // 創建AssetManager對象
        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        //創建Resources實現類對象
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
}

@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        AssetManager assets = new AssetManager();

        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (key.mResDir != null) {
            //添加資源文件目錄
            if (assets.addAssetPath(key.mResDir) == 0) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
       //...省略
}

通過上面的代碼,我們知道Activity中的Resouces對象是在生成ContextImpl時創建的,而且是通過Resources的資源加載是通過AssetManager實現的,而資源文件目錄是通過AssetManager的addAssetPath來實現的。(回答了問題3)

結合上面的代碼分析,知道了Activity是什么時候、在哪里、如何創建的;頁知道了Activity中資源是怎么創建和建立關系的,那有什么方案可以解決插件中Activity的生命周期的問題呢?
目前市面主要有兩個方案:通過代理或通過hook。
問題5:什么是hook?

3.1、代理

問題6:代理的思想是什么或者如何實現插件Activity的生命周期?
代理的思想是在主工程放一個ProxyActivity,啟動插件的Activity時先啟動ProxyActivity,再在ProxyActivity中創建插件Activity,并同步生命周期給插件的Activity(解答了問題6),看下其原理圖:


圖1

缺點

  • 插件中的Activity必須繼承PluginActivity,開發侵入性強。
  • 如果想支持Activity的singleTask,singleInstance等啟動模式,需要自己管理Activity棧,實現起來很繁瑣。
  • 插件中需要小心處理Context,容易出錯。
  • 如果想把之前的模塊改造成插件需要很多額外的工作。
3.2、Hook:

Hook:使用技術手段在運行時動態的將額外代碼依附給現進程,從而實現替換現有處理邏輯或插入額外功能的目的
可以理解為Hook通過代理、反射等機制實現代碼的注入,從而實現對原來功能的改寫,以達到預想中的效果
此處回答了問題5
我們再看下Activity的整體啟動流程圖:


圖2

結合Activity創建過程的代碼分析和上圖的流程中,我們可以通過hook步驟1和步驟10來實現插件Activity的生命周期等相關功能。代理的方式時因為浸入性太強,hook的方式需要考慮浸入性的問題,其次我們在開發應用的時候,如果Activity不再Manifest中注冊,當正常方式啟動Activity時就會拋出找不到Activity的錯誤信息,所以
通過hook的方式啟動插件Activity需要解決如下問題:
a、插件Activity如何繞開Manifest中注冊的檢測
b、如何創建Activity實例,并同步生命周期
我們通過VirtualApk插件化框架來看其實現方案:
a、預先在Manifest中注冊各種啟動模式的Activity占坑,啟動時hook第1步,將Intent根據啟動模式替換成預先在Manifest占坑的Activity,這樣就解決了Manifest中注冊的檢測
b、hook第10步,使用插件的ClassLoader反射創建插件Activity,之后Activity的生命周期回調都通知給插件Activity,這樣就解決了創建Activity并同步生命周期的問題

3.2.1、替換系統Instrumentation

VirtualApk在初始化時會調用hookInstrumentationAndHandler(),該方法hook了系統的Instrumentation(該類與Activity啟動相關):

protected void hookInstrumentationAndHandler() {
        try {
            // 獲取ActivityThread對象
            ActivityThread activityThread = ActivityThread.currentActivityThread();
            // 獲取ActivityThread中的Instrumentation
            Instrumentation baseInstrumentation = activityThread.getInstrumentation();
            // 創建自定義的VAInstrumentation,具有復寫父類一些函數且具有代理功能
            final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
            // 反射的機制替換ActivityThread中的Instrumentation
            Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
            Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
            Reflector.with(mainHandler).field("mCallback").set(instrumentation);
            this.mInstrumentation = instrumentation;
            Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }

上面代碼,創建自定義的Instrumentation,通過反射替換了ActivityThread中的Instrumentation

3.2.2、使用自定義統Instrumentation啟動Activity

復寫3.2.1自定義Instrumentation的execStartActivity,

@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {
        injectIntent(intent);
        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}

protected void injectIntent(Intent intent) {
        // 處理隱身啟動,如果匹配到插件Activity,則隱身啟動替換為顯示啟動
        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中的ClassName替換為占坑中的Activity,解決啟動時是否
            //在Manifest中注冊的校驗
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        }
    }

public void markIntentIfNeeded(Intent intent) {
        if (intent.getComponent() == null) {
            return;
        }

        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();
        // search map and return specific launchmode stub activity
        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
            intent.putExtra(Constants.KEY_IS_PLUGIN, true);
            intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
            intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
            dispatchStubActivity(intent);
        }
    }

execStartActivity時,先處理隱身啟動,如果匹配到插件Activity則替換為顯示啟動;接著通過markIntentIfNeeded將待啟動的插件Activity替換為預先在Manifest占坑的Activity,并將插件Activity信息保存在Intent中。其中有個dispatchStubActivity函數,會根據Activity的launchMode選擇具體啟動哪個StubActivity。VirtualAPK為了支持Activity的launchMode在主工程的AndroidManifest中對于每種啟動模式的Activity都預埋了多個坑位。

3.2.3、hook創建Activity

上一步欺騙了系統,讓系統以為啟動的是一個正常的Activity。這一步切換回插件的Activity,調用VAInstrumentation的newActivity:

@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            cl.loadClass(className);
            Log.i(TAG, String.format("newActivity[%s]", className));
            
        } catch (ClassNotFoundException e) {
            ComponentName component = PluginUtil.getComponent(intent);
            
            if (component == null) {
                return newActivity(mBase.newActivity(cl, className, intent));
            }
          
            String targetClassName = component.getClassName();
            Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));
            //根據component獲取對應的插件
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
            // 插件對象為空時,使用默認的創建方式
            if (plugin == null) {
                // Not found then goto stub activity.
                boolean debuggable = false;
                try {
                    Context context = this.mPluginManager.getHostContext();
                    debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                } catch (Throwable ex) {
        
                }
    
                if (debuggable) {
                    throw new ActivityNotFoundException("error intent: " + intent.toURI());
                }
                
                Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
                return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
            }
            //使用插件的ClassLoader
            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
            activity.setIntent(intent);
            
            //設置activity的資源對象
            // for 4.1+
            Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
            //返回創建的Activity
            return newActivity(activity);
        }

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

由于Manifest中注冊的占坑Activity沒有實現類,所以這里會報ClassNotFoundException 異常,在處理異常中取插件Activity的信息,使用插件ClassLoader反射創建插件Activity

3.2.4、設置Activity的資源Resources對象

插件Activity的構造創建之后,還需要做一些額外的操作方才可以正常運行,比如設置訪問資源所使用的Resources對象、Context等:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
        injectActivity(activity);
        mBase.callActivityOnCreate(activity, icicle, persistentState);
}

protected void injectActivity(Activity activity) {
        final Intent intent = activity.getIntent();
        if (PluginUtil.isIntentFromPlugin(intent)) {
            
            Context base = activity.getBaseContext();
            try {
                //根據Intent獲取對應的插件對象
                LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
                //反射設置Resources對象為插件Resources
                Reflector.with(base).field("mResources").set(plugin.getResources());
                Reflector reflector = Reflector.with(activity);
                // 反射設置Context為插件的Context, 其實就是主工程的Context
                reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
                // 反射設置Application為插件的Application,其實就是主工程的Application
                reflector.field("mApplication").set(plugin.getApplication());
                
                //設置橫豎屏
                // set screenOrientation
                ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
                if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                    activity.setRequestedOrientation(activityInfo.screenOrientation);
                }
                
                //重新設置Intent
                // for native activity
                ComponentName component = PluginUtil.getComponent(intent);
                Intent wrapperIntent = new Intent(intent);
                wrapperIntent.setClassName(component.getPackageName(), component.getClassName());
                activity.setIntent(wrapperIntent);
                
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        }
    }

這段代碼主要是將Activity中的Resource,Context等對象替換成了插件的相應對象,保證插件Activity在調用涉及到Context的方法時能夠正確運行。

至此便實現了插件Activity的啟動,且此插件Activity中并不需要什么額外的處理,和常規的Activity一樣。那問題來了,之后的onResume,onStop等生命周期怎么辦呢?答案是所有和Activity相關的生命周期函數,系統都會調用插件中的Activity。原因在于AMS在處理Activity時,通過一個token表示具體Activity對象,而這個token正是和啟動Activity時創建的對象對應的,而這個Activity被我們替換成了插件中的Activity,所以之后AMS的所有調用都會傳給插件中的Activity。

其他組件

四大組件中Activity的支持是最復雜的,其他組件的實現原理要簡單很多,簡要概括如下:

  • Service:Service和Activity的差別在于,Activity的生命周期是由用戶交互決定的,而Service的生命周期是我們通過代碼主動調用的,且Service實例和manifest中注冊的是一一對應的。實現Service插件化的思路是通過在manifest中預埋StubService,hook系統startService等調用替換啟動的Service,之后在StubService中創建插件Service,并手動管理其生命周期。
  • BroadCastReceiver:解析插件的manifest,將靜態注冊的廣播轉為動態注冊。
  • ContentProvider:類似于Service的方式,對插件ContentProvider的所有調用都會通過一個在manifest中占坑的ContentProvider分發。

七、插件間、插件與主程序間的交互

上面解決了插件化的原理,那么插件與插件之間、插件與主工程之間如何相互調用、相互訪問資源呢?
相互調用對應的是類的調用,而每個類都需要對應的ClassLoader來加載,所以一般就分為兩種調用機制:

  • 單ClassLoader機制:將插件中DexClassLoader的pathList合入主工程的pathList中,這樣主工程就可以調用插件的類和方法,插件調用另一個插件則需要借助主工程或者插件框架統一訪問接口來實現。單ClassLoader的弊端就是如果插件使用不同版本的庫就會出現問題。
  • 多ClassLoader機制:每個插件對應一個DexClassLoader,那么主工程調用插件的類和方法就需要借助插件的ClassLoader,一般需要進行插件化框架進行統一管理。
    資源的訪問也是有兩種方式:
  • 合入式:將插件的資源路徑合入主工程的AssetManager中,因此生成的Resources可以同時訪問插件和主工程的資源,弊端是由于插件和主工程是獨立編譯的,所有會存在資源id沖突的情況(解決資源Id沖突需要通過更改編譯過程修改資源Id,資源id是由8位16進制數表示,表示為0xPPTTNNNN。PP段用來區分包空間,默認只區分了應用資源和系統資源,TT段為資源類型,NNNN段在同一個APK中從0000遞增。其中系統的pp段的值是0x01,應用pp段的值是0x7f;系統和應用的tt段值都是0x04,剩下的NNNN值在0x0000至0xfffff之間)
  • 獨立式:資源隔離,不存在沖突情況,但是資源共享比較麻煩,需要借助統一接口進行管理,會存在資源在不同插件重復存在的情況。

八、幾種成熟的插件框架

圖3

第一代:dynamic-load-apk最早使用ProxyActivity這種靜態代理技術,由ProxyActivity去控制插件中PluginActivity的生命周期。該種方式缺點明顯,插件中的activity必須繼承PluginActivity,開發時要小心處理context。而DroidPlugin通過Hook系統服務的方式啟動插件中的Activity,使得開發插件的過程和開發普通的app沒有什么區別,但是由于hook過多系統服務,異常復雜且不夠穩定。

第二代:為了同時達到插件開發的低侵入性(像開發普通app一樣開發插件)和框架的穩定性,在實現原理上都是趨近于選擇盡量少的hook,并通過在manifest中預埋一些組件實現對四大組件的插件化。另外各個框架根據其設計思想都做了不同程度的擴展,其中Small更是做成了一個跨平臺,組件化的開發框架。

第三代:VirtualApp比較厲害,能夠完全模擬app的運行環境,能夠實現app的免安裝運行和雙開技術。Atlas是阿里今年開源出來的一個結合組件化和熱修復技術的一個app基礎框架,其廣泛的應用與阿里系的各個app,其號稱是一個容器化框架。

九、參考

深入理解Android插件化技術

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容