高質(zhì)量 Android 開(kāi)發(fā)框架 LoonAndroid 詳解
整個(gè)框架式不同于androidannotations,Roboguice等ioc框架,這是一個(gè)類(lèi)似spring的實(shí)現(xiàn)方式。在整應(yīng)用的生命周期中找到切入點(diǎn),然后對(duì)activity的生命周期進(jìn)行攔截,然后插入自己的功能。
框架的說(shuō)明
如果你想看ui方面的東西,這里沒(méi)有,想要看牛逼的效果這里也沒(méi)有。這只是純實(shí)現(xiàn)功能的框架,它的目標(biāo)是節(jié)省代碼量,降低耦合,讓代碼層次看起來(lái)更 清晰。整個(gè)框架一部分是網(wǎng)上的,一部分是我改的,為了適應(yīng)我的編碼習(xí)慣,還有一部分像orm完全是網(wǎng)上的組件。在此感謝那些朋友們。整個(gè)框架式的初衷是為 了偷懶,之前都是一個(gè)功能一個(gè)jar,做項(xiàng)目的時(shí)候拉進(jìn)去,這樣對(duì)于我來(lái)說(shuō)依然還是比較麻煩。最后就導(dǎo)致我把所有的jar做成了一個(gè)工具集合包。有很多框 架都含有這個(gè)工具集合里的功能,這些不一定都好用,因?yàn)檫@是根據(jù)我個(gè)人使用喜歡來(lái)實(shí)現(xiàn)的,如果你們有自己的想法,可以自己把架包解壓了以后,源碼拉出來(lái)改 動(dòng)下。目前很多框架都用到了注解,除了androidannotations沒(méi)有入侵我們應(yīng)用的代碼以外,其他的基本上都有,要么是必須繼承框架里面的 activity,要么是必須在activity的oncreat里面調(diào)用某個(gè)方法。整個(gè)框架式不同于 androidannotations,Roboguice等ioc框架,這是一個(gè)類(lèi)似spring的實(shí)現(xiàn)方式。在整應(yīng)用的生命周期中找到切入點(diǎn),然后對(duì) activity的生命周期進(jìn)行攔截,然后插入自己的功能。
如果需要混淆 第一步 你要先引入你得架包 -libraryjars libs/android-support-v4.jar -libraryjars libs/loonandroid.jar 第二步 你要保證注解在代碼優(yōu)化的時(shí)候不能被刪除掉 -keepattributes Signature -keepattributes Annotation第三步 support4 要排除掉 -dontwarn android.support.v4.**
-keep class android.support.v4.** { ; }
-keep interface android.support.v4.app.* { ; }
-keep public class * extends android.support.v4.*
-keep public class * extends android.app.Fragment 第四步 只要使用了注解的包名 全部排除掉 -dontwarn xxx.**
-keep class xxx.** { ; }
其中XXX替換成你使用了注解的包名第五步 保證R不被混淆 -keep class *.R$* {
*;
} 即OK
框架的主要功能
其中分為以下幾種:
-
自動(dòng)注入框架(只需要繼承框架內(nèi)的application既可)
-
圖片加載框架(多重緩存,自動(dòng)回收,最大限度保證內(nèi)存的安全性)
-
網(wǎng)絡(luò)請(qǐng)求模塊(繼承了基本上現(xiàn)在所有的http請(qǐng)求)
-
eventbus(集成一個(gè)開(kāi)源的框架)
-
驗(yàn)證框架(集成開(kāi)源框架)
-
json解析(支持解析成集合或者對(duì)象)
-
數(shù)據(jù)庫(kù)(不知道是哪位寫(xiě)的 忘記了)
-
多線程斷點(diǎn)下載(自動(dòng)判斷是否支持多線程,判斷是否是重定向)
-
自動(dòng)更新模塊
-
一系列工具類(lèi)
一 自動(dòng)注入框架
1 無(wú)需繼承任何BaseActivity
舉例:普通activity
- public class FourActivity extends Activity {
- View xx;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main4);
- xx = find......;
- //---------------------------------------------------------
- 組件的初始化
- //---------------------------------------------------------
- }
- }
這其中我們會(huì)耗費(fèi)大量的代碼或者重復(fù)性的去些一些代碼。特別是布局比較復(fù)雜的情況下。
如果用框架
- @InjectLayer(R.layout.activity_main3)
- public class ThirdActivity extends Activity {
- @InjectView
- View xx;
- }
即可
像軟件的說(shuō)明頁(yè)面,就是單純的展示一個(gè)布局,那么就是
- @InjectLayer(R.layout.activity_main3)
- public class ThirdActivity extends Activity {
- }
即可
整個(gè)ioc框架不需要你繼承任何的acitivity,這樣就保證了不會(huì)在你的代碼結(jié)構(gòu)層次上造成影響,因?yàn)橛械臅r(shí)候你需要自己的BaseActivity來(lái)實(shí)現(xiàn)你公用的功能。
2 支持子父布局
這種情況下,對(duì)于一般的框架來(lái)說(shuō),做法有以下幾種:
-
ActivityGroup 一般的ioc框架都需要繼承框架內(nèi)的activity,activitygroup會(huì)讓很多框架用不了,現(xiàn)在ActivityGroup也是不提倡的了。
-
BaseActivity 一般的Ioc框架會(huì)需要你的BaseActivity 去繼承框架內(nèi)的activity
-
中間用fragment 這樣的情況也一樣,你的FragmentActivity必須繼承它的activity才能實(shí)現(xiàn)ioc框架功能。對(duì)于這個(gè)框架來(lái)說(shuō)很容易實(shí)現(xiàn)
1 ActivityGroup 你不需要繼承任何activity 和普通activity 實(shí)現(xiàn)方式(如上面的例子)
2 BaseActivity
見(jiàn)代碼:
首先是BaseActivity @InjectPLayer(R.layout.activity_com) public class BaseActivity extends Activity {}
其中R.layout.activity_com是包括上下導(dǎo)航的布局,中間是一個(gè)view子activity只需要這么寫(xiě)即可
@InjectLayer(value = R.layout.activity_main, parent = R.id.common)
public class MainActivity extends BaseActivity {}
當(dāng)然 又會(huì)有問(wèn)題了,那么我上下導(dǎo)航里面的點(diǎn)擊事件怎么綁定,怎么去初始化,難道要每一個(gè)子activity都要去寫(xiě)嗎?當(dāng)然不需要
- @InjectPLayer(R.layout.activity_com)
- public class BaseActivity extends Activity {
- @InjectInit
- private void init() {
- MeApplication.logger.s("公共類(lèi)的初始化");
- }
- // 這里是第一種交互事件注入方式(單擊)
- @InjectMethod(@InjectListener(ids = { R.id.top, R.id.bottom }, listeners = { OnClick.class }))
- private void click2(View view) {
- Handler_TextStyle handler_TextStyle = new Handler_TextStyle();
- switch (view.getId()) {
- case R.id.top:
- handler_TextStyle.setString("點(diǎn)擊了頂部按鈕(在基類(lèi)中統(tǒng)一注冊(cè),也可以單獨(dú)注冊(cè))");
- handler_TextStyle.setBackgroundColor(Color.RED, 3, 5);
- Toast.makeText(this, handler_TextStyle.getSpannableString(), Toast.LENGTH_LONG).show();
- break;
- case R.id.bottom:
- handler_TextStyle.setString("點(diǎn)擊了底部按鈕(在基類(lèi)中統(tǒng)一注冊(cè),也可以單獨(dú)注冊(cè))");
- handler_TextStyle.setBackgroundColor(Color.RED, 3, 5);
- Toast.makeText(this, handler_TextStyle.getSpannableString(), Toast.LENGTH_LONG).show();
- break;
- }
- }
- }
如上 其中@InjectInit注解表示不管是在子activity還是父activity 都是在布局初始化完成以后才會(huì)調(diào)用,其先后順序是
父布局layout->子布局layout->父布局ioc和事件綁定->子布局事件綁定。
父activity 中可以對(duì)所有的公用組件和事件進(jìn)行初始化和綁定還沒(méi)完,又會(huì)有另一個(gè)問(wèn)題,如果我某個(gè)頁(yè)面下導(dǎo)航的a按鈕和其他頁(yè)面底部a按鈕的功能不一樣 要單獨(dú)設(shè)置怎么辦。那么我們可以在子布局進(jìn)行@InjectMethod和@InjectView進(jìn)行事件綁定和組件注入,它們會(huì)覆蓋父類(lèi)中相同id的組 件的操作以下是view注入的方法說(shuō)明:
@InjectPLayer
表示是Activity的setContentView
@InjectLayer(value = R.layout.activity_main2, parent = R.id.common, isFull = true, isTitle = true)
其中需要哪個(gè)參數(shù)就用哪個(gè),value 是必須的 如果只有l(wèi)ayout可以這么寫(xiě)@InjectPLayer(R.layout.activity_com)。其中value 表示layout,parent表示它在父布局中所對(duì)應(yīng)組件的id 如上圖中 中間顯示區(qū)域的view的id。Isfull是否全屏,默認(rèn)為false.isTitle 是否有標(biāo)題,默認(rèn)false;
@InjectView
自動(dòng)注入view注解。
基本寫(xiě)法:
@InjectView
TextView test;
其中test表示它在xml中對(duì)應(yīng)的Id為test
@InjectView(R.id.next2)
TextView test;
表示它在xml中對(duì)應(yīng)的Id為next2
高級(jí)寫(xiě)法:
@InjectView(binders = { @InjectBinder(method = "click", listeners = { OnClick.class, OnLongClick.class }) })
Button next, next3, next4;
其中表示對(duì)id為next,next3,next4進(jìn)行注解,其中binders 表示綁定了以下事件,binders 是個(gè)數(shù)組,也就是說(shuō)可以用多個(gè)InjectBinder綁定多個(gè)事件,也可以用listeners = { OnClick.class, OnLongClick.class }來(lái)表示對(duì)組件注入了點(diǎn)擊事件和長(zhǎng)按事件
@InjectView(value = R.id.next2, binders = { @InjectBinder(method = "click", listeners = { OnClick.class }) })
Button button;
對(duì)于變量名和組件id不一致的view則需要設(shè)置value Click 表示那些注入的事件觸發(fā)以后所調(diào)用的方法,其必須在當(dāng)前類(lèi)內(nèi)。 // 支持由參數(shù)和無(wú)參數(shù) 即click(View view)或者click() 當(dāng)然click名字必須對(duì)于變量注解中的method = “click”
- private void click(View view) {
- switch (view.getId()) {
- case R.id.next:
- startActivity(new Intent(this, ThirdActivity.class));
- break;
- ...
- }
- }
- @InjectResource
- @InjectResource
- String action_settings;
- @InjectResource
- Drawable ic_launcher;
- InjectResource支持string和drawable的注解
- @InjectMethod
// 底部導(dǎo)航欄 子類(lèi)覆蓋父類(lèi)
- @InjectMethod(@InjectListener(ids = { R.id.bottom }, listeners = { OnClick.class, OnLongClick.class }))
- private void click3(View view) {
- Handler_TextStyle handler_TextStyle = new Handler_TextStyle();
- handler_TextStyle.setString("點(diǎn)擊了底部按鈕 子類(lèi)覆蓋了父類(lèi)");
- handler_TextStyle.setBackgroundColor(Color.RED, 3, 5);
- Toast.makeText(this, handler_TextStyle.getSpannableString(), Toast.LENGTH_LONG).show();
- }
@InjectMethod是當(dāng)我們對(duì)一個(gè)組件只需要觸發(fā)而不需要find出來(lái)的時(shí)候用到。 ids 表示綁定哪些id,listeners 表示綁定哪些事件 這兩個(gè)參數(shù)都是數(shù)組
當(dāng)然 如果嫌注解字段太長(zhǎng),可以自己修改。這個(gè)是整個(gè)view的注入。
- @InjectInit
- @InjectInit
- void init() {
- MeApplication.logger.s("子類(lèi)的初始化");
- test.setText("初始化完成,第一個(gè)頁(yè)面");
- }
這個(gè)注解你在activity中添加到任何一個(gè)方法名上,那么,當(dāng)所有的layout和所有的view以及事件綁定完畢以后,會(huì)第一個(gè)調(diào)用含有這個(gè)注解的方法。它相當(dāng)于oncreat
注意:框架注解了整個(gè)activity的生命周期, @InjectOnNewIntent,@InjectPause,@InjectResume,
@InjectRestart,@InjectStart,@InjectStop 其中OnDestroy無(wú)注解。
如果Activity中有含有這些注解的方法 那么不同生命周期下回自動(dòng)調(diào)用這些方法
二:Fragment的自動(dòng)注入
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- this.inflater = inflater;
- View rootView = inflater.inflate(R.layout.activity_left, container, false);
- Handler_Inject.injectView(this, rootView);
- return rootView;
- }
只需要在onCreateView里面調(diào)用Handler_Inject.injectView(this, rootView);
即可
在fragment中除了activity的生命周期注解和@InjectLayer注解無(wú)法使用外,組件綁定和事件綁定都可以使用,@InjectBefore也可以使用
@InjectBefore 是在組件初始化之前調(diào)用
三:圖片下載框架
這個(gè)是圖片框架重寫(xiě)了好多次了,總是有點(diǎn)問(wèn)題,里面基本上每一段代碼都有注釋?zhuān)€有一些bug,
因?yàn)轫?xiàng)目中用的還是這次重寫(xiě)之前的。
如果大家發(fā)現(xiàn)問(wèn)題,記得告訴我框架中調(diào)用的方法名和參數(shù)基本上都不會(huì)變,避免替換jar導(dǎo)致需要改動(dòng)大部分代碼。
整個(gè)圖片下載的邏輯是這樣的:
-
1 根據(jù)url和view去調(diào)用圖片下載的方法
-
2 從緩存去拿bitmap
-
3 如果bitmap不為空 判斷是否針對(duì)這個(gè)url有單獨(dú)的配置 沒(méi)有則使用全局配置加載圖片
-
4 如果bitmap為空 則開(kāi)啟線程,放到本地線程池中,然后從本地文件讀取
-
5 如果文件存在,則轉(zhuǎn)為bitmap放到緩存,然后重復(fù)2,然后9
-
6 如果文件不存在,則開(kāi)啟線程放到網(wǎng)絡(luò)線程池中去下載文件
-
7 下載成功則放到本地sdcard 然后把文件轉(zhuǎn)為bitmap放到緩存,然后重復(fù)2,然后9
-
8 下載不成功,然后重復(fù)2,然后9
-
9 如果bitmap不為空 判斷是否針對(duì)這個(gè)url有單獨(dú)的配置 沒(méi)有則使用全局配置加載這張圖片 如果bitmap為空 則顯示失敗的默認(rèn)圖
具體的流程 可以參考源碼
緩存分為三層
第一層是LruCache(原理去百度)
第二層是LinkedHashMap
第三層是 view標(biāo)記
1 當(dāng)LruCache中的圖片超過(guò)了規(guī)定了內(nèi)存,那么從LruCache移除一個(gè)使用最少的,放到LinkedHashMap中
2 當(dāng)每一張圖片的url對(duì)應(yīng)一個(gè)count,一旦加載一張圖片,那么這個(gè)url的count加1
3 自定義AsyImageView繼承ImageView,重寫(xiě)了onDetachedFromWindow方法,一旦
AsyImageView從當(dāng)前視圖移除掉會(huì)調(diào)用onDetachedFromWindow該方法,此刻該圖片所對(duì)應(yīng)的url數(shù)目count減1
4 因?yàn)閘istview中的imageview如果用了ViewHolder那么第3條就不適合了,此刻每一個(gè)imagview的hashCode對(duì)應(yīng)一個(gè)url,
一旦imagview更換了一個(gè)新的url,那么該imagview的hashcode上一個(gè)的引用將被移除,
那么上一次顯示的url所對(duì)應(yīng)的count將減1
5 當(dāng)LinkedHashMap超過(guò)了規(guī)定限制的時(shí)候,那么遍歷所有的count一旦count為0 則移除回收
圖片下載使用
一:必須條件
必須在配置文件中添加配置,來(lái)打開(kāi)圖片下載引擎的初始化,為了減少啟動(dòng)時(shí)間,默認(rèn)關(guān)閉。
#開(kāi)啟框架內(nèi)置的圖片下載 如果不設(shè)置 則無(wú)法使用框架類(lèi)的圖片下載
imageload_open=true
二:使用方法
1 普通圖片下載
ImageDownloader.download("網(wǎng)絡(luò)和本地圖片鏈接",mAsyImageView);
如果需要配置bitmap的高寬
第一種方式:
在xml布局文件中對(duì)AsyImageView的高寬進(jìn)行設(shè)置
第二種方式:
全局圖片配置,所有圖片顯示默認(rèn)用此配置
GlobalConfig globalConfig = GlobalConfig.getInstance();
globalConfig.setMaxWidth(w);
來(lái)設(shè)置
第三種
SingleConfig config = new SingleConfig();
config ....設(shè)置寬高
ImageDownloader.download("網(wǎng)絡(luò)和本地圖片鏈接",mAsyImageView,config )
其中優(yōu)先級(jí)
第三種 > 第一種 > 第二種
其中GlobalConfig 支持的設(shè)置有高寬的設(shè)置,內(nèi)存緩存的大小,默認(rèn)圖片,
下載失敗的圖片,最大緩存數(shù)目,線程池,緩存類(lèi)型,顯示控制,listview得滑動(dòng)監(jiān)聽(tīng),圖片加載動(dòng)畫(huà)
其中SingleConfig 支持的設(shè)置有高寬的設(shè)置,默認(rèn)圖片,下載失敗的圖片,下載進(jìn)度,顯示控制,加載動(dòng)畫(huà)
其中SingleConfig 優(yōu)先于GlobalConfig
支持配置文件配置:
mAsyImageView.setTemplate("one");
ImageDownloader.download("url",mAsyImageView);
其中one在配置文件里面配置,這樣 不管在任何地方,只要AsyImageView.setTemplate(“one”);就可以使用名稱(chēng)為one的配置了。
支持本地文件加載調(diào)用接口不變。
需要進(jìn)度顯示的:
- SingleConfig config = new SingleConfig();
- config.setDisplayer(new DisplayerLister() {
- @Override
- public void startLoader(AsyImageView imageView) {
- super.startLoader(imageView);
- }
- @Override
- public Bitmap finishLoader(Bitmap bitmap, AsyImageView imageView) {
- pin_progress_1.setVisibility(View.GONE);
- return bitmap;
- }
- @Override
- public void progressLoader(int progress, AsyImageView imageView) {
- pin_progress_1.setProgress(progress);
- super.progressLoader(progress, imageView);
- }
- });
- ImageDownloader.download("url",photo,config);
其中url的服務(wù)器必須支持獲取文件長(zhǎng)度
需要顯示動(dòng)畫(huà)的:如果是單獨(dú)某一個(gè)圖片
- SingleConfig config = new SingleConfig();
- config.setDisplayerAnimation(new FadeInAnimation());
如果是全局的
- GlobalConfig config = new GlobalConfig();
- config.setDisplayerAnimation(new FadeInAnimation());
其中FadeInAnimation是框架自帶的一個(gè)漸變的動(dòng)畫(huà)如果需要自定義 實(shí)現(xiàn)DisplayerAnimation接口即可
2 listview中圖片下載
只要在listview的注解@InjectView(isasy=true)中添加了isasy=true(默認(rèn)為false)
那么系統(tǒng)會(huì)自動(dòng)給你注入OnScrollListener滾動(dòng)事件,以便實(shí)現(xiàn)圖片飛行停止才加載,緩慢拖動(dòng)加載的功能。如果你要實(shí)現(xiàn)自己的OnScrollListener
如下
- @InjectBefore
- void test(){
- //@InjectView(isasy=true)表示這個(gè)listview里面有網(wǎng)絡(luò)圖片下載,并且需要實(shí)現(xiàn)滑動(dòng)停止才加載的功能
- //@InjectView(isasy=true)框架會(huì)給listview自動(dòng)注入OnScrollListener,如果你自己也要滾動(dòng)監(jiān)聽(tīng)
- //那么請(qǐng)?jiān)诖伺渲茫缦?/span>
- GlobalConfig config = GlobalConfig.getInstance();
- config.setOnScrollLoaderListener(new MyOnScrollListener());
- System.out.println("before");
- }
- //必須繼承框架內(nèi)的滾動(dòng)監(jiān)聽(tīng)
- class MyOnScrollListener extends OnScrollLoaderListener{
- @Override
- public void onScrollListener(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- ApplicationBean.logger.s("滾動(dòng)監(jiān)聽(tīng):"+firstVisibleItem);
- }
- @Override
- public void onScrollStateChange(AbsListView view, int scrollState) {
- ApplicationBean.logger.s("滾動(dòng)狀態(tài)");
- }
- }
- @InjectBefore 表示在組件初始化以前開(kāi)始調(diào)用,因?yàn)闈L動(dòng)監(jiān)聽(tīng)必須在listview被初始化之前賦值,否則無(wú)效 將默認(rèn)使用框架內(nèi)的滾動(dòng)監(jiān)聽(tīng)
3 無(wú)需顯示的圖片下載
- ImageDownloader.download("url", new LoaderLister() {
- @Override
- public void finishLoader(String url, File file) {
- System.out.println("下載完成"+file.getPath());
- }
- @Override
- public void failLoader(String url) {
- System.out.println("下載失敗");
- }
- });
如果需要下載進(jìn)度
- ImageDownloader.download("url", new LoaderLister() {
- @Override
- public void startLoader(String url) {
- System.out.println("開(kāi)始下載");
- super.startLoader(url);
- }
- @Override
- public void finishLoader(String url, File file) {
- System.out.println("下載完成"+file.getPath());
- }
- @Override
- public void progressLoader(int progress) {
- System.out.println("下載進(jìn)度"+progress);
- super.progressLoader(progress);
- }
- });