Dload
一個(gè)動(dòng)態(tài)加載jar包的實(shí)例
github:https://github.com/andoop/Dload
概念?說明
動(dòng)態(tài)加載:
此處的動(dòng)態(tài)加載是指從服務(wù)端或者其他地方獲取jar包摹闽,并在運(yùn)行時(shí)期刀疙,加載jar包您炉,并與 jar包互相調(diào)用妓美。
本例中凤跑,為了方便演示久锥,將要?jiǎng)討B(tài)加載的jar放到了assets目錄下姆钉,在程序運(yùn)行時(shí)期佳晶,將其加載到/data/data/pkgname/files下,來模擬從服務(wù)端獲取為什么要?jiǎng)討B(tài)加載:
- 減少應(yīng)用安裝包體積截型, 程序包很大時(shí)趴荸,將部分模塊打成jar包,動(dòng)態(tài)的從服務(wù)端獲取宦焦,然后加載
- 方便升級(jí)維護(hù)发钝,將主要功能放入動(dòng)態(tài)jar包中,主應(yīng)用(不包含動(dòng)態(tài)jar包的部分)主要來維護(hù)動(dòng)態(tài)jar波闹,包括jar的加載酝豪,升級(jí)等。升級(jí)應(yīng)用精堕,可以更新動(dòng)態(tài)jar包孵淘,用戶在不重新安裝的情況下,就能做到部分(強(qiáng)調(diào)部分歹篓,是因?yàn)樗容^適用于部分功能升級(jí))升級(jí)
- 插件化開發(fā)瘫证,動(dòng)態(tài)jar包可以當(dāng)做插件來開發(fā),在應(yīng)用中滋捶,需要什么功能痛悯,就下載什么插件,如一些皮膚主題類的功能重窟,可以作為插件功能來開發(fā)载萌,用戶更換皮膚或者主題時(shí),只需要下載和更新對(duì)應(yīng)的插件就行,如:桌面系統(tǒng)(不同的桌面主題)扭仁,鎖屏(不同的鎖屏界面和風(fēng)格)
- 感覺還有好多好處垮衷,不一一列舉了........
demo比較簡(jiǎn)單,但是能演示和介紹整個(gè)流程以及思想就行啦乖坠,干脆利索搀突,效果圖如下:
打開插件就會(huì)進(jìn)入插件中的頁面(ListFragment),點(diǎn)擊任何一個(gè)條目熊泵,進(jìn)入插件中另一個(gè)頁面(DetailFragment)
功能?實(shí)現(xiàn)細(xì)節(jié)
首先從一個(gè)類入手仰迁,這個(gè)類是功能核心,那就是ClassLoader,這個(gè)類可以讓我們實(shí)現(xiàn)動(dòng)態(tài)加載顽分,ClassLoader是一個(gè)抽象類徐许,實(shí)際開發(fā)過程中,我們一般是使用其具體的子類DexClassLoader卒蘸、PathClassLoader這些類加載器來加載類的雌隅,它們的不同之處是:
- DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk缸沃;
- PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過的apk恰起;
所以,因?yàn)槲覀円虞djar趾牧,所以我們選擇DexClassLoader检盼。
看一下整個(gè)工程目錄結(jié)構(gòu)
宿主工程和動(dòng)態(tài)工程都依賴dloadlib,dloadlib中定義了兩個(gè)工程都交互的接口武氓,并對(duì)動(dòng)態(tài)jar進(jìn)行加載和調(diào)用
看一下dloadlib工程結(jié)構(gòu)
看一下代碼細(xì)節(jié)
宿主工程中:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化插件
Dload.getInstance(this).init(new DloadListener() {
@Override
public void onSuccess() {
// TODO Auto-generated method stub
Log.e(">>>","sccess");
}
@Override
public void onStart() {
// TODO Auto-generated method stub
Log.e(">>>","start");
}
@Override
public void onProgress(int persent) {
// TODO Auto-generated method stub
Log.e(">>>","progress>"+persent+"%");
}
@Override
public void onFail(String err) {
// TODO Auto-generated method stub
Log.e(">>>","fail>"+err);
}
});
}
public void showList(View view){
//打開插件中ListFragment
Dload.getInstance(this).showList();
}
}
Dload類中邏輯
public class Dload {
private Context context;
private DloadProxy dloadProxy;
public static Dload INSTANCE;
private Dload(Context context){
this.context = context;
}
//單例
public static Dload getInstance(Context context){
if(INSTANCE==null){
synchronized (Dload.class) {
if(INSTANCE==null){
INSTANCE=new Dload(context);
}
}
}
return INSTANCE;
}
/**
* 初始化狀態(tài)梯皿,例如:加載插件
*/
public void init(DloadListener dloadListener){
//真正去加載插件
DexExcutor.getInstance(context).init(dloadListener);
//初始化代理類
dloadProxy=new DloadProxy();
}
/**
* 打開列表,最終會(huì)調(diào)用插件功能
*/
public void showList(){
//調(diào)用代理類的方法县恕,最終會(huì)調(diào)用插件功能
dloadProxy.showList(context);
}
}
看一下怎么初始化插件,DexExcutor類走起..
public class DexExcutor {
public static DexExcutor INSTANCE;
private Context context;
private DloadListener dloadListener;
private String filepath;
private DexClassLoader dexClassLoader;
private DexExcutor(Context context){
this.context = context;
filepath=context.getFilesDir().getAbsolutePath();
}
//單例
public static DexExcutor getInstance(Context context){
if(INSTANCE==null){
synchronized (Dload.class) {
if(INSTANCE==null){
INSTANCE=new DexExcutor(context);
}
}
}
return INSTANCE;
}
//初始化插件
public void init(DloadListener dloadListener){
this.dloadListener = dloadListener;
if(dloadListener!=null)
dloadListener.onStart();
//檢查插件是否存在
File file = new File(filepath+File.separator+MConstans.jarname);
if(file.exists()){
//存在剂桥,加載插件
loadPlugin();
}else{
//不存在忠烛,則將插件從asset或者服務(wù)端加載到本地
downloadPlugin();
}
}
private void downloadPlugin() {
// TODO Auto-generated method stub
DloadUtils.downloadPlugin(context,"plugin.jar",filepath+File.separator+MConstans.jarname,new DloadUtils.Downloadlistener() {
@Override
public void onSuccess() {
// TODO Auto-generated method stub
loadPlugin();
}
@Override
public void onStart() {
// TODO Auto-generated method stub
}
@Override
public void onProgress(int progress) {
// TODO Auto-generated method stub
if(dloadListener!=null){
dloadListener.onProgress(progress);
}
}
@Override
public void onFail(String err) {
// TODO Auto-generated method stub
if(dloadListener!=null){
dloadListener.onFail(err);
}
}
});
}
/**
* 加載插件
*/
private void loadPlugin() {
try {
//根據(jù)插件的路徑,實(shí)例化對(duì)應(yīng)的dexclassloader
dexClassLoader = new DexClassLoader(filepath+File.separator+MConstans.jarname, filepath,null, context.getClassLoader());
if(dloadListener!=null){
dloadListener.onProgress(100);
dloadListener.onSuccess();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
if(dloadListener!=null)
dloadListener.onFail(e.getMessage());
}
}
/**
* 根據(jù)類名獲取一個(gè)實(shí)例
*
* @param className 動(dòng)態(tài)工程中類的全類名
*
* @return Object
*/
public Object newInstance(String className) {
try {
if (null != dexClassLoader) {
Class<?> dynamic_class = dexClassLoader.loadClass(className);
return dynamic_class.newInstance();
}
} catch (ClassNotFoundException e) {
Log.e(">>>", "newInstance:" + e.getMessage());
} catch (InstantiationException e) {
Log.e(">>>", "newInstance:" + e.getMessage());
} catch (IllegalAccessException e) {
Log.e(">>>", "newInstance:" + e.getMessage());
}
return null;
}
}
流程很簡(jiǎn)單权逗,再看一下代理是怎樣個(gè)代理美尸,進(jìn)入DloadProxy,
public class DloadProxy implements Idload {
@Override
public void showList(Context context) {
//實(shí)例化動(dòng)態(tài)工程中的類斟薇,這個(gè)類實(shí)現(xiàn)了Idload接口
Idload newInstance = (Idload) DexExcutor.getInstance(context).newInstance("cn.andoop.android.dloadplugin.DloadImp");
if(newInstance==null){
Toast.makeText(context, "插件還沒有加載好", Toast.LENGTH_SHORT).show();
return;
}
//真正調(diào)用動(dòng)態(tài)工程中的功能
newInstance.showList(context);
}
}
有沒有很簡(jiǎn)單J病(因?yàn)槭莇emo,所以邏輯越簡(jiǎn)單越好堪滨,沒有做很多容錯(cuò)處理)
看一下動(dòng)態(tài)工程中怎樣實(shí)現(xiàn)的吧
public class DloadImp implements Idload {
@Override
public void showList(Context context) {
// TODO Auto-generated method stub
DLoadActivity.start(context, "cn.andoop.android.dloadplugin.ui.ListFragment", null);
}
}
DloadImp 是動(dòng)態(tài)工程中的類胯陋,這個(gè)類就是對(duì)dloadlib中Idload的實(shí)現(xiàn)
DloadActivity是dloadlib中的acitvity,這個(gè)activity就是預(yù)先在宿主中注冊(cè)過了activity,用它來承載動(dòng)態(tài)工程中所有的頁面(Fragment)遏乔,這樣看來义矛,是不是動(dòng)態(tài)工程中展現(xiàn)頁面只能是Fragment?,其實(shí)不然,動(dòng)態(tài)工程中其實(shí)可以有fragment盟萨,activity凉翻,service,等組件捻激,只不過因?yàn)槭莿?dòng)態(tài)加載進(jìn)來的制轰,說白了,最后這些類的實(shí)例最后都是反射得到的胞谭,他們的生命周期艇挨,系統(tǒng)可不管,所以直接使用動(dòng)態(tài)工程中的fragment韭赘、activity缩滨、service等有生命周期的組件肯定是不行的,但是方法總是有地泉瞻,那就是“占坑”脉漏,那就是預(yù)先在宿主中注冊(cè)一些組件,如activity袖牙、service侧巨,然后讓這些組件來代理動(dòng)態(tài)工程中組件的生命周期,這樣問題就解決了鞭达,但是也有其他問題呀司忱,那就是動(dòng)態(tài)工程中的資源怎樣使用呢,能不能通過R去訪問呢畴蹭,這些問題留在后面解答坦仍,在我的dltest工程里尋找答案吧
看一下DLoadActivity吧,看看怎樣代理fragment生命周期
public class DLoadActivity extends FragmentActivity {
private final static String CLASS_NAME = "classname";
private String mClassName;
private Bundle mBundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != getIntent()) {
mClassName = getIntent().getStringExtra(CLASS_NAME);
mBundle = getIntent().getExtras();
}
setContentView(getContentView());
}
private View getContentView() {
// TODO Auto-generated method stub
LinearLayout ll_content = new LinearLayout(this);
ll_content.setOrientation(LinearLayout.VERTICAL);
ll_content.setId(10000012);
FragmentTransaction ft = this.getSupportFragmentManager()
.beginTransaction();
// 通過類名叨襟,反射獲取到對(duì)應(yīng)的類繁扎。既Fragment
Fragment fragment = (Fragment) DexExcutor.getInstance(this)
.newInstance(mClassName);
if (null != fragment) {
if (null != mBundle) {
fragment.setArguments(mBundle);
}
ft.add(ll_content.getId(), fragment, mClassName);
ft.commit();
}
return ll_content;
}
//調(diào)用DLoadActivity加載fragment
public static void start(Context context, String className, Bundle bundle) {
Intent intent = new Intent(context, DLoadActivity.class);
if (null != bundle) {
intent.putExtras(bundle);
}
intent.putExtra(CLASS_NAME, className);
context.startActivity(intent);
}
}
動(dòng)態(tài)工程中fragment又是怎樣寫的呢?
public class ListFragment extends Fragment implements OnItemClickListener {
private RelativeLayout rl_content;
private MBaseAdapter mBaseAdapter;
private List<ListItem> data;
@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
initData();
return getContentView();
}
private View getContentView() {
rl_content = new RelativeLayout(getActivity());
ListView listView = new ListView(getActivity());
rl_content.addView(listView);
listView.setAdapter(mBaseAdapter);
listView.setOnItemClickListener(this);
return rl_content;
}
...后面代碼就不粘貼啦
在這個(gè)demo中糊闽,可不能給fragment寫布局文件了梳玫,必須進(jìn)行代碼布局了,其實(shí)也沒啥嘛右犹,布局文件能干的提澎,我都能在代碼中實(shí)現(xiàn),不就是多寫幾行代碼而已念链,但是圖片資源怎樣使用呢盼忌?
看看下面吧
在動(dòng)態(tài)工程中使用圖片資源
這是動(dòng)態(tài)工程中另一個(gè)fragment积糯,在它中,使用了圖片資源
public class DetailFragment extends Fragment {
@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
return getContentView();
}
//照樣通過代碼布局
private View getContentView() {
LinearLayout linearLayout = new LinearLayout(getActivity());
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView title = new TextView(getActivity());
ImageView imageView=new ImageView(getActivity());
WebView webView = new WebView(getActivity());
title.setPadding(20, 20, 20, 20);
title.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
title.setGravity(Gravity.CENTER);
webView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// TODO Auto-generated method stub
return false;
}
});
webView.getSettings().setJavaScriptEnabled(true);
linearLayout.addView(title);
linearLayout.addView(imageView);
linearLayout.addView(webView);
//獲取資源碴犬,就這樣絮宁,將圖片名稱傳入這個(gè)工具類,就可以獲取bp對(duì)象
Bitmap imageResouce = new ResourceUtils(getActivity()).getImageResouce("assets/car.jpg");
imageView.setImageBitmap(imageResouce);
Bundle bundle = getArguments();
if(bundle!=null&&( bundle.getSerializable("item")!=null)){
if(bundle.getSerializable("item") instanceof ListItem) {
ListItem listitem = (ListItem) bundle.getSerializable("item");
title.setText(listitem.text);
webView.loadUrl(listitem.url);
}
}
return linearLayout;
}
}
ResourceUtils是一個(gè)工具類服协,就是讀取文件中圖片而已绍昂,有興趣可以看看,也可以完善一下
代碼分析到此結(jié)束偿荷,如有不懂窘游,還是看看code吧,show you code跳纳!忍饰,但是本篇教程距離結(jié)束還早呢,還是往下看吧
怎樣將動(dòng)態(tài)工程打包呢寺庄?
task buildLib (type: Jar,dependsOn:'build') {
from ('build/intermediates/classes/release')
//包含資源目錄
from ('src/main/assets/')
//from fileTree(dir: 'src/main',includes: ['assets/**'])
}
自定義task艾蓝,打包的時(shí)候也需要用到的圖片放到j(luò)ar中,需要將圖片放入到assets文件夾下的assets下
執(zhí)行buildLib即可生成動(dòng)態(tài)工程對(duì)應(yīng)的jar斗塘,所在目錄為build/lib
對(duì)生成的jar再次處理
android dalvik 不能直接加載變異.class文件赢织,需要再次處理一下,編譯成dex文件馍盟,通過dx命令即可于置,
格式如下
dx --dex --output=out.jar in.jar
次工程對(duì)應(yīng)的批處理文件(在builddex中)如下:
cd E:\android_dev\sdk\sdk\build-tools\23.0.2\
e:
dx --dex --output=F:\projects\mprojects\DLoad\builddex\dex\plugin.jar F:\projects\mprojects\DLoad\builddex\dloadplugin.jar
需要處理的jar為dloadplugin.jar,生成的jar為plugin.jar贞岭,執(zhí)行dx命令需要到對(duì)應(yīng)文件夾下執(zhí)行才行八毯,如我的:E:\android_dev\sdk\sdk\build-tools\23.0.2\,不多解釋了瞄桨,很簡(jiǎn)單
最后话速,將生成的plugin.jar放入宿主工程的assets目錄即可(本工程是這樣做的,真正開發(fā)中讲婚,jar往往會(huì)放在服務(wù)端尿孔,宿主去檢查時(shí)候需要更新插件),本工程只為演示而生筹麸,更多變化,以及完善雏婶,這都需要你結(jié)合實(shí)際開發(fā)而去應(yīng)對(duì)物赶。
總結(jié)一下吧
動(dòng)態(tài)加載用法流程如下:
- 定義宿主和動(dòng)態(tài)工程交互接口如:ILoad,并預(yù)先注冊(cè)需要的組件
- 宿主或著動(dòng)態(tài)工程中實(shí)現(xiàn)接口
- 生成動(dòng)態(tài)jar
- 通過dexclassloader加載動(dòng)態(tài)jar包留晚,動(dòng)態(tài)jar包可以來源于服務(wù)端或者其他地方
- 宿主調(diào)用動(dòng)態(tài)工程中ILoad的實(shí)現(xiàn)酵紫,動(dòng)態(tài)工程也可以調(diào)用宿主方法告嘲,宿主實(shí)現(xiàn)一下ILoad既可(工程中沒有體現(xiàn),原理比較簡(jiǎn)單奖地,通過dloadlib橄唬,直接調(diào)用就行)
最后說一下注意點(diǎn)
工程中值演示了加載一個(gè)動(dòng)態(tài)jar,當(dāng)然也可以加載多個(gè)参歹,但是每一個(gè)動(dòng)態(tài)jar對(duì)應(yīng)一個(gè)dexclassloader對(duì)象
對(duì)jar的升級(jí)維護(hù)校驗(yàn)仰楚,工程沒有體現(xiàn),真正開發(fā)犬庇,這些都要考慮僧界。
暫時(shí)就這些吧,后期如有需求和疑問臭挽,還會(huì)再補(bǔ)充完善的
周一捂襟、二會(huì)不斷更新內(nèi)容,歡迎持續(xù)關(guān)注andoop欢峰,每周干貨永不停葬荷!