android 动态加载之免安装升级(插件式开发)
来源:互联网 发布:传奇3怪物数据库 编辑:程序博客网 时间:2024/06/07 01:43
{大半年没更新博客,一开始是忙坏了,后面没忙了发现自己不想写了,变懒惰,今天重新开始检讨自己,并且要坚持写博客这个习惯。一定要坚持下来分享自己的技术心德。}
今天我们就说讨论讨论,[动态加载:]顾名思义,就是让apk不需要升级就能更新的功能,也可以理解为”插件”,也可以叫插件式开发。
动态加载有几种比如说有加载apk的,加载dex的jar的等等,这里我们主要针对加载dex的jar这种形式。
动态图:
现在咱们说说如何实现这个功能呢。
1、其实呢就跟你项目里面集成了一个jar一个概念,只是这个jar不在项目里面了,而是在sdcard或者别的地方。这样就能完成需要升级的时候,去下载最新的jar并加载,从而达到不需要升级apk就能更新的功能。
2、这里我们主要项目为一个主体项目,一个jar项目。具体实现咱们在后面边看图边讲解。
3、jar项目打包出来的项目,需要由.class文件转成dex文件的jar,这里需要用到一个dx的工具,后面也会依次介绍,当然了网上也有下,我这里也会提供。废话不多说,咱们现在就进入高潮!!!
首先是启动类代码如下(这里呢所有触发都在点击事件里面,由于没设立服务器,我们暂时把需要动态加载的jar包放在assets文件夹里面,然后在拷贝进sdcard里面去,去加载sdcard里面的jar。拷贝成功之后就该去加载dex,并且启动里面的start方法。该方法后面会介绍干啥用):
package com.test.demo;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.os.Environment;import android.view.View;import android.widget.Button;import com.example.test.R;/** * create by wangyuwen at 2017/4/11. */public class MainActivity extends Activity implements View.OnClickListener { private Button btn_jar; //sdcar里面存储dex public static final String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dynamic_V1.jar"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_jar = (Button) findViewById(R.id.btn_jar); btn_jar.setOnClickListener(this); } @Override public void onClick(View view) { if (view == btn_jar) {// 加载第一个dex File file = new File(filePath); if (!file.exists()) { AssetsCopySystemCard(this, "dynamic_V1.jar", filePath); } // 因为所有类都反射到同一个activity,所以需要把唯一的承载跳转activity的intent传进去 Intent intent = new Intent(); intent.setClassName(getPackageName(), "com.test.demo.IntentActivity"); // 加载动态dex里面的唯一通道,拿到dex里面的类对象,并且开始调用start方法 DynamicUtil.DynamicAc(this, null).ExecuteMethod("start", new Class[] { Intent.class }, new Object[] { intent }); } } /** * 把Assets里面的文件拷贝到sdcard * * @return */ public static synchronized void AssetsCopySystemCard(Context context, String assets, String path) { InputStream is = null; FileOutputStream fos = null; try { File file = new File(path); if (!file.exists()) { file.createNewFile(); } is = context.getAssets().open(assets); // 第二个参数是是否追加,false是覆盖,true是追加 fos = new FileOutputStream(file, false); byte[] buffer = new byte[1024]; int byteCount = 0; while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取 buffer字节 fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流 } } catch (Exception e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } if (fos != null) { fos.close(); } } catch (Exception e) { e.printStackTrace(); } } }}
因为有界面需要跳转,所以我们需要一个承载的activity代码如下(这个类啥都没干,就是去加载dex里面的activity的基类。因为在启动类里面有个传了包含这个承载类的intent进去,所以跳转之后就会到这里来。):
package com.test.demo;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.util.Log;/** * Created by wyw on 2017/4/11. */public class IntentActivity extends Activity { public ObjectAcUtil objectAcUtil; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { //加载动态dex,拿到dex里面的反射类对象 objectAcUtil = DynamicUtil.DynamicAc(this, "Activity"); objectAcUtil.ExecuteMethod("onCreate", new Class[] { Bundle.class }, new Object[] { savedInstanceState }); } catch (Exception e) { Log.e(getPackageName(), "[载体]onCreate Activity 失败", e); } } @Override protected void onStart() { super.onStart(); try { objectAcUtil.ExecuteMethod("onStart", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onStart Activity 失败", e); } } @Override protected void onResume() { super.onResume(); try { objectAcUtil.ExecuteMethod("onResume", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onResume Activity 失败", e); } } @Override protected void onRestart() { super.onRestart(); try { objectAcUtil.ExecuteMethod("onRestart", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onRestart Activity 失败", e); } } @Override protected void onPause() { super.onPause(); try { objectAcUtil.ExecuteMethod("onPause", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onPause Activity 失败", e); } } @Override protected void onStop() { super.onStop(); try { objectAcUtil.ExecuteMethod("onStop", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onStop Activity 失败", e); } } @Override protected void onDestroy() { super.onDestroy(); try { objectAcUtil.ExecuteMethod("onDestroy", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onDestroy Activity 失败", e); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); try { objectAcUtil.ExecuteMethod("onActivityResult", null, null); } catch (Exception e) { Log.e(getPackageName(), "[载体]onActivityResult Activity 失败", e); } }}
接下来就是动态加载的核心代码了(这里是2个类,一个加载dex的工具类,一个是反射过来的object类,来加载该类里面的方法):
package com.test.demo;import java.io.File;import android.annotation.SuppressLint;import android.app.Activity;import android.text.TextUtils;import android.util.Log;import dalvik.system.DexClassLoader;/** * create by wangyuwen at 2017/4/11. */@SuppressLint("NewApi")public class DynamicUtil { /* 反射的类名 */ public static final String CLASSNAME = "com.dynamic.Dynamic"; /* 反射的类名activity */ public static final String CLASSNAME_AC = "com.dynamic.DynamicAC"; public static ObjectAcUtil DynamicAc(Activity activity, String strAc) { try { File jarFile = new File(MainActivity.filePath); if (jarFile.exists()) { DexClassLoader dexCl = new DexClassLoader(MainActivity.filePath, activity.getCacheDir().getAbsolutePath(), null, DynamicUtil.class.getClassLoader()); Class acl; if (TextUtils.isEmpty(strAc)) { acl = dexCl.loadClass(DynamicUtil.CLASSNAME); } else { acl = dexCl.loadClass(DynamicUtil.CLASSNAME_AC); } return new ObjectAcUtil(activity, acl); } } catch (Throwable e) { Log.e(activity.getPackageName(), "[动态加载] 出错!", e); } return null; }}package com.test.demo;import java.lang.reflect.Constructor;import android.app.Activity;import android.util.Log;public class ObjectAcUtil { /* 反射出来的对象(这个就等于某个类被new出来的对象) */ private Object object; public ObjectAcUtil(Activity activity, Class c) { try { Constructor constructor = c.getConstructor(new Class[] { Activity.class }); object = constructor.newInstance(new Object[] { activity }); } catch (Exception e) { Log.e("123456", "ObjectUtil Service", e); } } public synchronized Object ExecuteMethod(String methodName, Class[] parameterType, Object[] parameter) { try { if (parameterType == null && parameter == null) { return object.getClass().getMethod(methodName).invoke(object); } else { return object.getClass().getMethod(methodName, parameterType).invoke(object, parameter); } } catch (Exception e) { Log.e("123456", "ExecuteMethod", e); } return null; }}
主体项目总工就只有4个类,Manifest文件我就不贴了,里面就注册一个启动activity和承载activity就行了。接下来看下被加载项目,也就是jar项目。
首先看下上面启动类点击事件里面调用的类到底做了些什么呢?代码如下(这里贴的是2个类,第一个类呢也就是点击事件里面调用的类,并且反射了start方法,start方法就只干了一件事,就是加个bundle然后跳转activity,因为intent是传下来的,所以不用指定跳转activity,这里需要把你写界面的那个类的类名传进去为什么要传呢, 后面会介绍。下面那个类是跳转工具类,我也直接贴在这里了,跳转工具类里面的参数不懂的自行百度,这里就不做过多描述。):
package com.dynamic;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public class Dynamic { private Activity activity; public Dynamic(Activity activity) { this.activity = activity; } public void start(Intent intent) { startIntentView(intent); } public void startIntentView(Intent intent) { Bundle bundle = new Bundle(); bundle.putString("String", "d动态加载的JAR"); HelpUtil.IntentView(activity, intent, ViewB.class.getName(), bundle); }}package com.dynamic;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.util.Log;/** * create by wangyuwen at 2017/4/12 0012 */public class HelpUtil { public static final String KEY_VIEW_NAME = "key_view_name"; public static void IntentView(Context context, Intent intent, String className, Bundle data) { if (intent != null) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(HelpUtil.KEY_VIEW_NAME, className); intent.putExtras(data); context.startActivity(intent); Log.i("123456", "start intent activity!"); } else { Log.e("123456", "intent = null"); } }}
跳转到了承载activity,上面也介绍了承载activity只干一件事,就是反射dex里面的activity基类,也就是即将要介绍的请看代码(这里就会有很多要问我了,为什么这里还要去反射,因为需求是主体项目在不需要更新的情况去更新jar项目,这就导致主体项目的承载activity只能反射到这里,要想灵活开发的话,这里肯定要去反射一个ui基类,ui实现类去继承这个ui基类,因为你会写很多ui实现类去跳转所以需要基类去定位调用。反射类名就在bundle里面,上面就说到bundle里面包含了你写的ui实现类的类名进来):
package com.dynamic;import java.lang.reflect.Constructor;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public class DynamicAC { private Activity activity; private BaseView baseView; public DynamicAC(Activity activity) { this.activity = activity; } public void onCreate(Bundle savedInstanceState) { try { String className = activity.getIntent().getStringExtra(HelpUtil.KEY_VIEW_NAME); Bundle data = activity.getIntent().getExtras(); Class c = Class.forName(className); Class[] paramTypes = { Activity.class, Bundle.class }; Object[] params = { activity, data }; Constructor constructor = c.getConstructor(paramTypes); baseView = (BaseView) constructor.newInstance(params); baseView.onCreate(savedInstanceState); } catch (Exception e) { activity.finish(); } } public void onStart() { try { baseView.onStart(); } catch (Exception e) { activity.finish(); } } public void onResume() { try { baseView.onResume(); } catch (Exception e) { activity.finish(); } } public void onRestart() { try { baseView.onRestart(); } catch (Exception e) { activity.finish(); } } public void onPause() { try { baseView.onPause(); } catch (Exception e) { activity.finish(); } } public void onStop() { try { baseView.onStop(); } catch (Exception e) { activity.finish(); } } public void onDestroy() { try { baseView.onDestroy(); } catch (Exception e) { activity.finish(); } } public void onActivityResult(int requestCode, int resultCode, Intent data) { try { baseView.onActivityResult(requestCode, resultCode, data); } catch (Exception e) { activity.finish(); } }}
接下来我们看看ui实现类和基类了,代码如下(第一个是基类,很清楚明了构造方法里面拿到bundle和activity,然后写一些抽象方法,让继承的人去实现。第二个是实现类,构造里面的super下面去拿bundle里面String去放进textview里面去,onCreate就是干创建视图的工作了,注意了jar项目里面不能用res文件,所以所有视图得用代码去写,所以这就造成了要对代码写ui的要求很高。):
package com.dynamic;import android.app.Activity;import android.content.Intent;import android.os.Bundle;/** * create by wangyuwen at 2017/4/11 0011 */public abstract class BaseView { protected Bundle bundle; protected Activity activity; public BaseView(Activity activity, Bundle bundle) { this.activity = activity; this.bundle = bundle; } public abstract void onCreate(Bundle savedInstanceState); public abstract void onStart(); public abstract void onResume(); public abstract void onRestart(); public abstract void onPause(); public abstract void onStop(); public abstract void onDestroy(); public abstract void onActivityResult(int requestCode, int resultCode, Intent data);}package com.dynamic;import android.app.Activity;import android.content.Intent;import android.graphics.Color;import android.os.Bundle;import android.util.Log;import android.view.Gravity;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.TextView;/** * create by wangyuwen at 2017/4/11 0011 */public class ViewB extends BaseView { private String str; public ViewB(Activity activity, Bundle bundle) { super(activity, bundle); str = bundle.getString("String"); } @Override public void onCreate(Bundle savedInstanceState) { Log.i("123456", "进来了!"); LinearLayout linearLayout = new LinearLayout(activity); linearLayout.setOrientation(LinearLayout.VERTICAL); linearLayout.setGravity(Gravity.CENTER); linearLayout.setBackgroundColor(Color.BLACK); linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); TextView textView = new TextView(activity); textView.setText(str + ""); textView.setTextColor(Color.WHITE); textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); linearLayout.addView(textView); activity.setContentView(linearLayout); Log.i("123456", "执行完了!onCreate"); } @Override public void onStart() { Log.i("123456", "onStart!"); } @Override public void onResume() { Log.i("123456", "onResume!"); } @Override public void onRestart() { Log.i("123456", "onRestart!"); } @Override public void onPause() { Log.i("123456", "onPause!"); } @Override public void onStop() { Log.i("123456", "onStop!"); } @Override public void onDestroy() { Log.i("123456", "onDestroy!"); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i("123456", "onActivityResult!"); }}
到了这里项目差不多就介绍完了,现在可以说如何打包成dex文件。第一步肯定是jar项目导jar包。请看图:
我们导出jar之后,现在是要转dex文件了。这里我们会需要用到一个dx工具,这个工具我会一起放入项目的zip里面。
然后用法如下将dx.zip解压,将其里面的资源拷贝到android sdk platform-tools目录下即可使用(window环境)。
编译命令,cmd进入到android sdk platform-tools目录 dx –dex –output=target.jar origin.jar
上述命令中 origin.jar为源代码导出的jar包(源码包要在android sdk platform-tools目录里面),target.jar为dx工具产生的dex二进制jar包!
生成的target.jar文件就是已经转好的文件,这时候就可以把这个文件丢入主体项目的aseets目录里面了,然后开始跑动你的项目试试吧。
做到这一步就赶紧把你的代码运行起来吧!!本篇博客就到这里,如果有有疑问的欢迎留言讨论。同时希望大家多多关注我的博客,多多支持我。
尊重原创转载请注明:(http://blog.csdn.net/u013895206) !
下面是地址传送门:址:http://download.csdn.net/detail/u013895206/9837770
0 0
- android 动态加载之免安装升级(插件式开发)
- Android中插件开发篇之----动态加载Activity(免安装运行程序)
- Android中插件开发篇之----动态加载Activity(免安装运行程序)
- Android中插件开发篇之----动态加载Activity(免安装运行程序)
- Android中插件开发篇之----动态加载Activity(免安装运行程序)
- Android优化之插件开发(动态加载Apk)
- android 动态加载之插件化开发1
- Android插件化开发之动态加载的类型
- android 动态加载 插件模型开发
- Android 动态加载与插件开发
- Android动态换肤(二、apk免安装插件方式)
- 加载插件(四)之动态加载
- Android插件化之资源动态加载
- 插件化开发系列之二—动态加载技术加载已安装和未安装的apk
- Android插件-动态加载
- Android 插件化 动态升级
- Android 插件化 动态升级
- Android 插件化 动态升级
- 最小生成树的Prim算法实现
- tensorflow初试
- 数据库/Java关键字<2>
- String int 转换方法
- 程序
- android 动态加载之免安装升级(插件式开发)
- jQuery的优势
- Hibernate 学习(一)初识Hibernate
- 矩阵快速幂 51nod
- 最小生成树的Kruskal算法
- Linux进程管理
- MySQL创建数据库与创建用户以及授权
- Oracle数据库中SYS、SYSTEM、DBSNMP、SYSMAN四类权限的区别
- 几种TCP连接中出现RST的情况