看DynamicLoadApk源码有感

就在2月7号,公司年后第二天上班,我提了离职,到2月18号拿到了离职证明。
至此,我开启了失业之旅…….

早就在知乎上看到各种关于移动端工作难找的帖子,所以心里也是做好了准备。
从提辞职那天起,我就开始投简历,两个星期下来,加上同学推荐的,一共面了
3家,当然是各种被吊打,但是现在回头来看,又觉得其实那些面试题或者笔试题
并没有那么难,只能怪自己没有好好准备就跑去面了,浪费了3家都不错的互联网公司。

其中有一家也是奇葩,笔试完面试完,CTO让我回来仿照他们家App做个Demo,
我以为Demo做出来会有戏,于是乎用周末两天把Demo做出来了,发给他们,
过了一天,我看他们还没回我邮件,我就打电话给他们HR,让她帮我提醒CTO
查看邮件,然后过了一个星期,邮件还是没有得到回复,这时候我才发觉被pass掉了…….
后来想想,还是得怪自己当时的笔试和面试做得too菜….怪我咯

好了,我的苦逼面试经历就说到这里了,下面说回正题了
(我在想要不要把我有限的笔试题和面试题也写篇博客…….)

DynamicLoadApk是任玉刚大神的热插件作品,项目地址
如果还不是很清楚热插件是啥玩意的话,可以看这里
其实已经有篇很好的关于DynamicLoadApk源码解析,在这里

只是我觉得既然看了源码,就应该记录一下自己思路,免得看过就忘了

DynamicLoadApk的实现总结为两个字:代理模式
总体设计图:
这里写图片描述

DLPluginManger简单来说是通过DexClassLoader来加载插件里的组件,组件是指
Activity,Service,Fragment等(目前好像就支持这两个,BTW:此框架已经停止维护了)

BasePlugin是所有插件组件的基类,拿DLBasePluginActivity来说,所有的插件
Activity都必须继承它,通过attach方法跟代理Activity绑定,后面会说到。

Proxy代理,拿DLProxyActivity来说,在宿主App里面会有一个同名的Activity,
用来管理插件里的组件Activity的生命周期,下面是宿主App的Manifest.xml:

<manifest
    package="com.host"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity
            android:name="com.ryg.dynamicload.DLProxyActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="com.ryg.dynamicload.proxy.activity.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
        <activity
            android:name="com.ryg.dynamicload.DLProxyFragmentActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="com.ryg.dynamicload.proxy.fragmentactivity.VIEW"/>

                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

        <service android:name="com.ryg.dynamicload.DLProxyService"/>
    </application>

</manifest>

可以看到宿主App里面已经注册好了DLPluginActivity,然后具体的实现是在插件里

来看看DLBasePluginActivity里的代码

public class DLBasePluginActivity extends Activity implements DLPlugin {

    private static final String TAG = "DLBasePluginActivity";

    /**
     * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
     */
    protected Activity mProxyActivity;

    /**
     * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
     * 可以当作this来使用
     */
    protected Activity that;
    protected DLPluginManager mPluginManager;
    protected DLPluginPackage mPluginPackage;

    protected int mFrom = DLConstants.FROM_INTERNAL;

    @Override
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
        Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
        mProxyActivity = (Activity) proxyActivity;
        that = mProxyActivity;
        mPluginPackage = pluginPackage;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
        }
        if (mFrom == DLConstants.FROM_INTERNAL) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
            that = mProxyActivity;
        }

        mPluginManager = DLPluginManager.getInstance(that);
        Log.d(TAG, "onCreate: from= "
                + (mFrom == DLConstants.FROM_INTERNAL ? "DLConstants.FROM_INTERNAL" : "FROM_EXTERNAL"));
    }

DLPlugin是一个接口,抽象出Activity所有生命周期方法,如下:

public interface DLPlugin {

    public void onCreate(Bundle savedInstanceState);
    public void onStart();
    public void onRestart();
    public void onActivityResult(int requestCode, int resultCode, Intent data);
    public void onResume();
    public void onPause();
    public void onStop();
    public void onDestroy();
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
    public void onSaveInstanceState(Bundle outState);
    public void onNewIntent(Intent intent);
    public void onRestoreInstanceState(Bundle savedInstanceState);
    public boolean onTouchEvent(MotionEvent event);
    public boolean onKeyUp(int keyCode, KeyEvent event);
    public void onWindowAttributesChanged(LayoutParams params);
    public void onWindowFocusChanged(boolean hasFocus);
    public void onBackPressed();
    public boolean onCreateOptionsMenu(Menu menu);
    public boolean onOptionsItemSelected(MenuItem item);
}

DLBasePluginActivity 的attach()方法,是把插件里的Activity跟代理Activity绑定,这里还涉及到一个DLAttachable,DLAttachable也是一个接口,如下:

public interface DLAttachable {
    /**
     * when the proxy impl ( {@see DLProxyImpl#launchTargetActivity()} ) launch
     * the plugin activity , dl will call this method to attach the proxy activity
     * and pluginManager to the plugin activity. the proxy activity will load
     * the plugin's resource, so the proxy activity is a resource delegate for
     * plugin activity.
     * 
     * @param proxyActivity a instance of DLPlugin, {@see DLBasePluginActivity}
     *            and {@see DLBasePluginFragmentActivity}
     * @param pluginManager DLPluginManager instance, manager the plugins
     */
    public void attach(DLPlugin proxyActivity, DLPluginManager pluginManager);
}

在DLProxyActivity里就是通过实现这个接口来绑定插件Activity的,如下:

public class DLProxyActivity extends Activity implements DLAttachable {

    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);

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

    @Override
    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
        mRemoteActivity = remoteActivity;
    }

就是通过DLBasePluginActivity的attach和DLProxyActivity的attch来实现插件Activity和代理Activity的双向绑定。

这两个attach方法是在DLProxyImpl的launchTargetActivity()中实现,如下:

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    protected void launchTargetActivity() {
        try {
            Class<?> localClass = getClassLoader().loadClass(mClass);
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {});
            mPluginActivity = (DLPlugin) instance;
            ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
            Log.d(TAG, "instance = " + instance);
            // attach the proxy activity and plugin package to the mPluginActivity
            mPluginActivity.attach(mProxyActivity, mPluginPackage);

            Bundle bundle = new Bundle();
            bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
            mPluginActivity.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

至此,就实现了把插件里的组件(Activity为例)和代理类(Activity为例)的双向绑定,
在宿主的MainActivity里便可以调起插件里的Activity,实现如下:

public class MainActivity extends Activity {

    private Button btnTest;
    private TextView tvTip;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.btnTest = (Button) findViewById(R.id.btn_test);
        this.tvTip = (TextView) findViewById(R.id.tv_tip);
        this.init();
    }

    //初始化
    private void init() {
        //获取插件
        String pluginFolder = "/mnt/sdcard/DynamicLoadHost";
        File file = new File(pluginFolder);
        File[] plugins = file.listFiles();
        //判断有没有插件
        if (plugins == null || plugins.length == 0) {
            this.tvTip.setVisibility(View.VISIBLE);
            return;
        }
        //调用第一个插件
        File plugin = plugins[0];
        final PluginItem item = new PluginItem();
        item.pluginPath = plugin.getAbsolutePath();
        item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
        //获取插件的启动Activity的名称
        if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
            item.launcherActivityName = item.packageInfo.activities[0].name;
        }
        //获取插件启动Service的名称
        if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
            item.launcherServiceName = item.packageInfo.services[0].name;
        }
        //显示插件
        tvTip.setText("检测到一个插件:" + item.pluginPath);
        //加载插件
        DLPluginManager.getInstance(this).loadApk(item.pluginPath);
        //添加监听器
        this.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //提示
                Toast.makeText(getApplicationContext(), "开始调用插件", Toast.LENGTH_SHORT).show();
                //调用插件
                usePlugin(item);
            }
        });
    }

    //调用插件
    private void usePlugin(PluginItem pluginItem) {
        DLPluginManager pluginManager = DLPluginManager.getInstance(this);
        pluginManager.startPluginActivity(this, new DLIntent(pluginItem.packageInfo.packageName, pluginItem.launcherActivityName));
    }

    //插件Bean
    public static class PluginItem {
        public PackageInfo packageInfo;
        public String pluginPath;
        public String launcherActivityName;
        public String launcherServiceName;

        public PluginItem() {
        }
    }
}

这就是设计模式的伟大之处啊!
其实不用这种代理模式也是可以实现动态加载的,就是全部用反射去实现,这样的话效率肯定会差太多…..
不得不说大神写的东西就是难看懂,我看了两天才明白这其中的关系..GG

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值