Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)
来源:互联网 发布:哈尔滨蓝网网络直播 编辑:程序博客网 时间:2024/06/04 01:27
Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)
插件开发的原理简单的说就是将插件apk合并到宿主的ClassLoader中。我先简单说下如何使用插件中的资源,因为预注册时有些坑就跟这个有关系。
要使用apk中的资源,我们首先想到有个Resources就好了,先看下Resources的构造方法:
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { this(assets, metrics, config, null);}
所以接下来我们就要获取一个AssetManager的对象,通过反射,如下(其中dexPath为插件apk的路径):
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; } }
现在AssetManager也有了,我们就可以生成Resources(下面的mApplicationContext是宿主的ApplicationContext)。
private Resources createResources( AssetManager assetManager ) { Resources superRes = mApplicationContext.getResources(); Resources resources = new Resources( assetManager , superRes.getDisplayMetrics() , superRes.getConfiguration() ); return resources; }
然后我们可以自定义一个context,来管理我们新生成的这个AssetManager和Resources。
public class DLPluginContext extends ContextWrapper{ protected AssetManager mAssetManager; protected Resources mResources; protected Theme mTheme; protected ClassLoader classLoader; //pluginPackageName是插件apk的包名 public DLPluginContext( Context containerContext,String pluginPackageName ) { super( containerContext ); String pluginPath = DLUtils.getPluginPath( containerContext , pluginPackageName ); mAssetManager = createAssetManager(pluginPath ); mResources = createResources(mAssetManager ); int mThemeResource = DLUtils.selectDefaultTheme(0, containerContext.getApplicationInfo().targetSdkVersion); if (mTheme == null) { mTheme = mResources.newTheme(); } mTheme.applyStyle(mThemeResource, true); classLoader = pluginPackage.classLoader; } @Override public Resources getResources() { if(this.mResources != null){ return this.mResources; } return super.getResources(); } @Override public AssetManager getAssets() { // TODO Auto-generated method stub if(this.mAssetManager != null) return this.mAssetManager; return super.getAssets(); } @Override public Theme getTheme() { // TODO Auto-generated method stub if(this.mTheme != null) return this.mTheme; return super.getTheme(); } @Override public ClassLoader getClassLoader() { if(this.classLoader != null) return classLoader; return super.getClassLoader(); }}
接下来:
android插件化启动activity的方式有两种方式
1、代理方式:参见“木质的旋律”大大的博客http://blog.csdn.net/h28496/article/details/50414873
2、预注册的方式:参见“木质的旋律”大大的博客http://blog.csdn.net/H28496/article/details/49966503
首先我们的宿主apk中有个管理类DLHost,其中有一个DLPlugin的对象,DLPlugin的是一个抽象类,具体实现是在插件中。DLHost的initPlugin()方法实现dexClassLoader的合并,并通过类名的反射获取到一个DLPlugin的对象。这样就实现了宿主和插件的通信。
public abstract class DLHost{ public static final String SUFFIX = ".apk"; protected Context containerContext; protected DLPlugin plugin; public DLHost( Context containerContext ) { this.containerContext = containerContext; } public Context getContext() { return containerContext; } public void start( String packageName , String pluginClassName ) { try { initPlugin( packageName , pluginClassName ); } catch( Throwable e ) { //Log.i( "" , " e.printStackTrace(); " ); e.printStackTrace(); } } private void initPlugin( String packageName , String pluginClassName ) { // Log.d( "update" , "pluginPath 0" ); if( plugin != null ) return; checkPluginFile( packageName ); String pluginPath = DLUtils.getPluginPath( containerContext , packageName ); PackageInfo packageInfo = DLUtils.getPackageInfo( containerContext , pluginPath ); if( packageInfo != null ) { DLPluginManager mDLPluginManager = DLPluginManager.getInstance( containerContext ); mDLPluginManager.loadApk( pluginPath ); DLPluginPackage pluginPackage = mDLPluginManager.getPackage( packageName ); if( pluginPackage == null ) { return; } Class<?> clazz = DLUtils.loadPluginClass( pluginPackage.classLoader , pluginClassName ); if( clazz == null ) { return; } DLPlugin instance; try { Constructor<?> m = null; m = clazz.getConstructor( new Class[]{ DLHost.class } ); instance = (DLPlugin)m.newInstance( new Object[]{ this } ); plugin = instance; } catch( Exception e ) { // TODO Auto-generated catch block e.printStackTrace(); } } }}
TestHost继承DLHost,宿主MainActivity的button点击时,先完成dexclassloader的合并,然后启动activity。
public class MainActivity extends Activity{ @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_main ); findViewById( R.id.button ).setOnClickListener( new View.OnClickListener() { @Override public void onClick( View v ) { // TODO Auto-generated method stub TestHost.getInstance( getApplicationContext() ); Intent it = new Intent(); it.setClassName( MainActivity.this , "com.zjp.example.testplugin.PluginActivity" ); startActivity( it ); } } ); }}
“com.zjp.example.testplugin.PluginActivity”是插件中的activity,插件apk中的这个activity的内容如下:
public class PluginActivity extends Activity{ @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); TextView tv = new TextView( this ); tv.setText( "这是插件中的activity" ); setContentView( tv ); }}
看到这里,大家可以看到,我在插件的activity中并没有使用到插件apk中的资源,如果不使用插件apk的这个资源,所有的view都靠new出来,到这里就可以结束啦。但正常的大家一般不会自己为难自己的,界面还是通过布局文件实现的。
下面开始在插件中使用资源文件。
我想通过插件中DLPluginContext生成LayoutInflater.from( pluginContext ).inflate( R.layout.xxx , null );一个view,然后通过setContentView(view)就实现了引用插件中的资源。
说到这里,就来看看我们的DLPlugin(上面DLHost中有一个DLPlugin的对象)。
public abstract class DLPlugin{ protected DLHost host; protected Context pluginContext; public DLPlugin( DLHost host ) { this.host = host; pluginContext = new DLPluginContext( host.getContext() , getPackageName() ); }}
DLPlugin中生成了DLPluginContext的对象,而DLPlugin我们上面已经通过DLHost反射生成了实例,这样我们就可以直接用了。
但是却有空指针,plugin对象为空。
如下图1的log堆栈中,在host也就是宿主中通过反射调用了plugin(插件)种TestPlugin(继承DLPlugin)的初始化方法,并且赋值给一个静态变量instance,如下图2,但是在plugin中我直接调用这个TestPlugin.getInstance()静态方法却返回的是一个null,见如图1的最后一行log。
图1
图2
这个问题到现在我也没有搞明白是为什么,静态变量在同一个进程中(我打印过进程id,id值相等)不是可以大家一起用的么?如果有谁明白,请回复一下我。
public class PluginActivity extends Activity{ @Override protected void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); Context context = new DLPluginContext( this , "com.zjp.example.testplugin" ); View view = (ViewGroup)LayoutInflater.from( context ).cloneInContext( context ).inflate( R.layout.activity_main , null ); setContentView( view ); TextView tv = (TextView)findViewById( R.id.helloworld ); tv.setText( "这是插件布局的中的textview" ); }}
记住:这里通过LayoutInflater来生成布局一定要执行cloneInContext( context ),不然不能生成view。
你以为这篇流水账到这里就结束了么,还没有,请看官们接着往下看。
我在我的布局文件中加入了一个webview,也可以实现。
这里我遇到webview播放网络视频的问题,点击全屏按钮,能全屏播放。在4.2的机器上出现了视频全屏后不显示播放、暂停的按钮。但是另写了一个demo可以显示出来,然后就开始不停查找webview设置的属性是不是哪里不对,然后并没有结果。这时候就只能查看源码了。
在这里,我给大家安利一个很好的看源码的网站,各个不同的版本源码都有:http://androidxref.com/。
在4.2的源码上,通过surfaceview和mediacontrol生成两个view,放到framelayout中,通过webview的回调返回给webview,webview收到这个回调,将这个view加入到布局中实现布局。
现在的问题是MediaControl这个view不见了,接着查看MediaControl这个类生成view的地方,因为我们的webview是通过插件的布局文件生成的,WebView的Context是我们自定义的DLPluginContext,所以这里要生成view必须通过inflate.cloneInContext(),导致这个view没有生成成功。
所以我们可以通过new WebView(Context context)生成WebView,这个context传入的是我们当前的activity,然后加入到布局中就可以啦。
做了插件化代理方式、预注册方式,遇到好一些问题都是关于Context,宿主的Context,插件的Context,一定要用正确。
最后,关于预注册的代码下载见:http://download.csdn.net/download/shuishuixiaoping/9929970
当时没有注意,下载需要5积分,我也没有找到地方哪里可以改,有需要的请留言。
这篇流水账就到这里结束啦,欢迎大家吐槽。
- Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)
- 【Android】Android插件开发 —— 打开插件的Activity(预注册方式)
- 【Android】Android插件开发 —— 打开插件的Activity(代理方式)
- 【Android】Android插件开发 —— 打开插件的Activity(Hook系统方法)
- 记录打开过的Activity
- Android开发,我踩过的坑
- android 通过代理activity的方式实现插件化
- android 通过代理activity的方式实现插件化
- Android移动APP开发笔记——Cordova(PhoneGap)通过CordovaPlugin插件调用 Activity 实例
- 注册登录测试用例—祭我所踩过的那些坑
- 这些年我踩过的坑——Android
- 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑
- 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑
- Android开发记录——Activity的启动区别
- 我使用过的gulp插件,记录一下
- android 通过sheme打开activity
- 码农之记录那些我踩过的坑
- IC设计笔记----记录我踩过的坑
- android 7.0安装APK崩溃
- 单据转换插件--下推填充
- SDWebImage中Options属性
- Jvm中Jstack使用
- Linux 设备驱动基本概念
- Android插件开发 —— 通过预注册方式打开activity(记录我踩过的坑)
- Android动画总结
- 【Unity】ScriptableObject的介绍
- Arcgis javascript那些事儿(十七)——地理编码服务的发布与使用
- SEOer们应该系统的学习哪些知识呢?个人分享
- 数据结构-各种排序方式的时空复杂度及稳定性表
- bzoj 3994: [SDOI2015]约数个数和
- centos7安装rabbitmq(非RPM安装,感觉比activemq麻烦)
- [插头DP] poj3133 Manhattan Wiring