dl插件框架 - 《Andriod必备技能》

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,周一、二内容更新,干货永不断!
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值