android 动态加载之免安装升级(插件式开发)
Posted silly_wy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 动态加载之免安装升级(插件式开发)相关的知识,希望对你有一定的参考价值。
大半年没更新博客,一开始是忙坏了,后面没忙了发现自己不想写了,变懒惰,今天重新开始检讨自己,并且要坚持写博客这个习惯。一定要坚持下来分享自己的技术心德。
今天我们就说讨论讨论,[动态加载:]顾名思义,就是让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
以上是关于android 动态加载之免安装升级(插件式开发)的主要内容,如果未能解决你的问题,请参考以下文章
从零开始实现ASP.NET Core MVC的插件式开发 - 插件安装