通用庫
在開發android應用時,一般會使用一些現有庫來縮短開發週期,將代碼進行模塊化;
使用框架雖然可能會增加最終代碼量,但在開發過程中會非常方便。
android從出生到現在已經很多年了,因此有大量的庫可供使用,android使用的技能不會特別多,但比較雜,雖然所有的代碼都可以通過手✖完成,但這個過程肯定會相當痛苦。
很多庫使用率都很高,通過github上的star數量可以分辨出哪些庫比較主流,通過更新頻率也可以查看哪些庫依然在維護。現整理一些比較好的庫,同時通過這些項目來搭建出一般android項目可以直接通用的框架代碼。
一、框架需設計的庫
雖然說一般框架不需要太多的內容,不過要實現大部分的功能,還是需要引用較多的資源庫的,這裏進行列舉,然後接下來進行簡單的說明:
- 基本通用包(系統自帶或者support庫提供):
- multidex:dex分包處理
- constraint
- palette:着色
- cardview:卡片佈局
- design
- 數據庫:
- litepal
- 自動注入/變量賦值框架:
- dagger:爲成員變量等自動賦值
- butterknife:依據xml自動生成變量並賦值,點擊監聽
- 線程任務處理:
- Rxjava(RxAndroid)
- 網絡任務:
- retrofit
- okhttp:http核心庫
- 數據類型轉換:
- Gson:json數據處理
- 圖片處理:
- Glide:圖片下載裁剪
- zxing-library:二維碼圖片識別或生成
- takephoto:從設備中選擇圖片或拍照獲得圖片
- 工具類:
- utilcode:這是工具庫集合,提供各種常用的操作處理
- statusbarutil:狀態欄工具類
- 調試時檢測優化:
- leakcanary-android
- 日誌工具
- Logger:打印日誌
- 彈出框:
- pickerview:時間選擇器、條件選擇器
- 權限處理:
- permissionsdispatcher:無反射動態請求權限
- 自定義view:
- flowlayout:tag組
- xrecyclerview:可上拉加載下拉刷新的recyclerview
- convenientbanner:輪播插件
- BottomNavigationViewEx:底部導航欄(多tab視圖)
- roundedimageview:圓角頭像,可存在border
- gridPasswordView:密碼框
- badge:角標庫
- 界面跳轉:
- ARouter:阿里推出的庫,可實現路由跳轉、攔擊、降級、參數自動注入
- 其他:
- pushsdk:友盟統計、消息推送(module形式)
- rxdownload:基於Rxjava的用於軟件更新的庫
- logging-interceptor:網絡請求時打印信息
- adapter-rxjava:將網絡請求變爲Rxjava監聽形式
- converter-gson:網絡返回數據(若爲gson格式)轉爲bean
大部分的庫都已經列舉出來了;
其中一些庫如自定義View等內容可以參考github源碼,也可以在項目中找到對應使用的位置
對於友盟推送等內容,也已經在代碼中集成;同時在BaseApplication類中進行了初始化
動態權限庫自身在AndroidStudio中是有插件的,並且源碼中啓動Activity就是示例;
LocationPickerView也提供了自定義View的模版。
其中有些庫功能很強大,但使用起來會進行其他配置,因此單獨說明:
二、網絡請求模塊
該模塊由Rxjava、RxAndroid、logging-interceptor、adapter-rxjava、converter-gson、retrofit、okhttp等項目包組成,將網絡請求、數據轉換、異常處理、日誌輸出等功能內聚到一起,整個架構可用簡圖概括(只是草圖):
這樣用戶只需要將網絡請求的字段傳入,並定義可以處理的結構以及異常情況下的處理,就可以省略中間的具體的過程。
具體實現如下:
@Provides
@Singleton
HttpInterface provideHttpInterface() {
//網絡請求的Host
String baseUrl = BaseApplication.app.getBaseNetUrl();
//生成JSON轉換的庫
Gson gson = new GsonBuilder()
.serializeNulls()
.setDateFormat("yyyy:MM:dd HH:mm:ss")
.create();
GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
//生成RxJava轉換的adapter
RxJava2CallAdapterFactory rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create();
//生成OkHttp網絡傳輸的客戶端
HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url.host(), cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url.host());
return cookies != null ? cookies : new ArrayList<>();
}
})
.addInterceptor(chain -> {
Request request = chain.request()
.newBuilder()
.addHeader("SDK", String.valueOf(Build.VERSION.SDK_INT ))
.build();
return chain.proceed(request);
})
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addNetworkInterceptor(new StethoInterceptor())
.connectTimeout(2000, TimeUnit.MILLISECONDS)
.readTimeout(2000, TimeUnit.MILLISECONDS)
.writeTimeout(2000,TimeUnit.MILLISECONDS)
.build();
//最後組合成Retrofit對象
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.baseUrl(baseUrl)
.client(okHttpClient)
.build();
//將註解後的interface請求接口轉換爲真正可用的網絡請求對象
return retrofit.create(HttpInterface.class);
}
可以看到,最後其實是生成了HttpInterface對象。這個HttpInterface對象是自定義的,自身是接口類型,用於定義網絡請求所需的參數,以及可以處理的返回數據的類型,如假如需要請求一次網絡:
public interface HttpInterface {
/**
* 登錄
*
* @param username 用戶名
* @param pwd 密碼
* @return 登錄返回對象
*/
@POST("login/doLogin")
@FormUrlEncoded
Observable<BaseHttpBean<Object>> doLogin(@Field("username") String username,
@Field("pwd") String pwd);
}
如上一般,只需要定義參數,以及可解析的數據對象即可。
然後發起網絡請求:
/**
* 舉例請求網絡數據
*/
@OnClick(R.id.fab)
public void onViewClicked() {
httpInterface.doLogin("name", "password")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<BaseHttpBean<Object>>() {
@Override
public void accept(BaseHttpBean<Object> objectBaseHttpBean) throws Exception {
// TODO: 2018/3/10 成功
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
// TODO: 2018/3/10 失敗
}
});
}
網絡請求的超時判斷、數據 轉換等操作,觀察者方法執行時,就已經完成,如果需要添加其他的網絡任務,只需要在HttpInterface接口中添加對應的方法即可。
三、自動注入模塊
通過dagger可以把已經註解的成員變量進行賦值,只要提供對應類型的提供方即可;
利用dagger框架也可以很方便的實現MVP模式,同時可以省略View層與Presenter層的賦值操作,只要有需要對應變量的地方,直接通過@Inject註解即可。
Butterknife則專注於 對xml佈局文件中,變量的生成,以及控件的點擊監聽,同時ButterKnife還在Androidstudio上實現了plugin,使用起來比較方便。直接百度即可,這裏不額外說明;
以剛纔的網絡請求模塊,可能需要在多處調用HttpInterface的實例,這時就可以 通過dagger來完成這一功能:
1、爲dagger定義變量作用範圍
一般按照模版,定義兩個註解即可:
定義Activity作用範圍:
/**
* 功能----定義每個activity的生命週期,供dagger框架使用
* <p>
* Created by MNLIN on 2017/9/22.
*/
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}
定義Fragment作用範圍:
/**
* 功能----fragment對應dagger的生命週期控制
* <p>
* Created by MNLIN on 2017/9/23.
*/
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerFragment {
}
2、定義作用範圍內需要的變量,或全局變量
Activity和Fragment作用範圍內的變量是局部的;
Application作用範圍是全局的(Activity和Fragment需設置依賴Application的compont);
這點是通過compont組件來保證的;
這裏只寫出全局模式變量的注入方法:
/**
* 功能----應用的組件
* <p>
* Created by MNLIN on 2017/9/22.
*/
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(BaseApplication application);
HttpInterface initHttpInterface();
}
可以看到,在ApplicationComponent中聲明瞭HttpInterface,這樣所有的位置只要通過@Inject進行註解,就可以獲取對應實例。
而該實例的創建則是通過module提供的,這點對於Activity,Fragment,Application相同:
/**
* 功能----Application的module,爲ApplicationComponent提供對象生成器
* Created by MNLIN on 2017/9/22
*/
@Singleton
@Module
public class ApplicationModule {
String tag = "";
private BaseApplication application;
public ApplicationModule(BaseApplication application) {
this.application = application;
}
@Provides
@Singleton
HttpInterface provideHttpInterface() {
//網絡請求的Host
String baseUrl = BaseApplication.app.getBaseNetUrl();
//生成JSON轉換的庫
Gson gson = new GsonBuilder()
.serializeNulls()
.setDateFormat("yyyy:MM:dd HH:mm:ss")
.create();
GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);
//生成RxJava轉換的adapter
RxJava2CallAdapterFactory rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create();
//生成OkHttp網絡傳輸的客戶端
HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url.host(), cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url.host());
return cookies != null ? cookies : new ArrayList<>();
}
})
.addInterceptor(chain -> {
Request request = chain.request()
.newBuilder()
.addHeader("SDK", String.valueOf(Build.VERSION.SDK_INT ))
.build();
return chain.proceed(request);
})
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addNetworkInterceptor(new StethoInterceptor())
.connectTimeout(2000, TimeUnit.MILLISECONDS)
.readTimeout(2000, TimeUnit.MILLISECONDS)
.writeTimeout(2000,TimeUnit.MILLISECONDS)
.build();
//最後組合成Retrofit對象
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.baseUrl(baseUrl)
.client(okHttpClient)
.build();
//將註解後的interface請求接口轉換爲真正可用的網絡請求對象
return retrofit.create(HttpInterface.class);
}
}
通過這樣一個流程,就可以完成前期設定工作,當然還需要最後一步:
3、在基類中初始化
之前只是進行了準備,但是真正注入還是需要在代碼執行時候來啓動,因此在BaseActivity、BaseFragment、BaseApplication中需要添加初始化代碼:
這裏只顯示Application部分:BaseApplication類:
//注入dagger框架
applicationComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
applicationComponent.inject(this);
具體詳細設置可以參考源碼;
四、跳轉攔截模塊——ARouter
這個模塊雖然基本功能看上去和平時使用startActivity的方式差不了多少,但是真正帶入後會發現很多精巧的地方,表面上看起來是提供路由跳轉的其他一種方式,只是高端一些,不過真正使用起來會發現出奇的好。
1、定義字符串,供路由跳轉
一般來說service使用的情況比Activity和Fragment要少的多,因此如果需要使用service的話,可以前往ARouter的GITHUB進行查看。
ARouter框架提供了:路由跳轉,碎片生成,降級,自定義序列化等服務;不過用的雖然不多,但比較精巧的則是權限判斷;
引用ARouter自己的說明:
6.爲目標頁面聲明更多信息
// 我們經常需要在目標頁面中配置一些屬性,比方說"是否需要登陸"之類的
// 可以通過 Route 註解中的 extras 屬性進行擴展,這個屬性是一個 int值,換句話說,單個int有4字節,也就是32位,可以配置32個開關
// 剩下的可以自行發揮,通過字節操作可以標識32個開關,通過開關標記目標頁面的一些屬性,在攔截器中可以拿到這個標記進行業務邏輯判斷
@Route(path = "/test/activity", extras = Consts.XXXX)
具體功能可以查看項目代碼:
以下給出引導部分:
/**
* 功能----路徑跳轉activity/fragment
*
* <p>
* Created by MNLIN on 2017/11/24.
*/
public final class ARouterConst {
/**
* 無權限
* 登錄
* 綠色通道(若設定則無法跳轉,相當於禁止功能)
* activity啓動:清除任務棧
*/
public static final int FLAG_NONE = 0x00000000;
public static final int FLAG_LOGIN = 0x00000003;
public static final int FLAG_FORCE_ACCESS = 0x00000040;
public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x00000200;
/**
* activity/fragment
*/
public static final String Activity_SelectFunctionActivity = "/activity/SelectFunctionActivity";
public static final String Activity_SearchFilterActivity = "/activity/SearchFilterActivity";
public static final String Fragment_WalletFragment = "/fragment/WalletFragment";
}
這裏下方以Activity_或者Fragment_開頭的常量提供ARouter路由進行最基本的功能跳轉,而上方則定義了項目中可能會用到的“權限”;
假設現在有一場景,活動A向活動B進行跳轉,此時要求用戶已經登錄,否則的話就需要中斷跳向B的操作,直接跳轉到活動C;
一般情況下可能我們會在B中進行判斷,若是未登錄的話,則主動將頁面路由到C,可是這樣的話,實際並沒有阻止跳轉操作,若是C中一開始就進行危險操作,則可能會直接崩潰。
或者說可以在A中進行判斷,但如此一來,若是向B跳轉的活動過多,就需要在多個活動中編寫同樣的代碼,當然這可以編寫工具類,但若是頁面過多,這種邏輯會很麻煩.
而ARouter則相當於在跳轉過程中進行攔截,所有不符合條件的跳轉都將另行處理:
/**
* function : 跳轉攔截器(權限攔截)
* <p>
* 比較經典的應用就是在跳轉過程中處理登陸事件,這樣就不需要在目標頁重複做登陸檢查
* 攔截器會在跳轉之間執行,多個攔截器會按優先級順序依次執行
*
* @author MNLIN
*/
@Interceptor(priority = 2, name = "ARouter跳轉攔截器")
public class ARouterInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Logger.v("######界面跳轉 : " + postcard.toString());
//當前所有的權限
String[] permissions = new String[]{
"登錄",
"綠色通道",//若目標此flag設定,則表示禁止跳轉
"棧單例模式"//若目標設置此flag,則添加singTask標誌
};
int[] FLAGS_ALL = new int[]{
ARouterConst.FLAG_LOGIN,
ARouterConst.FLAG_FORCE_ACCESS,
ARouterConst.FLAG_ACTIVITY_CLEAR_TOP
};
//當前所有權限對應的boolean值;爲false則對應權限設爲 ARouterConst.FLAG_NONE
boolean[] FLAGS_ALL_VALUE = new boolean[]{
DefaultPreferenceUtil.getInstance().hasLogin(),
false,
false
};
//當前所有的權限
int currentFlags = Integer.MIN_VALUE;
for (int position = 0; position < FLAGS_ALL.length; position++) {
currentFlags |= FLAGS_ALL_VALUE[position] ? FLAGS_ALL[position] : ARouterConst.FLAG_NONE;
}
Logger.v("######當前所有權限 : " + Integer.toBinaryString(currentFlags));
//目標界面需要的權限
int requireFlags = postcard.getExtra() | Integer.MIN_VALUE;
Logger.v("######目標所需權限 : " + Integer.toBinaryString(requireFlags));
//如果需要的權限都已存在,則直接跳轉,不做處理
if ((requireFlags & currentFlags) == requireFlags) {
callback.onContinue(postcard);
return;
}
//如果發現不一致,說明某些權限不存在,則需要依次判斷哪個權限不存在
for (int position = 0; position < FLAGS_ALL.length; position++) {
if ((requireFlags & FLAGS_ALL[position]) != 0 && (currentFlags & FLAGS_ALL[position]) == 0) {
// TODO: 2018/1/20 沒有對應的f權限
boolean consume = false;
switch (position) {
case 0: //未登錄
consume = dispatchLogin(postcard, callback);
break;
case 1:
break;
case 2: //棧單例模式
consume = dispatchSingleTask(postcard, callback);
break;
default: {
callback.onInterrupt(new RuntimeException("沒有 " + permissions[position] + " 權限"));
}
}
if (!consume) {
callback.onInterrupt(new RuntimeException("界面無法跳轉"));
}
return;
}
}
//權限定義錯誤
RxBus.getInstance().post(new BaseEvent(Const.SHOW_TOAST, "未知權限"));
}
/**
* 請求單例啓動
*
* 清除棧上其他活動
*/
private boolean dispatchSingleTask(Postcard postcard, InterceptorCallback callback) {
postcard.withFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
callback.onContinue(postcard);
return true;
}
/**
* 處理未登錄操作
*/
private boolean dispatchLogin(Postcard postcard, InterceptorCallback callback) {
RxBus.getInstance().post(new BaseEvent(Const.SHOW_LOGIN_DIALOG, null));
return false;
}
@Override
public void init(Context context) {
// 攔截器的初始化,會在sdk初始化的時候調用該方法,僅會調用一次
}
/**
* 更換意圖的跳轉路徑
* 然後進行跳轉處理
*
* @param postcard 意圖
* @param des 目的 string
*/
private void replaceDes(Postcard postcard, String des) {
//動態的修改postcard信息,更換跳轉路徑
Postcard newPostcard = ARouter.getInstance().build(des);
LogisticsCenter.completion(newPostcard);
postcard.setPath(newPostcard.getPath()).setGroup(newPostcard.getGroup()).setDestination(newPostcard.getDestination());
}
}
如代碼所示,可以分爲四個步驟:
- 獲取當前應用擁有的所有權限
- 獲取目標頁面所需的權限
- 進行判斷,若是目標頁面所需權限當前已擁有,則跳轉成功;否則進行步驟4
- 按順序判斷是什麼權限未滿足,通過 dispatch*** 方法進行處理
同時 項目中放置了很多方便操作的工具類以及代碼生成器;
如在 build.gradle 中添加了生成MVP模式類的代碼:
/**
* Desc: generate mvp architecture code automatically
*
* @Des For example: gradle mvp -D domain="Test" -D path="/activity"
* def domainParam = System.getProperty('domain')
* def pathParam = System.getProperty('path')
*
* */
task mvp_activity(type: GenerateMVPActivity) {
group 'personal'
description 'generate java code for MVP-Activity architecture'
def domainParam = "Test"
def pathParam = "/activity"
if (domainParam && pathParam) {
domain domainParam
uiPath pathParam
}
}
使用時將 domainParam 重新賦值,然後通過任務就可以生成相應的代碼。
任務執行可以通過AndroidStudio右側的任務列表進行:
也可以在該任務上: 右鍵 => Assign ShortCut => 添加快捷鍵
以後每次生成代碼,只需要修改domainParam 字段值,然後使用快捷鍵生成代碼即可,不過要注意: 不能使用相同名字的Activity及Fragment,會相互覆蓋;
功能一般而言足夠普通項目使用,只需要在使用時進行:修改包名,設置友盟KEY 等操作後就可以添加業務處理了。