Android插件化技術之旅 1 開篇 - 實現啓動插件與調用插件中的Activity和Service

前言

Android技術現在已很成熟了,組件化、插件化、熱修復等等框架層出不窮,若是隻停留在單純的會用框架上,技術永遠得不到成長,只有懂得其原理,可以婉婉道來,可以本身手動寫出,技術纔會獲得成長,與其焦慮將來,不如把握如今。本篇將手寫教你們寫出插件化框架,插件化技術是Android高級工程師必備的技術之一,懂其思想,知其原理。本篇專題將由10篇文章來詳細的講解插件化技術,深耕一個技術領域,才能懂得如何更廣闊的橫向發展。git

本專題代碼地址github

什麼是插件化?

插件化技術的起源於免安裝運行APK的想法,這個免安裝的APK就是一個插件,支持插件化的app能夠在運行時加載和運行插件,這樣即可以將app中一些不經常使用的功能模塊作成插件,一方面減少了安裝包的大小,另外一方面能夠實現app功能的動態擴展。bash

在過去幾年有一款叫23code的應用,不知你們是否知道,這款應用大火在於,它能夠直接下載demo並運行起來,這能夠說是最先的插件化應用。app

如今並無什麼新的插件化項目了,比較流行的框架有以下:框架

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

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

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

插件化的將來:去年比較火的ReactNative,儘管它不可能成爲最終方案,可是移動應用Web化是一個必然的趨勢,就好像曾經的桌面應用由C/S到B/S的轉變。從Google今年推崇的Flutter,移動應用愈來愈趨向於Web化。ui

插件化與組件化的區別?

至於什麼是組件化,你們能夠看我寫的關於組件化的專題this

組件化是將各個模塊分紅獨立的組件,每一個組件都要打進apk包中。

而插件化各個組件均可以單獨打成apk包,在須要時下載apk包,並加載運行。

插件化原理

1 設計接納標準,符合這個標準的app都能接入進來,該標準跟生命週期相關。

2 設計好插件遵循這個標準,因爲插件沒有安裝(上下文均不可用),須要給插件傳遞一個上下文。

3 用一個空殼Activity 放入插件內容。

4 反射獲得Apk裏面MainActivity的全類名。

5 加載

image.png

從上圖能夠看出,啓動一個差價,核心代碼就是ProxyActivity和PluginInterfaceActivity.下面咱們着重講解核心思想的代碼。

代碼其實很簡單,啓動插件的過程:

  1. 首先須要一個空殼的ProxyActivity來啓動插件的Activity。
  2. ProxyActivity根據插件apk包的信息,拿到插件中的ClassLoader和Resource,而後經過反射並建立MainActivity 轉換爲PluginInterfaceActivity,並調用其onCreate方法。
  3. 插件調用的setContentView被重寫了,會去調用ProxyActivity的setContentView,因爲ProxyActivity的getClassLoader和gerResource被重寫是插件中的resource,因此ProxyActivity的setContentView可以訪問插件中的資源,findViewById一樣也是調用ProxyActivity的。
  4. ProxyActivity中的其餘生命週期回調函數中調用相應PluginActivity的生命週期。

image.png

直接上代碼

下面代碼定義了插件Activity必需要實現的一個接口,也可說定義的一個標準,因爲插件並無安裝到手機上,沒法拿到上下文,生命週期天然也沒法調用,咱們須要將宿主的一個空殼ProxyActivity,將生命週期傳遞給插件。插件須要用到上下文的方法都須要重寫。

public interface PluginInterfaceActivity {

    /**
     * 上下文的傳遞 經過上下文來啓動Activity
     *
     * @param activity
     */
    void attach(Activity activity);

    //---------------------- 生命週期 傳遞到插件中 -------------------------//

    void onCreate(@Nullable Bundle savedInstanceState);

    void onStart();

    void onResume();

    void onPause();

    void onStop();

    void onRestart();

    void onDestroy();

    void onSaveInstanceState(Bundle outState);

    boolean onTouchEvent(MotionEvent event);

    void onBackPressed();
}
複製代碼

重寫必需要用到的方法,插件的Activity都須要繼承BaseActivity

public class BaseActivity extends AppCompatActivity implements PluginInterfaceActivity {

    protected Activity mActivity;

    @Override
    public void attach(Activity proxyActivity) {
        this.mActivity = proxyActivity;
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {

    }

    @Override
    public void onStart() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onRestart() {

    }

    @Override
    public void onDestroy() {

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {

    }

    //--------------------------凡事用到上下文的地方都須要重寫,須要重寫的方法-----------------------------//

    /**
     * 這裏咱們要使用宿主傳遞過來的上下文進行加載view
     *
     * @param view
     */
    @Override
    public void setContentView(View view) {
        if (mActivity != null) {
            mActivity.setContentView(view);
        } else {
            super.setContentView(view);
        }
    }

    @Override
    public void setContentView(int layoutResID) {
        if (mActivity != null) {
            mActivity.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }

    @Override
    public <T extends View> T findViewById(int id) {
        if (mActivity != null) {
            return mActivity.findViewById(id);
        } else {
            return super.findViewById(id);
        }
    }

    @Override
    public Intent getIntent() {
        if (mActivity != null) {
            return mActivity.getIntent();
        }
        return super.getIntent();
    }

    @Override
    public ClassLoader getClassLoader() {
        if (mActivity != null) {
            return mActivity.getClassLoader();
        }
        return super.getClassLoader();
    }

    @Override
    public LayoutInflater getLayoutInflater() {
        if (mActivity != null) {
            return mActivity.getLayoutInflater();
        }
        return super.getLayoutInflater();
    }

    @Override
    public ApplicationInfo getApplicationInfo() {
        if (mActivity != null) {
            return mActivity.getApplicationInfo();
        }
        return super.getApplicationInfo();
    }

    @Override
    public Window getWindow() {
        return mActivity.getWindow();
    }


    @Override
    public WindowManager getWindowManager() {
        return mActivity.getWindowManager();
    }

    @Override
    public void startActivity(Intent intent) {
        if (mActivity != null) {
            //插件的launchMode 只可以是標準的
            Intent intent1 = new Intent();
            intent1.putExtra("className", intent.getComponent().getClassName());
            mActivity.startActivity(intent1);
        } else {
            super.startActivity(intent);
        }
    }

    @Override
    public ComponentName startService(Intent service) {
        if (mActivity != null) {
            Intent m = new Intent();
            m.putExtra("serviceName", service.getComponent().getClassName());
            return mActivity.startService(m);
        } else {
            return super.startService(service);
        }
    }
}
複製代碼

下面來看宿主是如何啓動插件的呢? 經過一個空殼的Activity ProxyActivity代理的方式最先是由dynamic-load-apk提出的,其思想很簡單,在主工程中放一個ProxyActivy,啓動插件中的Activity時會先啓動ProxyActivity,在ProxyActivity中建立插件Activity,並同步生命週期。

public class ProxyActivity extends AppCompatActivity {

    //須要加載插件的全類名
    private String className;

    private PluginInterfaceActivity pluginInterfaceActivity;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");
//        Class.forName()
        //插件並無被安裝到手機上的
        try {
            //className 表明activity的全類名
            Class activityClass = getClassLoader().loadClass(className);
            //調用構造函數
            Constructor constructors = activityClass.getConstructor(new Class[]{});
            //獲得Activity對象
            Object newInstance = constructors.newInstance(new Object[]{});
            //最好不要反射 onCreate()
            //經過標準來
            pluginInterfaceActivity = (PluginInterfaceActivity) newInstance;
            //注入上下文
            pluginInterfaceActivity.attach(this);
            Bundle bundle = new Bundle();//將一些信息傳遞
            bundle.putString("test", "我是宿主給你傳遞數據");
            pluginInterfaceActivity.onCreate(bundle);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

    @Override
    public ComponentName startService(Intent service) {
        String serviceName = service.getStringExtra("serviceName");
        Intent intent = new Intent(this, ProxyService.class);
        intent.putExtra("serviceName", serviceName);
        return super.startService(intent);
    }

    //對外
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        pluginInterfaceActivity.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        pluginInterfaceActivity.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        pluginInterfaceActivity.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        pluginInterfaceActivity.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        pluginInterfaceActivity.onDestroy();
    }
}
複製代碼

啓動插件的代碼以下:

public void loadPlugin(View view) {
        loadDownPlugin();
        Intent intent = new Intent(this, ProxyActivity.class);
        //獲得全類名
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }
複製代碼

其實,啓動插件就是啓動宿主的一個空殼的Activity,將這個空殼的Activity的上文和生命週期傳遞到插件的Activity。

這即是啓動插件的核心代碼:

//className 表明activity的全類名
            Class activityClass = getClassLoader().loadClass(className);
            //調用構造函數
            Constructor constructors = activityClass.getConstructor(new Class[]{});
            //獲得Activity對象
            Object newInstance = constructors.newInstance(new Object[]{});
            //最好不要反射 onCreate()
            //經過標準來
            pluginInterfaceActivity = (PluginInterfaceActivity) newInstance;
            //注入上下文
            pluginInterfaceActivity.attach(this);
            Bundle bundle = new Bundle();//將一些信息傳遞
            bundle.putString("test", "我是宿主給你傳遞數據");
            pluginInterfaceActivity.onCreate(bundle);
複製代碼

其實咱們就是啓動了宿主中的一個空殼Activity,而後加載插件APK包中的資源,並將生命週期傳遞,那麼下面咱們思考一個問題:

插件中的MainActivity調用插件中的OtherActivity,是如何調用的呢?startActivity需不須要重寫?

答案是確定的,啓用插件中的其餘Activity,其實就是從新建立一個新的空殼的Activity。

//重寫插件中的startActivity 將要啓動的Activity的全類名傳遞給ProxyActivity
   @Override
    public void startActivity(Intent intent) {
        if (mActivity != null) {
            //插件的launchMode 只可以是標準的
            Intent intent1 = new Intent();
            intent1.putExtra("className", intent.getComponent().getClassName());
            mActivity.startActivity(intent1);
        } else {
            super.startActivity(intent);
        }
    }
    
    //重寫ProxyActivity的startActivity,跳轉一個新的ProxyActivity,並跳轉Activity的全類名傳遞過去,至關於重走了一邊上面的過程
    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }
複製代碼

ProxyActivity代理方式主要注意兩點:

  • ProxyActivity中須要重寫getResouces,getAssets,getClassLoader方法返回插件的相應對象。生命週期函數以及和用戶交互相關函數,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等須要轉發給插件。
  • PluginActivity中全部調用context的相關的方法,如setContentView,getLayoutInflater,getSystemService等都須要調用ProxyActivity的相應方法。

調用插件中的Service

經過上述的講解,咱們知道了調用插件中的Activity,其實就是在宿主中建立一個空殼的Acitvity,而後加載插件中的資源,傳遞上下文。

那麼調用插件中的Service呢?原理是同樣的,原理是同樣的仍是在宿主中建立一個空殼的Service ProxyService,ProxyService 將生命週期傳遞給插件中的Service

本身能夠去實現一下,這裏我只把核心代碼給出

public class ProxyService extends Service {
    private String serviceName;

    private PluginInterfaceService pluginInterfaceService;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void init(Intent intent) {
        //開啓某個服務的全類名
        serviceName = intent.getStringExtra("serviceName");
        //生成一個class
        DexClassLoader classLoader = PluginManager.getInstance().getClassLoader();
        try {
            Class<?> aClass = classLoader.loadClass(serviceName);
            Constructor<?> constructor = aClass.getConstructor(new Class[]{});
            //獲取某個service對象
            Object newInstance = constructor.newInstance(new Object[]{});
            pluginInterfaceService = (PluginInterfaceService) newInstance;
            pluginInterfaceService.attach(this);
            Bundle bundle = new Bundle();
            pluginInterfaceService.onCreate();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (pluginInterfaceService == null) {
            init(intent);
        }
        return pluginInterfaceService.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        pluginInterfaceService.onStart(intent, startId);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        pluginInterfaceService.onConfigurationChanged(newConfig);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        pluginInterfaceService.onLowMemory();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        pluginInterfaceService.onTrimMemory(level);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        pluginInterfaceService.onUnbind(intent);
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        pluginInterfaceService.onRebind(intent);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        pluginInterfaceService.onTaskRemoved(rootIntent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        pluginInterfaceService.onDestroy();
    }
}

複製代碼

須要注意的是:ProxyActivity和ProxyService必定要在宿主的 Manifest 中註冊。

OK,這編文章的講解是插件化最初的時候出現的設計原理,接下來會一步步深刻講解。

相關文章
相關標籤/搜索