Android 插件化 02 —— 提線木偶

回顧:

上一篇通過一個小實驗,驗證了容器項目能夠通過一些偏門的方法獲取插件apk的資源,并且顯示插件簡單的Activity界面。

但插件作為一個獨立的應用,不會像Demo那樣只有一個簡單的界面。每個實際應用的Activity都會涉及大量的控件、事件響應以及交互操作等等。作為插件的Container,實在沒必要去關心插件功能上的具體實現(xiàn)細節(jié),它只要做一個規(guī)則的制定者就行了。
就如總理說的那樣:讓政府的歸政府,讓市場的歸市場……

展望:

先來看我們寫Android應用的一般規(guī)律(Activity方面的):
onCreate()方法一般布局并初始化控件,添加事件監(jiān)聽等;
onDestory()方法一般用于釋放一些資源;
onPause()、onResume()用于前后臺切換響應等,Activity中幾乎所有行為都與其生命周期息息相關。

Activity生命周期

如果Container掌控了插件Activity生命周期的各個方法,那么插件就會像提線木偶一樣,為其所用。

也就是說,Container —— PluginActivity中,關于生命周期的方法大致應該是這樣的:

protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    
    //...
    
    //讀取apk資源
    loadResources(apkPath);
    
    //...
    
    //調(diào)用插件PluginDemoActivity對象的onCreate()
    //...
}

@Override
protected void onDestroy()
{
    super.onDestroy();
    //調(diào)用插件PluginDemoActivity對象的onDestroy()
    //...
}

@Override
protected void onPause()
{
    super.onPause();
    //調(diào)用插件PluginDemoActivity對象的onPause()
    //...
}

@Override
protected void onResume()
{
    super.onResume();
    //調(diào)用插件PluginDemoActivity對象的onResume()
    //...
}

@Override
protected void onStart()
{
    super.onStart();
    //調(diào)用插件PluginDemoActivity對象的onStart()
    //...
}

@Override
protected void onStop()
{
    super.onStop();
    //調(diào)用插件PluginDemoActivity對象的onStop()
    //...
}

涉及到的問題:

如何獲得插件Activity的對象,或者說是如何加載另一個apk中的class

DexClassLoader是一個類加載器,可以用來從.jar和.apk文件中加載class??梢杂脕砑虞d執(zhí)行沒有和應用程序一起安裝的那部分代碼。

public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent);

參數(shù):
dexPath:apk路徑
optimizedDirectory:解壓后的.dex文件的存儲路徑,這個路徑強烈建議使用應用程序的私有路徑,不要放在sdcard上,否則代碼容易被注入攻擊。
libraryPath:系統(tǒng)庫的存放路徑,可以為空。
parent:父親加載器,一般為context.getClassLoader(),使用當前上下文的類加載器。

使用,上PluginActivity的代碼:

public class PluginActivity extends Activity
{
    protected AssetManager mAssetManager;
    protected Resources mResources;
    protected Theme mTheme;

    private Class<?> mPluginDemoClass;
    private Object mPluginActivity;

    //生命周期的每個方法
    private Method method_onStart;
    private Method method_onPause;
    private Method method_onResume;
    private Method method_onStop;
    private Method method_onDestroy;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        try
        {
            //得到插件apk路徑
            String dir = Environment.getExternalStorageDirectory().toString();
            String apkPath = dir + "/PluginDemo.apk"; //apk存放路徑
            if (!new File(apkPath).exists())
            {
                return;
            }

            //讀取apk資源
            loadResources(apkPath);

            //得到DexClassLoader
            File dexOutputDir = getDir("dex", 0);
            ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
            DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, localClassLoader);

            //動態(tài)加載插件Activity
            mPluginDemoClass = loader.loadClass("com.test.plugindemo.MainActivity");
            Constructor<?> localConstructor = mPluginDemoClass.getConstructor(new Class[]{});
            mPluginActivity = localConstructor.newInstance(new Object[]{});

            //將代理對象設置給插件Activity
            //因為插件Activity對象需要使用過的資源都在當前Activity中,所以需要把容器Activity對象傳過去
            Method setProxy = mPluginDemoClass.getMethod("setProxy", new Class[]{Activity.class});
            setProxy.setAccessible(true);
            setProxy.invoke(mPluginActivity, new Object[]{this});

            //存儲每個生命周期的方法
            method_onStart = mPluginDemoClass.getDeclaredMethod("onStart");
            method_onStart.setAccessible(true);
            method_onPause = mPluginDemoClass.getDeclaredMethod("onPause");
            method_onPause.setAccessible(true);
            method_onResume = mPluginDemoClass.getDeclaredMethod("onResume");
            method_onResume.setAccessible(true);
            method_onStop = mPluginDemoClass.getDeclaredMethod("onStop");
            method_onStop.setAccessible(true);
            method_onDestroy = mPluginDemoClass.getDeclaredMethod("onDestroy");
            method_onDestroy.setAccessible(true);

            //調(diào)用它的onCreate方法
            Method onCreate = mPluginDemoClass.getDeclaredMethod("onCreate", new Class[]{Bundle.class});
            onCreate.setAccessible(true);
            onCreate.invoke(mPluginActivity, new Object[]{new Bundle()});

            this.setContentView(R.layout.activity_main);
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    protected void loadResources(String apkPath)
    {
        try
        {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, apkPath);
            mAssetManager = assetManager;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        superRes.getDisplayMetrics();
        superRes.getConfiguration();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }


    @Override
    protected void onDestroy()
    {
        super.onDestroy();

        try
        {
            method_onDestroy.invoke(mPluginActivity, new Object[]{});
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

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

        try
        {
            method_onPause.invoke(mPluginActivity, new Object[]{});
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

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

        try
        {
            method_onResume.invoke(mPluginActivity, new Object[]{});
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

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

        try
        {
            method_onStart.invoke(mPluginActivity, new Object[]{});
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

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

        try
        {
            method_onStop.invoke(mPluginActivity, new Object[]{});
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    
    @Override
    public AssetManager getAssets()
    {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources()
    {
        return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Theme getTheme()
    {
        return mTheme == null ? super.getTheme() : mTheme;
    }
}

容器的代碼基本就這些,需要注意的是,在創(chuàng)建出插件的mPluginActivity對象后,需要把容器(或者說是代理)對象設置到對象中,因為所有資源都需要從這里獲取。

接下來修改插件工程:

上插件主Activity的代碼,為了增加點交互,我在里面增加了一個Button,點擊此Button彈出一個AlertDialog

public class MainActivity extends Activity
{
    private Button mButton = null;
    private Activity mProxyActivity = null; //代理Activity

    public void setProxy(Activity proxyActivity)
    {
        mProxyActivity = proxyActivity;
    }

    //這里需要注意的是,以下重載的方法中,不能調(diào)用super.xxxx了,不然報錯,原因也很簡單,
    //這個Activity實質上不是真正的Activity了,沒有生命周期的概念了,調(diào)用super的方法肯定報錯
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
//        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if (v.getId() == R.id.button)
                {
                    new AlertDialog.Builder(mProxyActivity)
                            .setMessage("show me the money")
                            .setPositiveButton("ok", null)
                            .show();
                }
            }
        });
    }

    @Override
    public void setContentView(int layoutResID)
    {
//        super.setContentView(layoutResID);
        mProxyActivity.setContentView(layoutResID);
    }

    @Override
    public View findViewById(int id)
    {
//        return super.findViewById(id);
        return mProxyActivity.findViewById(id);
    }

    @Override
    protected void onDestroy()
    {
//        super.onDestroy();
    }

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

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

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

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

}

對于插件來說,需要非常注意的就是:使用的資源一定要是來自代理(容器)對象的。

到這里,簡單的插件化就基本完成了。同上一篇一樣,制作PluginDemo.apk,放到指定目錄,運行Container工程,點擊加載插件,成功!


關于Android插件化,還有一些內(nèi)容要說,比如:橫豎屏切換需要刷新資源;插件apk如何做到既能提供給容器調(diào)用又能直接安裝使用等等,空下來再說吧……
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容