美文网首页
dl插件框架学习

dl插件框架学习

作者: andoop | 来源:发表于2016-09-02 18:42 被阅读0次

    DLTest

    百度dl框架研究学习demo

    github:https://github.com/andoop/DLTest

    关于dl框架

    1. 插件加载框架
    2. 与DLoad(动态加载,我的另一个工程)原理一样,但是封装处理的很好,Dload在此
    3. 在插件(动态包中)支持R访问资源(在Dload中,我们是通过流来获取图片资源的)
    4. 可维护多个插件(动态包),Dload中,我们只做了加载一个动态包的功能
    5. 实际开发中,可直接在dl框架上修改使用即可,还是比较实用的
    6. 虽然也可以加载还有.so文件的插件(动态包,下面都统一叫做“插件”),但是会有好多问题的

    先上demo,一睹为快

    首页
    demo首页,显示插件列表,有两个插件:main_plugin_a和main_plugin_b

    首页
    进入main_plugin_a中

    首页
    开启main_plugin_a中的service

    首页
    关闭main_plugin_a中的service

    首页
    打开main_plugin_a中另一个activity

    首页

    进入插件main_plugin_b


    首页

    在插件b中调用宿主方法


    首页

    在宿主中调用插件main_plugin_b中的方法


    分析一下demo代码

    看一下宿主工程

    MainActivity 的initData方法

         private void initData() {
                //得到插件存放的位置,demo中用到的插件存放到了sdcard的dltest文件夹下
                String pluginFolder = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"dltest";
                File file = new File(pluginFolder);
        
                File[] plugins = file.listFiles();
                if (plugins == null || plugins.length == 0) {
                    mNoPluginTextView.setVisibility(View.VISIBLE);
                    return;
                }
                //遍历dltest文件夹下所有插件
                for (File plugin : plugins) {
                    //PluginItem是我自己定义的类
                    PluginItem item = new PluginItem();
                    item.pluginPath = plugin.getAbsolutePath();
                    //使用DlUtils获取插件包信息
                    item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
                    if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
                        //得到插件的主activity
                        item.launcherActivityName = item.packageInfo.activities[0].name;
                    }
                    if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
                        item.launcherServiceName = item.packageInfo.services[0].name;
                    }
                    mPluginItems.add(item);
                    //加载插件,第二个参数为false,标识没有.so文件
                    DLPluginManager.getInstance(this).loadApk(item.pluginPath,false);
                }
        
                mListView.setAdapter(mPluginAdapter);
                mListView.setOnItemClickListener(this);
                mPluginAdapter.notifyDataSetChanged();
            }
    

    点击ListView中对应条目(会调起插件)

      @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            PluginItem item = mPluginItems.get(position);
            DLPluginManager pluginManager = DLPluginManager.getInstance(this);
            //打开插件的中的Activity
            pluginManager.startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName));
        }
    

    看看main_plugin_a插件中
    //继承dl框架中的DLBasePluginActivity
    public class MainActivity extends DLBasePluginActivity implements View.OnClickListener {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //正常使用布局文件
            setContentView(R.layout.activity_main);
            findViewById(R.id.btn_openSecond).setOnClickListener(this);
            findViewById(R.id.btn_openService).setOnClickListener(this);
            findViewById(R.id.btn_stopService).setOnClickListener(this);
        }
        //开启插件中另一个Activity
        public void openSecond(){
            startPluginActivity(new DLIntent(getPackageName(),SecondTestActivity.class));
        }
        //开启服务
        private void openService() {
            startPluginService(new DLIntent(getPackageName(),TestService.class));
        }
        //停止服务
        private void stopService() {
            stopPluginService(new DLIntent(getPackageName(),TestService.class));
        }
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_openSecond:
                    openSecond();
                    break;
                case R.id.btn_openService:
                    openService();
                    break;
                case R.id.btn_stopService:
                    stopService();
                    break;
            }
        }
    }
    

    在插件中,所有的Activity都要继承dl中的DLBasePluginActivity,所有的FragmentActivity都要继承dl中的DLBasePluginFragmentActivity,所有的service都要继承dl中的DLBasePluginService,具体使用请看demo即可

    开启Activity使用startPluginActivity(DLIntent);开启服务使用startPluginService(DLIntent);停止服务使用stopPluginService(DLIntent),当然也可以绑定服务的,有相关方法


    看看main_plugin_b,插件b实现了与宿主的互相调用

    插件b中

    public class MainActivity extends DLBasePluginFragmentActivity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.btn_plugin_b).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //得到宿主实现的接口对象
                    IHost host = InterfaceManager.getHost();
                    if(host==null){
                        Toast.makeText(MainActivity.this.that,"hello", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    //调用宿主方法
                    String hostMethod = host.hostMethod(that,"在插件main_plugin_b中调用>>");
                    Toast.makeText(MainActivity.this.that, hostMethod, Toast.LENGTH_SHORT).show();
                }
            });
            //传入插件b实现的接口对象,宿主得到后可以调用其方法
            InterfaceManager.setPlugin(new IPlugin() {
                @Override
                public String pulginMethod(String s) {
                    return s+"我是插件b";
                }
            });
        }
    }
    

    IHost、IPlugin和InterfaceManager没有在dl的library中,存在于我写的另一个library中,在这个类库中,主要定义插件和宿主之间调用的接口,demo中只是定义了一个简单的接口,详情请看demo中interactlib。


    在宿主中调用插件b实现IPlugin接口的对象的方法

     public void doActionWithPlugin(View view){
            String s = InterfaceManager.getPlugin().pulginMethod("在host中调用>>>");
            Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
        }
    

    插件打包使用

    插件应该从服务端下载,demo中为了演示方便,将插件APk通过adb命令push到了sdcard的dltest文件夹下

    task命令如下

    def dlPath = '/sdcard/dltest'
    def upload = { ->
            try {
                //创建dltest文件夹
                exec {
                    commandLine 'E:/android_dev/sdk/sdk/platform-tools/adb.exe', 'shell', 'mkdir', dlPath
                }
            }
            catch (ignored) {
            }
    
            try{
                //将所有工程中生成的apk上传到dltest文件夹中
                exec {
                    commandLine 'E:/android_dev/sdk/sdk/platform-tools/adb.exe', 'push', project.name + '-debug.apk', dlPath
                    workingDir project.projectDir.toString() + '/build/outputs/apk/'
                }
            }
            catch (ignored){}
        }
    
        task uploadDebug << {
            upload()
        }
    

    最后执行uploadDebug任务即可

    在这里不用通过dx命令再次处理插件包了,是因为这里的插件包是apk文件,apk文件中的.class文件已经被处理过了


    dl原理分析

    占坑
     <activity android:name="com.ryg.dynamicload.DLProxyActivity"/>
        <activity android:name="com.ryg.dynamicload.DLProxyFragmentActivity"/>
        <service android:name="com.ryg.dynamicload.DLProxyService"
            android:enabled="true"
            android:exported="true"/>
    

    插件中的Activity,FragmentActivity,service要分别继承dl的DLBasePluginActivity、DLBasePluginFragmentActivity和DLBasePluginService

    dl中的DLProxyActivity,DLProxyFragmentActivity,DLProxyService会代理插件中Activity,FragmentActivity,service的生命周期


    插件中组件关联代理组件

    以Activity为例:
    DLProxyActivity实现了DLAttachable接口

    public interface DLAttachable {
        public void attach(DLPlugin proxyActivity, DLPluginManager pluginManager);
    }
    

    他调用attach方法将被代理的Activity传过来(插件中继承了DLBasePluginActivity的Activity)

    DLBasePluginActivity实现了DLPlugin,DLPlugin如下

    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();
        //调用这里的attach,将代理Activity传入到了DLBasePluginActivity中(插件中Activity)
        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);
    }
    

    都是Activity的生命周期方法

    在DLProxyActivity中

    @Override
        protected void onStart() {
            mRemoteActivity.onStart();
            super.onStart();
        }
    
        @Override
        protected void onRestart() {
            mRemoteActivity.onRestart();
            super.onRestart();
        }
    
        @Override
        protected void onResume() {
            mRemoteActivity.onResume();
            super.onResume();
        }
    
        @Override
        protected void onPause() {
            mRemoteActivity.onPause();
            super.onPause();
        }
    

    摘出了部分代码,mRemoteActivity就是被代理的DLBasePluginActivity(插件中的Activity)


    插件中为什么可以通过R来访问图片呢?

    重写Activity中的 getAssets()和getResources()并返回自己的重写构建AssetManager,(AssetManager中对应的资源路径,改为插件路径即可)和由此生成的Resources即可,

    dl中体现如下:(DLPluginManager中)

      AssetManager assetManager = createAssetManager(dexPath);
      Resources resources = createResources(assetManager);
    

    private AssetManager createAssetManager(String dexPath) {
            try {
                AssetManager assetManager = AssetManager.class.newInstance();
                Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assetManager, dexPath);
                return assetManager;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
    
        }
    
        private Resources createResources(AssetManager assetManager) {
            Resources superRes = mContext.getResources();
            Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
            return resources;
        }
    

    dexPath即为插件路径,例如:/sdcard/dltest/xxx.apk


    怎样实例化插件中类的呢(DLPluginManager中)
     private DexClassLoader createDexClassLoader(String dexPath) {
            File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
            dexOutputPath = dexOutputDir.getAbsolutePath();
            //初始化一个dexclassloader
            DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
            return loader;
        }
    

           DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
            AssetManager assetManager = createAssetManager(dexPath);
            Resources resources = createResources(assetManager);
            // create pluginPackage
            pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
            mPackagesHolder.put(packageInfo.packageName, pluginPackage);
    

    每一个插件都会对应一个dexclassloder,生成的dexclassload会被封装到DLPluginPackage对象中,而这个对象会被放到以插件包名为键的map中,这样插件和dexclassloader就一一对应了


      protected void launchTargetActivity() {
            try {
                //根据类名生成一个插件中Activity的对象
                Class<?> localClass = getClassLoader().loadClass(mClass);
                Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
                Object instance = localConstructor.newInstance(new Object[] {});
                //生成的插件中Activity的对象
                mPluginActivity = (DLPlugin) instance;
                //代理Activity关联插件中的Activity
                ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
                Log.d(TAG, "instance = " + instance);
                // attach the proxy activity and plugin package to the mPluginActivity
                //插件中Activity关联代理Activity
                mPluginActivity.attach(mProxyActivity, mPluginPackage);
    
                Bundle bundle = new Bundle();
                bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
                mPluginActivity.onCreate(bundle);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    上面是生成插件中Activity对象的过程,插件中service对象生成与关联代理的流程,是类似的,这个不过多讨论了


    在插件中使用上下文时,慎用this

    插件中的Activity最后是被当做普通的类,通过反射得到其实例的,系统不会管理器生命周期,通过this获取上下文已经不行了,但是dl为我们提供了关键字that,that会在不同的情况下,指向本Activity或者代理Activity,所以,在使用this行不通的情况下,可以使用that。


    注意点

    插件和宿主之间的桥梁其实是接口,如demo中宿主和插件b之间相互调用的接口,宿主和插件b都依赖了interactlib工程,但是插件b打包的时候,却不能包含任何接口文件,否者会报异常,demo中处理如下:

    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        //去除v7中的v4包,防止跟宿主v4冲突
        compile('com.android.support:appcompat-v7:23.4.0') {
            exclude module: 'support-v4'
        }
        compile project(':dllib')
        //私有引用,打包时不会包含
        provided files('../interactlib/build/libs/interactlib.jar')
    }
    

    上面是插件b的dependencies


    以上就是dl的使用方法,还有它的原理解析,具体使用还是请参考demo,

    更多学习>>dl 源码地址:
    https://github.com/singwhatiwanna/dynamic-load-apk
    欢迎关注andoop,周一、二内容更新,干货永不断!

    相关文章

      网友评论

          本文标题:dl插件框架学习

          本文链接:https://www.haomeiwen.com/subject/mpckettx.html