{大半年没更新博客,一开始是忙坏了,后面没忙了发现自己不想写了,变懒惰,今天重新开始检讨自己,并且要坚持写博客这个习惯。一定要坚持下来分享自己的技术心德。}
今天我们就说讨论讨论,[动态加载:]顾名思义,就是让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;
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) {
File file = new File(filePath);
if (!file.exists()) {
AssetsCopySystemCard(this, "dynamic_V1.jar", filePath);
}
Intent intent = new Intent();
intent.setClassName(getPackageName(), "com.test.demo.IntentActivity");
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);
fos = new FileOutputStream(file, false);
byte[] buffer = new byte[1024];
int byteCount = 0;
while ((byteCount = is.read(buffer)) != -1) {
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 {
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";
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 {
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里面。
生成的target.jar文件就是已经转好的文件,这时候就可以把这个文件丢入主体项目的aseets目录里面了,然后开始跑动你的项目试试吧。
做到这一步就赶紧把你的代码运行起来吧!!本篇博客就到这里,如果有有疑问的欢迎留言讨论。同时希望大家多多关注我的博客,多多支持我。
尊重原创转载请注明:(http://blog.csdn.net/u013895206) !
下面是地址传送门:址:http://download.csdn.net/detail/u013895206/9837770