Android插件化探索(一)佔位式(插樁式)插件化詳解

前言

因爲近期項目中要用到插件,因此特意去翻找資料學習了一番,如今在這裏分享我所學到的東西給你們,有什麼錯誤的但願能給我指出來,文章有點長,但願你們能認真讀完。android

近些年來,插件化可謂是特別的火熱,就拿支付寶美團等軟件來講,都是使用這個技術來支撐他們的產品。可是什麼是插件化呢,插件化到底有什麼好處呢?git

插件化也就是運行的APP(宿主APP)去加載插件APP(沒有安裝的APP),這就是所謂的插件化開發。github

插件化到底運行在什麼場景下呢?其實插件化使用的場景有不少,這裏就好比下圖的支付寶或者美團等APP,點擊某個相應的item,就會跳轉到相應的頁面當中,其實這個頁面是插件apk中的頁面,可是它到底怎麼作到的呢?怎麼作到不安裝apk而加載插件中的頁面呢?api

下面咱們就來探索探索不用安裝插件apk是怎麼去加載裏面的Activity、Service、BroadCastReceiver等這些組件的。本篇文章所提的是佔位式(插樁式)插件化。數組

因爲插件apk是沒有安裝的,也就是插件apk沒有組件的一些環境,好比context上下文對象之類的,若是要用到這些環境就必須依賴宿主的環境運行。因此咱們就要宿主跟插件之間定義一個標準。用來傳遞宿主中的環境給插件。bash

加載插件中的Activity

第一步:app

首先咱們先定義一個標準,讓插件實現咱們的標準來傳遞宿主APP的環境,下圖是定義Activity類的標準,下面咱們從加載插件中的Activity開始講起。ide

首先咱們要在插件中實現剛纔咱們定義的標準,因爲插件都須要宿主APP的環境,因此咱們就定義一個基類來實現該標準,而後讓咱們的插件的Activity來繼承該基類,該Activity就具備了宿主的環境了。如圖所示。學習

第二步: build工程,獲得插件apk,命名爲 plugin.apk 並把它放到咱們的sd目錄下。讓宿主APP來加載插件。 首先咱們要加載插件apk中的類,就須要用到DexClassLoader這個類,下面是用該類來加載插件apk的方法。ui

File file = new File(path);
 File pluginDir = context.getDir("plugin", Context.MODE_PRIVATE);
 //加載插件的class
 dexClassLoader = new DexClassLoader(path, pluginDir.getAbsolutePath(), null, context.getClassLoader());
複製代碼

參數說明:

path:插件所存放的目錄(plugin.apk存放的目錄)

pluginDir.getAbsolutePath():插件apk解析後dex文件所存放的路徑

null:該參數是so庫存放的路徑,因爲插件裏沒有so庫,因此爲null。

context.getClassLoader():類加載器

獲取到了DexClassLoader的對象,咱們就能夠拿到插件中的類了,接下來咱們要獲取插件apk中的資源對象,也就是 Resources對象。因爲插件apk沒有宿主的環境,也就沒法使用 context.getResources()的方式來獲得一個Resources對象,咱們只能new 一個對象出來。

//Resources的構造方法
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
    this(null);
    mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
複製代碼

從上面代碼中能夠看出,咱們能夠看出建立一個 Resources對象須要傳遞一個 AssetManagerDisplayMetricsConfiguration對象,而這些對象咱們怎麼能夠獲取到呢?其實後兩個參數很容易獲取到。後面的兩個參數咱們能夠直接用宿主的Resources對象裏面的屬性就能夠了,以下

Resources appResources = context.getResources();
 DisplayMetrics displayMetrics = appResources.getDisplayMetrics();
 Configuration configuration = appResources.getConfiguration();
複製代碼

如今咱們比較頭疼的問題是如何獲取AssetManager這個類的對象?其實咱們能夠利用反射的方式來獲取一個AssetManager的對象,以下

AssetManager pluginAssetManager = AssetManager.class.newInstance();
複製代碼

閱讀源碼發現,AssetManager類裏面要更改資源路徑就必需要調用 addAssetPath 方法。從這裏得出咱們能夠反射拿到這個方法,而後傳入插件apk的資源路徑便可。

AssetManager pluginAssetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.setAccessible(true);
addAssetPath.invoke(pluginAssetManager, path);
複製代碼

經過上述代碼能夠獲取到咱們的插件apk的 AssetManager對象了,擁有該對象,咱們就能夠獲取到 Resources對象了,整體代碼以下:

//加載插件的資源文件
 //一、獲取插件的AssetManager
 AssetManager pluginAssetManager = AssetManager.class.newInstance();
 Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
 addAssetPath.setAccessible(true);
 addAssetPath.invoke(pluginAssetManager, path);
 //二、獲取宿主的Resources
 Resources appResources = context.getResources();
 //實例化插件的Resources
 pluginResource = new Resources(pluginAssetManager, appResources.getDisplayMetrics(), appResources.getConfiguration());
複製代碼

拿到了DexClassLoader對象和Resources對象咱們就能夠加載插件apk中的activity等組件了。要跳轉到插件中的activity,咱們首先要獲取到插件找那個的Activity,經過 PackageManager.getPackageArchiveInfo()咱們能夠獲得一個 PackageInfo對象,而這個對象裏面就有咱們須要的Activity數組,以下圖:

獲取插件Activity數組咱們就能夠拿到 Activity對象,而且能夠進行跳轉了:

//獲取插件包的Activity
PackageManager packageManager = getPackageManager();
PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
//獲取在manifest文件中註冊的第一個activity
ActivityInfo activity = packageArchiveInfo.activities[0];
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", activity.name);
複製代碼

經過以上代碼顯示,咱們先是跳轉到了一個ProxyActivity,而後把插件的Activity類名傳遞過去,爲何不直接跳轉到插件的Activity呢?緣由是由於插件的Activity沒有在咱們的宿主的manifest文件中進行註冊,若是直接跳轉就會發生崩潰,因此咱們這裏先跳轉到一個代理的Activity,由於該Activity是宿主裏面的而且在manifest文件中進行註冊了,因此咱們用它來進行模擬Activity入棧出棧的操做,當咱們的代理Activity回調onCreate() 的時候,咱們能夠獲取到傳遞過來的className來反射獲得咱們的插件中的Activity對象,因爲咱們插件中的Activity是實現了上面所述的標準,咱們能夠經過標準來調用插件Activity中的生命週期。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //真正的加載插件裏面的Activity
        String className = getIntent().getStringExtra("className");
        try {
            Class<?> pluginActivity1Clazz = getClassLoader().loadClass(className);
            Constructor<?> constructor = pluginActivity1Clazz.getConstructor(new Class[]{});
            pluginActivity1 = (IActivityInterface) constructor.newInstance(new Object[]{});
            pluginActivity1.insertAppContext(this);
            Bundle bundle = new Bundle();
            bundle.putString("value", "我是宿主傳遞過來的字符串");
            pluginActivity1.onCreate(bundle);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
複製代碼

當執行完上述代碼後咱們就能夠加載出插件的Activity了。執行效果以下:

插件內跳轉Activity

跳轉成功後,咱們就要實現插件中的Activity跳轉到插件中的另外一個Activity了。你們都知道跳轉Activity的話咱們都要執行startActivity() 方法,而查看源碼而知,startActivity() 裏調用了 this.startActivity(intent, null);這裏的 this表示着上下文對象,可是咱們的插件APP沒有上下文對象的環境,若是執行了 startActivity() 方法確定會發生崩潰的,那麼怎樣才能避免崩潰,而且成功跳轉呢。其實很是簡單,咱們只要使用宿主APP的環境就能夠來實現該功能了,只不過是稍微有些麻煩。咱們須要重寫 startActivity() 方法,而且傳遞咱們須要跳轉的Activity的全類名給宿主,且跳轉的方法交給咱們的宿主來進行就能夠了,而後咱們重寫宿主APP的代理ActivitystartActivity() 方法用來接收插件APP傳遞過來的全類名,最後執行宿主APP的代理ActivitystartActivity() 方法便可。以下:

插件中的startActivity

另外提示一點,咱們插件的Activity 全部使用的都是宿主的環境,好比 setContentView()findViewById() 等方法,運行後效果以下:

至此,咱們動態加載插件APP的Activity已經講完了,至於動態加載Service與動態加載廣播的方法跟加載Activity的方法相似,這裏不作講解。下面講解的是靜態註冊插件中的廣播。

註冊插件內的靜態廣播

要加載插件中的靜態廣播,咱們先來提問一個問題。咱們APP中的靜態廣播究竟是何時被註冊的呢? 其實在手機開機的時候,手機裏面全部的已經安裝APP,系統會再次進行安裝一遍,這也就能說明Android手機開機的時候爲何會那麼慢。等到安裝完成後,會立刻掃描 **/data/app/**目錄,而後逐個去解析該目錄下的全部 apk裏面的 Manifest.xml 文件, 若是裏面有靜態廣播後,就會自動註冊。

咱們如今來研究系統是如何去解析 apk文件裏面的組件信息的。咱們知道解析apk 文件會用到 PackageManagerService 這個類,因此咱們來查看這個類的源碼。

跟蹤源碼發現,執行到 packageParser.parsePackage() 這個方法的時候會返回一個 PackageParser.Package 對象,咱們進去查看一下 PackageParser 這個類的源碼,發現 Package 是其裏面的靜態內部類。

從這個內部類裏咱們找到了咱們想要的東西,就是解析 Manifest.xml 文件後的數據類。咱們能夠經過反射來獲取這些屬性的值。那操做的步驟是怎樣的呢?

第一步:

咱們應該先模擬 PMS 解析安裝包的形式獲取到這些信息。閱讀源碼發現,PackageParser.class 裏的 Package parsePackage(File packageFile, int flags) 正好返回一個 Package 對象,因此咱們須要執行這個方法來獲取它。

如下是代碼的實現:

Class<?> mPackageParserClass = Class.forName("android.content.pm.PackageParser");
Object mPackageParser = mPackageParserClass.newInstance();
Method parsePackageMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class);
Object mPackage = parsePackageMethod.invoke(mPackageParser, file, PackageManager.GET_ACTIVITIES);

//二、獲取Package類下的   public final ArrayList<Activity> receivers = new ArrayList<Activity>(0); 廣播集合
Field mReceiversField = mPackage.getClass().getDeclaredField("receivers");
//本質上是 ArrayList<Activity> receivers
ArrayList<Object> receivers = (ArrayList<Object>) mReceiversField.get(mPackage);
複製代碼

第二步:

在第一步咱們獲取到了 receivers 這個廣播集合,有了這個集合咱們就能夠拿到裏面的廣播來進行註冊了。咱們先來看看先跟到這個 List<Activity> 尖括號裏面的對象,發現這個Activity 不是咱們四大組件的Activity,它純粹是爲了封裝這些數據的Java bean,因此千萬不要把它搞混了。

咱們能夠發現裏面有個 ActivityInfo 的屬性。咱們繼續跟這個類和他的父類的源碼,結果發現, name 這個屬性恰好就是 android:name="com.xxx.xxx" 對應裏面的包名跟類名,咱們須要獲取到這個值來 new 一個廣播對象出來。

咱們如今想辦法獲得上述 Activity 裏面的 info這個屬性這樣咱們就能夠得到 name的值了。繼續閱讀 PackageParser 的源碼,咱們發現

經過這個方法咱們能夠獲得一個 ActivityInfo 對象,因此咱們就來執行這個方法。執行該方法咱們須要傳遞四個參數,第一個 Activity類型咱們已經有了,第二個參數咱們能夠直接傳個0,第三個咱們能夠反射來獲取一個類的實例。如

Class<?> mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
 Object mPackageUserState = mPackageUserStateClass.newInstance();
複製代碼

如今就剩下最後一個參數了,就是咱們的userId,到底怎麼獲取這個參數值呢?其實咱們能夠從 android.os.UserHandle 這個類入手,分析該類得出的結果

經過圖中的方法能夠獲取一個userId;因此咱們就以反射來獲取該userId; 詳細代碼以下:

//先獲取到 ActivityInfo類
Class<?> mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Object mPackageUserState = mPackageUserStateClass.newInstance();

Method generateActivityInfoMethod = mPackageParserClass.getMethod("generateActivityInfo", mActivity.getClass(),
int.class, mPackageUserStateClass, int.class);
//獲取userId
Class<?> mUserHandleClass = Class.forName("android.os.UserHandle");
//public static @UserIdInt int getCallingUserId()
int userId = (int) mUserHandleClass.getMethod("getCallingUserId").invoke(null);

//執行此方法 因爲是靜態方法 因此不用傳對象
ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, mPackageUserState, userId);
複製代碼

以上咱們就能夠獲取到一個ActivityInfo 對象了。

第三步: 咱們知道註冊廣播的時候還要添加一個IntentFliter,可是這個應該從哪裏取到呢? 其實咱們剛纔分析的 Activity 那個類提及 ,它繼承與 Component 在跟進這個類去看,發現裏面有個 intents 的集合,查看泛型繼承的 IntentInfo,就是咱們想要的 IntentFliter,因此咱們要獲得這個集合,而後進行遍歷就能夠獲得 IntentFliter,而後咱們就能夠註冊廣播了。

因此註冊廣播的代碼以下:

//三、遍歷全部的靜態廣播
//Activity 該Activity 不是四大組件裏面的activity,而是一個Java bean對象,用來封裝清單文件中的activity和receiver
for (Object mActivity : receivers) {
    //四、獲取該廣播的全類名 即 <receiver android:name=".PluginStaticReceiver"> android:name屬性後面的值
    //  /**
    //     * Public name of this item. From the "android:name" attribute.
    //     */
    //    public String name;
    
    // public static final ActivityInfo generateActivityInfo(Activity a, int flags,
    //            PackageUserState state, int userId)
    //先獲取到 ActivityInfo類
    Class<?> mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
    Object mPackageUserState = mPackageUserStateClass.newInstance();
    
    Method generateActivityInfoMethod = mPackageParserClass.getMethod("generateActivityInfo", mActivity.getClass(),
    int.class, mPackageUserStateClass, int.class);
    //獲取userId
    Class<?> mUserHandleClass = Class.forName("android.os.UserHandle");
    //public static @UserIdInt int getCallingUserId()
    int userId = (int) mUserHandleClass.getMethod("getCallingUserId").invoke(null);
    
    //執行此方法 因爲是靜態方法 因此不用傳對象
    ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, mPackageUserState, userId);
    String receiverClassName = activityInfo.name;
    Class<?> receiverClass = getClassLoader().loadClass(receiverClassName);
    BroadcastReceiver receiver = (BroadcastReceiver) receiverClass.newInstance();
    
    //五、獲取 intent-filter  public final ArrayList<II> intents;這個是intent-filter的集合
    //靜態內部類反射要用 $+類名
    //getField(String name)只能獲取public的字段,包括父類的;
    //而getDeclaredField(String name)只能獲取本身聲明的各類字段,包括public,protected,private。
    Class<?> mComponentClass = Class.forName("android.content.pm.PackageParser$Component");
    Field intentsField = mActivity.getClass().getField("intents");
    ArrayList<IntentFilter> intents = (ArrayList<IntentFilter>) intentsField.get(mActivity);
    for (IntentFilter intentFilter : intents) {
        //六、註冊廣播
        context.registerReceiver(receiver, intentFilter);
    }
}
複製代碼

到這裏咱們已經完成了對插件中的靜態廣播進行註冊。運行效果以下:

至此,咱們的佔位式插件化已經講解完畢了,這裏貼一下部分類的完整代碼

MainActivity.class

public class MainActivity extends AppCompatActivity {

    private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "plugin.apk";
    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 666) {
                isLoadSuccess = true;
                if (dialog != null && dialog.isShowing()) {
                    dialog.dismiss();
                }
                Toast.makeText(MainActivity.this, "加載插件成功!", Toast.LENGTH_SHORT).show();
            } else if (msg.what == 0) {
                if (dialog != null && dialog.isShowing()) {
                    dialog.dismiss();
                }
                Toast.makeText(MainActivity.this, "加載插件失敗,請檢查插件是否存在!", Toast.LENGTH_SHORT).show();
            }
        }
    };
    //是否加載完成
    private boolean isLoadSuccess = false;

    private ProgressDialog dialog;

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

    @RequiresApi(api = Build.VERSION_CODES.M)
    public void loadPlugin(View view) {
        //判斷是否已經賦予權限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, 666);
            } else {
                showProgress();
                PluginManager.getInstance(this).loadPlugin(handler, path);
            }
        } else {
            showProgress();
            PluginManager.getInstance(this).loadPlugin(handler, path);
        }

    }


    public void jumpPluginActivity(View view) {
        if (isLoadSuccess) {
            //獲取插件包的Activity
            PackageManager packageManager = getPackageManager();
            PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
            //獲取在manifest文件中註冊的第一個activity
            ActivityInfo activity = packageArchiveInfo.activities[0];
            Intent intent = new Intent(this, ProxyActivity.class);
            intent.putExtra("className", activity.name);
            startActivity(intent);
        } else {
            Toast.makeText(this, "請先加載插件!", Toast.LENGTH_SHORT).show();
        }

    }

    private void showProgress() {
        if (dialog == null) {
            dialog = new ProgressDialog(this);
        }
        dialog.setTitle("加載插件中,請稍後。。。");
        dialog.setCancelable(false);
        dialog.show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 666) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                showProgress();
                PluginManager.getInstance(this).loadPlugin(handler, path);
            } else {
                Toast.makeText(MainActivity.this, "請打開讀取SD卡權限", Toast.LENGTH_SHORT).show();
            }
        }
    }

    public void parsePlugin(View view) {
        PluginManager.getInstance(this).parsePlugin(path);
    }

    public void sendStaticReceiver(View view) {
        Intent intent = new Intent("plugin_static_receiver");
        intent.putExtra("str", "我是從宿主中發來的字符");
        sendBroadcast(intent);
    }
}
複製代碼

PluginManager.class

public class PluginManager {
    private static final String TAG = PluginManager.class.getSimpleName();
    private static PluginManager instance;
    private Context context;
    private DexClassLoader dexClassLoader;
    private Resources pluginResource;

    private PluginManager(Context context) {
        this.context = context;
    }

    public static PluginManager getInstance(Context context) {
        if (instance == null) {
            synchronized (PluginManager.class) {
                if (instance == null) {
                    instance = new PluginManager(context);
                }
            }
        }
        return instance;
    }

    public void loadPlugin(final Handler handler, final String path) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    File file = new File(path);
                    if (!file.exists()) {
                        Log.e(TAG, "插件不存在");
                        return;
                    }
                    File pluginDir = context.getDir("plugin", Context.MODE_PRIVATE);
                    //加載插件的class
                    dexClassLoader = new DexClassLoader(path, pluginDir.getAbsolutePath(), null, context.getClassLoader());
                    //加載插件的資源文件
                    //一、獲取插件的AssetManager
                    AssetManager pluginAssetManager = AssetManager.class.newInstance();
                    Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
                    addAssetPath.setAccessible(true);
                    addAssetPath.invoke(pluginAssetManager, path);
                    //二、獲取宿主的Resources
                    Resources appResources = context.getResources();
                    //實例化插件的Resources
                    pluginResource = new Resources(pluginAssetManager, appResources.getDisplayMetrics(), appResources.getConfiguration());
                    if (dexClassLoader != null && pluginResource != null) {
                        handler.sendEmptyMessage(666);
                    } else {
                        handler.sendEmptyMessage(0);
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                    handler.sendEmptyMessage(0);
                }
            }
        }).start();

    }

    @SuppressLint("PrivateApi")
    public void parsePlugin(String pluginPath) {
        try {
            File file = new File(pluginPath);
            if (!file.exists()) {
                Log.e(TAG, "插件不存在");
                return;
            }
            //一、解析插件包  public Package parsePackage(File packageFile, int flags)
            Class<?> mPackageParserClass = Class.forName("android.content.pm.PackageParser");
            Object mPackageParser = mPackageParserClass.newInstance();
            Method parsePackageMethod = mPackageParserClass.getMethod("parsePackage", File.class, int.class);
            Object mPackage = parsePackageMethod.invoke(mPackageParser, file, PackageManager.GET_ACTIVITIES);

            //二、獲取Package類下的   public final ArrayList<Activity> receivers = new ArrayList<Activity>(0); 廣播集合
            Field mReceiversField = mPackage.getClass().getDeclaredField("receivers");
            ArrayList<Object> receivers = (ArrayList<Object>) mReceiversField.get(mPackage);

            //三、遍歷全部的靜態廣播
            //Activity 該Activity 不是四大組件裏面的activity,而是一個Java bean對象,用來封裝清單文件中的activity和receiver
            for (Object mActivity : receivers) {
                //四、獲取該廣播的全類名 即 <receiver android:name=".PluginStaticReceiver"> android:name屬性後面的值
                //  /**
                //     * Public name of this item. From the "android:name" attribute.
                //     */
                //    public String name;

                // public static final ActivityInfo generateActivityInfo(Activity a, int flags,
                //            PackageUserState state, int userId)
                //先獲取到 ActivityInfo類
                Class<?> mPackageUserStateClass = Class.forName("android.content.pm.PackageUserState");
                Object mPackageUserState = mPackageUserStateClass.newInstance();

                Method generateActivityInfoMethod = mPackageParserClass.getMethod("generateActivityInfo", mActivity.getClass(),
                        int.class, mPackageUserStateClass, int.class);
                //獲取userId
                Class<?> mUserHandleClass = Class.forName("android.os.UserHandle");
                //public static @UserIdInt int getCallingUserId()
                int userId = (int) mUserHandleClass.getMethod("getCallingUserId").invoke(null);

                //執行此方法 因爲是靜態方法 因此不用傳對象
                ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, mPackageUserState, userId);
                String receiverClassName = activityInfo.name;
                Class<?> receiverClass = getClassLoader().loadClass(receiverClassName);
                BroadcastReceiver receiver = (BroadcastReceiver) receiverClass.newInstance();

                //五、獲取 intent-filter  public final ArrayList<II> intents;這個是intent-filter的集合
                //靜態內部類反射要用 $+類名
                //getField(String name)只能獲取public的字段,包括父類的;
                //而getDeclaredField(String name)只能獲取本身聲明的各類字段,包括public,protected,private。
                Class<?> mComponentClass = Class.forName("android.content.pm.PackageParser$Component");
                Field intentsField = mActivity.getClass().getField("intents");
                ArrayList<IntentFilter> intents = (ArrayList<IntentFilter>) intentsField.get(mActivity);
                for (IntentFilter intentFilter : intents) {
                    //六、註冊廣播
                    context.registerReceiver(receiver, intentFilter);
                }

            }

        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    public Resources getResource() {
        return pluginResource;
    }

    public DexClassLoader getClassLoader() {
        return dexClassLoader;
    }
}
複製代碼

ProxyActivity.class

public class ProxyActivity extends Activity {

    private IActivityInterface pluginActivity1;
    private boolean isRegister;
    private ProxyReceiver proxyReceiver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //真正的加載插件裏面的Activity
        String className = getIntent().getStringExtra("className");
        try {
            Class<?> pluginActivity1Clazz = getClassLoader().loadClass(className);
            Constructor<?> constructor = pluginActivity1Clazz.getConstructor(new Class[]{});
            pluginActivity1 = (IActivityInterface) constructor.newInstance(new Object[]{});
            pluginActivity1.insertAppContext(this);
            Bundle bundle = new Bundle();
            bundle.putString("value", "我是宿主傳遞過來的字符串");
            pluginActivity1.onCreate(bundle);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void onStart() {
        super.onStart();
        pluginActivity1.onStart();

    }

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

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

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

    @Override
    protected void onRestart() {
        super.onRestart();
        pluginActivity1.onRestart();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        pluginActivity1.onDestroy();
        if (isRegister) {
            unregisterReceiver(proxyReceiver);
        }
    }

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

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance(this).getClassLoader();
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        //本身跳本身
        Intent newIntent = new Intent(this, this.getClass());
        newIntent.putExtra("className", className);
        super.startActivity(newIntent);
    }

    @Override
    public ComponentName startService(Intent service) {
        String className = service.getStringExtra("className");
        ProxyService.pluginServiceClassName = className;
        //本身跳本身
        Intent newService = new Intent(this, ProxyService.class);
        newService.putExtra("className", className);
        return super.startService(newService);
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        proxyReceiver = new ProxyReceiver(receiver.getClass().getName());
        isRegister = true;
        return super.registerReceiver(proxyReceiver, filter);
    }

}

複製代碼

插件中的BaseActivity.class

public abstract class BaseActivity extends Activity implements IActivityInterface {
    protected Activity appActivity;
    @Override
    public void insertAppContext(Activity appActivity) {
        this.appActivity = appActivity;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) { }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() { }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() { }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() { }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() { }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onRestart() { }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() { }

    @Override
    public void setContentView(int resId) {
        appActivity.setContentView(resId);
    }

    @Override
    public <T extends View> T findViewById(int id) {
        return appActivity.findViewById(id);
    }

    @Override
    public void startActivity(Intent intent) {
        Intent newIntent = new Intent();
        newIntent.putExtra("className", intent.getComponent().getClassName());
        appActivity.startActivity(newIntent);
    }

    @Override
    public ComponentName startService(Intent service) {
        Intent newService = new Intent();
        newService.putExtra("className", service.getComponent().getClassName());
        return appActivity.startService(newService);
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return appActivity.registerReceiver(receiver, filter);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        appActivity.sendBroadcast(intent);
    }
}

複製代碼

Plugin1Activity.class

public class Plugin1Activity extends BaseActivity {
    private static final String TAG = Plugin1Activity.class.getSimpleName();
    private boolean isRegister;

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreateActivity");
        setContentView(R.layout.activity_plugin1);
        TextView textView = findViewById(R.id.textView);
        Button button = findViewById(R.id.btn_jump);
        String value = savedInstanceState.getString("value");
        textView.setText(value != null ? value : "");
        Toast.makeText(appActivity, value, Toast.LENGTH_SHORT).show();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(appActivity, Plugin2Activity.class);
                startActivity(intent);
            }
        });
        findViewById(R.id.btn_start_plugin_service).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(new Intent(appActivity, PluginService.class));
            }
        });
        findViewById(R.id.btn_register_plugin_receiver).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isRegister) {
                    IntentFilter filter = new IntentFilter();
                    filter.addAction("yuongzw");
                    registerReceiver(new PluginReceiver(), filter);
                    isRegister = true;
                }
            }
        });
        findViewById(R.id.btn_send_plugin_receiver).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("yuongzw");
                intent.putExtra("str", "我是從插件發送的廣播");
                sendBroadcast(intent);
            }
        });
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStartActivity");
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {
        Log.d(TAG, "onResumeActivity");
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {
        Log.d(TAG, "onPauseActivity");
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {
        Log.d(TAG, "onStopActivity");
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onRestart() {
        Log.d(TAG, "onRestartActivity");
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroyActivity");
    }
}

複製代碼

PluginStaticReceiver.class

public class PluginStaticReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if ("plugin_static_receiver".equals(intent.getAction())) {
            Toast.makeText(context, "我是插件的靜態廣播,我收到了:" + intent.getStringExtra("str"), Toast.LENGTH_SHORT).show();
        }
    }
}
複製代碼

下面附上Github的項目地址,給須要的小夥伴進行參考:

項目地址:PluginLoadDemo

相關文章
相關標籤/搜索