
一 引導
|
|
|
|
|
|
|
|
|
|
|
|
二 專案架構演進
1 單專案階段

2 抽象基礎庫階段
-
程式碼的版本控制問題,為保證專案加快迭代,團隊新招1-3名開發同學,多人同時在一個專案上開發時,Git程式碼合併總會出現衝突,非常影響開發效率; -
專案的編譯構建問題,隨著專案程式碼量逐漸增多,執行App都是基於原始碼編譯,以至於首次整包編譯構建的速度逐漸變慢,甚至會出現為了驗證一行程式碼的改動而需要等待大幾分鐘或者更久時間的現象; -
多應用的程式碼複用問題,公司可能同時在進行多個App的開發,同樣的程式碼總是需要透過複製貼上的方式進行復用,維持同一個功能在多個App之間的邏輯一致性也會存在問題;

3 拓展核心能力階段
-
開發職責分離,團隊成員需要分為兩部分,分別對應業務開發和基礎架構。業務開發組負責完成日常業務迭代的支撐,以業務交付為目標;基礎架構組負責底層基礎核心能力建設,以提升效率、效能和核心能力拓展為目標; -
專案架構最佳化,基於1,要在應用層和基礎層之間,抽象出核心架構層,並將其連帶基礎層一起交由基礎架構組負責,如圖;

4 模組化階段

5 跨平臺開發階段

三 專案架構拆解
1 基礎層
基礎UI模組
網路模組
-
維護團隊和社群比較大,遇到問題後能夠有足夠多的自助解決空間; -
底層功能強大,支撐儘可能多的上層應用場景; -
拓展能力靈活,支援在框架基礎上做能力拓展和AOP處理; -
Api側友好,降低上層的理解和使用成本;
// 0. 初始化
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 1. 宣告服務介面
publicinterfaceGitHubService{
Call<List<Repo>> listRepos( String user);
}
// 2. 透過Retrofit獲取服務介面例項
GitHubService service = retrofit.create(GitHubService.class);
// 3. 業務層呼叫
Call<List<Repo>> repos = service.listRepos("octocat");
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://xxx.xxxxxx.xxx")
.client(new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
public Response intercept(@NonNull Chain chain)throws IOException {
// 新增統一請求頭
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + token)
.build();
return chain.proceed(newRequest);
}
})
.build()
)
.build();
圖片模組
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
Uriuri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/main/docs/static/logo.png");
SimpleDraweeViewdraweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
Glide.with(fragment)
.load(myUrl)
.into(imageView);

非同步模組
-
主執行緒做網路請求,會出現NetworkOnMainThreadException異常;
-
主執行緒做耗時任務,很可能會出現ANR(全稱Application Not Responding,指應用無響應);
-
子執行緒做UI操作,會出現CalledFromWrongThreadException異常(這裡只做一般性討論,實際上在滿足某些條件下子執行緒也能更新UI,參《Android 中子執行緒真的不能更新 UI 嗎?》,本文不討論該情況);
-
透過主執行緒Handler的post方法
privatestaticfinal Handler UI_HANDLER = new Handler(Looper.getMainLooper());
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
UI_HANDLER.post(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
-
透過主執行緒Handler的sendMessage方法
privatefinal Handler UI_HANDLER = new Handler(Looper.getMainLooper()) {
publicvoidhandleMessage(@NonNull Message msg){
if (msg.what == MSG_REFRESH_UI) {
refreshUI();
}
}
};
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
UI_HANDLER.sendEmptyMessage(MSG_REFRESH_UI);
}
-
透過Activity的runOnUiThread方法
publicclassMainActivityextendsActivity{
// ...
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
runOnUiThread(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
}
-
透過View的post方法
private View view;
privatevoiddoTask()throws Throwable {
Thread.sleep(3000);
view.post(new Runnable() {
publicvoidrun(){
refreshUI();
}
});
}
-
透過新開執行緒
privatevoidstartTask(){
new Thread() {
publicvoidrun(){
doTask();
}
}.start();
}
-
透過ThreadPoolExecutor
privatefinal Executor executor = Executors.newFixedThreadPool(10);
privatevoidstartTask(){
executor.execute(new Runnable() {
publicvoidrun(){
doTask();
}
});
}
-
透過AsyncTask
private void startTask() {
new AsyncTask<Void, Void, Void>() {
protectedVoid doInBackground(Void... voids) {
doTask();
returnnull;
}
}.execute();
}
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
Future<String> fetchUserOrder() =>
Future.delayed(const Duration(seconds: 2), () =>'Large Latte');
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return'Your order is: $order';
}
functionresolveAfter2Seconds(x) {
returnnewPromise(resolve => {
setTimeout(() => { resolve(x); }, 2000);
});
}
asyncfunctionf1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
source
.operator1()
.operator2()
.operator3()
.subscribe(consumer)
2 核心層
動態配置
-
Android(Native開發)不同於Web能夠隨時釋出上線,Android釋出幾乎都要走應用平臺的稽核;
-
業務上很多時候需要做AB測試或一些配置開關,以滿足業務的多樣性;


-
按照namespace進行多模組劃分配置,避免全域性一個大而全的配置;
-
每個namespace在初始化和每次改動時都會有個flag標識,以標識當前版本;
-
客戶端每個業務請求都在請求頭處統一拉上各flag或他們共同組合的md5等標識,為了在服務端統一攔截時進行flag時效性校驗;
-
服務端時效性檢驗結果透過統一響應頭下發,與業務介面隔離,上層業務方不感知;
-
客戶端收到時效性不一致結果時,再根據具體的namespace進行拉取,而不是每次全量拉取;
全域性攔截
-
彈出Toast
{
"type": "toast",
"content": "您好,歡迎來到XXX",
"gravity": "<這裡填寫toast要展示的位置, 可選項為(center|top|bottom), 預設值為center>"
}
-
彈出Dialog 這裡值得注意的是,Dialog的Action中嵌套了Toast的邏輯,多種Action的靈活組合能給我們提供豐富的互動能力。
{
"type": "dialog",
"title": "提示",
"message": "確定退出當前頁面嗎?",
"confirmText": "確定",
"cancelText": "取消",
"confirmAction": {
"type": "toast",
"content": "您點選了確定"
}
}
-
關閉當前頁面
{
"type": "finish"
}
-
跳轉到某個頁面
{
"type": "route",
"url": "https://www.xxx.com/goods/detail?id=xxx"
}
-
執行某個網路請求 同2,這裡也做了多Action的巢狀組合。
{
"type": "request",
"url": "https://www.xxx.com/goods/detail",
"method": "post",
"params": {
"id": "xxx"
},
"response": {
"successAction": {
"type": "toast",
"content": "當前商品的價格為${response.data.priceDesc}元"
},
"errorAction": {
"type": "dialog",
"title": "提示",
"message": "查詢失敗, 即將退出當前頁面",
"confirmText": "確定",
"confirmAction": {
"type": "finish"
}
}
}
}
-
提供根據頁面和事件標識來獲取服務端下發的Action的能力,這裡用到的DynamicConfig即為前面提到的動態配置。
privatestatic Action getClickActionIfExists(String page, String event) {
// 根據當前頁面和事件確定動作標識
String actionId = String.format("hook/click/%s/%s", page, event);
// 解析動態配置中, 是否有需要下發的Action
String value = DynamicConfig.getValue(actionId, null);
if (TextUtils.isEmpty(value)) {
returnnull;
}
try {
// 將下發Action解析為結構化資料
returnJSON.parseObject(value, Action.class);
} catch (JSONException ignored) {
// 格式錯誤時不做處理 (供參考)
}
returnnull;
}
-
提供包裝點選事件的處理邏輯(performAction為對具體Action的解析邏輯,功能比較簡單,這裡不做展開)
/**
* 包裝點選事件的處理邏輯
*
* @param page 當前頁面標識
* @param event 當前事件標識
* @param clickListener 點選事件的處理邏輯
*/
publicstatic View.OnClickListener handleClick(String page, String event,
View.OnClickListener clickListener) {
// 這裡返回一個OnClickListener物件, 降低上層業務方的理解成本和程式碼改動難度
returnnew View.OnClickListener() {
publicvoidonClick(View v){
// 取出當前事件的下發配置
Action action = getClickActionIfExists(page, event);
if (action != null) {
// 有配置, 則走配置邏輯
performAction(action);
} elseif (clickListener != null) {
// 無配置, 則走預設處理邏輯
clickListener.onClick(v);
}
}
};
}
// 之前
addGoodsButton.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
Router.open("https://www.xxx.com/goods/add");
}
});
// 之後
addGoodsButton.setOnClickListener(ActionManager.handleClick(
"goods-manager", "add-goods", new View.OnClickListener() {
publicvoidonClick(View v){
Router.open("https://www.xxx.com/goods/add");
}
}));
{
"hook/click/goods-manager/add-goods": {
"type": "dialog",
"title": "提示",
"message": "由於XX原因,新增商品頁面暫不可用",
"confirmText": "確定",
"confirmAction": {
"type": "finish"
}
}
}
本地配置
-
對本地配置提供預設值支援,未做任何配置時,配置返回預設值。比如,預設環境為線上,如果沒有更改配置,就不會讀取到日常和預發環境。
-
簡化配置的讀寫介面,讓上層業務方儘可能少感知實現細節。比如,我們不需要讓上層感知到本地配置的持久化資訊寫入的是SharedPreferences還是SQLite,而只需提供一個寫入的API即可。
-
向上層暴露進入本地配置頁的API方式,以滿足上層選擇性進入的空間。比如,上層透過我們暴露出去的API可以選擇實際使用App過程中,是透過點選頁面的某個操作按鈕、還是手機的音量鍵或者是搖一搖來進入配置頁面。
-
對於App中是否擁有本地配置能力的控制,儘可能放到編譯構建級別,保證線上使用者不會進入到配置頁面。比如,如果線上使用者能夠進入到預發環境,那很可能會醞釀著一場安全事故即將發生。
版本管理

日誌監控
-
環境隔離,release包禁止log輸出;
-
本地持久化,對於關鍵重要日誌(如某個位置錯誤引起的Crash)要做本地持久化儲存;
-
日誌上報,在使用者授權允許的情況下,將暫存使用者本地的日誌進行上傳,並分析具體的操作鏈路;


埋點統計

-
客戶端埋點一般分為P點(頁面級別)、E點(事件級別)和C點(自定義點);
-
埋點分為收集和上報兩個步驟,使用者量級較大時要注意對上報埋點進行合併、壓縮等最佳化處理;
-
埋點邏輯是輔邏輯,產品業務是主邏輯,客戶端資源緊張時要做好資源分配的權衡;
熱修復
-
應用出現重大缺陷並嚴重影響到使用者使用時,比如,在某些系統定製化較強的機型(如小米系列)上一旦進入商品詳情頁就出現應用Crash;
-
應用出現明顯阻塞問題並影響到使用者正常互動時,比如,在某些極端場景下,使用者無法關閉頁面對話方塊;
-
應用出現資損、客訴、輿論風波等產品形態問題,比如將價格單位“元”誤顯示為“分”;
3 應用層
抽象和封裝
publicclassGoodsListActivityextendsActivity{
privatefinal List<GoodsModel> dataList = new ArrayList<>();
private Adapter adapter;
protectedvoidonCreate(@Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_list);
RecyclerView recyclerView = findViewById(R.id.goods_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new Adapter();
recyclerView.setAdapter(adapter);
// 載入資料
dataList.addAll(...);
adapter.notifyDataSetChanged();
}
privateclassAdapterextendsRecyclerView.Adapter<ViewHolder> {
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position){
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.item_goods, parent, false);
returnnew ViewHolder(view);
}
publicvoidonBindViewHolder(@NonNull ViewHolder holder, int position){
GoodsModel model = dataList.get(position);
holder.title.setText(model.title);
holder.price.setText(String.format("%.2f", model.price / 100f));
}
publicintgetItemCount(){
return dataList.size();
}
}
privatestaticclassViewHolderextendsRecyclerView.ViewHolder{
privatefinal TextView title;
privatefinal TextView price;
publicViewHolder(View itemView){
super(itemView);
title = itemView.findViewById(R.id.item_title);
price = itemView.findViewById(R.id.item_price);
}
}
}
-
為什麼每個列表都要寫一個ViewHolder?
-
為什麼每個列表都要寫一個Adapter?
-
為什麼Adapter中對itemView的建立和資料繫結要在onCreateViewHolder和onBindViewHolder兩個方法中分開進行?
-
為什麼Adapter每次設定資料之後,都要呼叫對應的notifyXXX方法?
-
為什麼Android上實現一個簡單的列表需要大幾十行程式碼量?這其中有多少是必需的,又有多少是可以抽象封裝起來的?
publicclassGoodsListActivityextendsActivity{
private RecyclerViewHelper<GoodsModel> recyclerViewHelper;
protectedvoidonCreate(@Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_list);
RecyclerView recyclerView = findViewById(R.id.goods_recycler_view);
recyclerViewHelper = RecyclerViewHelper.of(recyclerView, R.layout.item_goods,
(holder, model, position, itemCount) -> {
TextView title = holder.getView(R.id.item_title);
TextView price = holder.getView(R.id.item_price);
title.setText(model.title);
price.setText(String.format("%.2f", model.price / 100f));
});
// 載入資料
recyclerViewHelper.addData(...);
}
}
-
頁面埋點,基於前面提到的埋點統計,在使用者進入、離開頁面等時機,將使用者頁面的互動情況進行收集上報;
-
公共UI,頁面頂部狀態列和ActionBar、頁面常用的下拉重新整理能力實現、頁面耗時操作時的載入進度條;
-
許可權處理,進入當前頁面所需要的許可權申請,使用者授權後的回撥邏輯和拒絕後的異常處理邏輯;
-
統一攔截,結合前面提到的統一攔截對進入頁面後新增支援動態配置互動的定製能力;
模組化

Intent intent = new Intent(this, GoodsActivity.class);
intent.putExtra("goodsId", model.goodsId);
startActivity(intent);
-
註冊 Activity 標識,在 AndroidManifest.xml 中註冊 Activity 的地方新增 action 標識
<activityandroid:name=".GoodsActivity">
<intent-filter>
<actionandroid:name="https://www.xxx.com/goods/detail" />
<categoryandroid:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
-
替換跳轉邏輯,程式碼中根據上一步註冊的 Activity 標識進行隱式跳轉
Intent intent = new Intent("https://www.xxx.com/goods/detail");
intent.putExtra("goodsId", model.goodsId);
startActivity(intent);
publicclass Router {
/**
* 根據url跳轉到目標頁面
*
* @param context 當前頁面上下文
* @param url 目標頁面url
*/
publicstaticvoid open(Context context, String url) {
// 解析為Uri物件
Uri uri = Uri.parse(url);
// 獲取不帶引數的url
String urlWithoutParam = String.format(
"%s://%s%s", uri.getScheme(), uri.getHost(), uri.getPath());
Intent intent = new Intent(urlWithoutParam);
// 解析url中的引數, 並透過Intent傳遞至下個頁面
for (String paramKey : uri.getQueryParameterNames()) {
String paramValue = uri.getQueryParameter(paramKey);
intent.putExtra(paramKey, paramValue);
}
// 執行跳轉操作
context.startActivity(intent);
}
}
Router.open(this, "https://www.xxx.com/goods/detail?goodsId=" + model.goodsId);
-
抽象統一方法,降低外部編碼成本;
-
統一收口路由邏輯,便於結合前面的「動態配置」和「統一攔截」章節進行線上App路由邏輯的動態更改;
-
標準化 Android 端頁面跳轉引數格式,統一使用 String 型別,解除目標頁面解析引數時的型別判斷歧義;
-
對跳轉頁面所需要的資料做了標準化支援,iOS 端再配合同步改造後,頁面跳轉邏輯將支援完全由業務服務端進行下發;
-
通知式通訊,只需要將事件告知對方,並不關注對方的響應結果。對於該種通訊,一般採用如下方式實現
-
藉助 framework 層提供的透過 Intent + BroadcastReceiver (或 LocalBroadcastManager)傳送事件; -
藉助框架 EventBus 傳送事件; -
基於觀察者模式自實現訊息轉發器來發送事件;
-
呼叫式通訊,將事件告知對方,同時還關注對方的事件響應結果。對於該種通訊,一般採用如下方式實現
-
定義出 biz-service 模組,將業務介面 interface 檔案收口到該模組,再由各介面對應語義的業務模組進行介面的實現,然後再基於某種機制(手動註冊或動態掃描)完成實現類的註冊; -
抽象出 Request => Response 的通訊協議,協議層負責完成 -
先將透過呼叫方傳遞的 Request 路由到被呼叫方的協議實現層; -
再將實現層返回結果轉化為泛化的 Response物件; -
最後將 Response 返回給呼叫方;
4 跨平臺層

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
活躍度中
目前託管Apache
|
活躍度高
Facebook
|
活躍度高
Dcloud
|
活躍度高
Google
|
|
|
|
|
|
四 核心原理總結
1 雙快取

2 執行緒池
-
在開發框架中,網路庫和圖片庫獲取網路資源需用到執行緒池;
-
在專案開發中,讀寫SQLite和本地磁碟檔案等IO操作需要用到執行緒池;
-
在類似於AsyncTask這種提供任務排程的API中,其底層也是依賴執行緒池來完成的;
// 構造執行緒池
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, keepAliveTimeUnit, workQueue, threadFactory, rejectedExecutionHandler);
// 提交子任務
executor.execute(new Runnable() {
publicvoidrun(){
// 這裡做子任務操作
}
});
// 核心執行緒數
int corePoolSize = 5;
// 最大執行緒數
int maximumPoolSize = 10;
// 閒置執行緒保活時長
int keepAliveTime = 1;
// 保活時長單位
TimeUnit keepAliveTimeUnit = TimeUnit.MINUTES;
// 阻塞佇列
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>(50);
// 執行緒工廠
ThreadFactory threadFactory = new ThreadFactory() {
public Thread newThread(Runnable r){
returnnew Thread(r);
}
};
// 任務溢位的處理策略
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

3 反射和註解
-
前面「熱修復」章節,基於ClassLoader 的方案,其內部實現幾乎全是透過反射進行 dex 更改的;
-
前面「網路模組」章節,Retorfit 只需要宣告一個介面再添加註解即可使用,其底層也是利用了反射註解和下面要介紹的動態代理技術;
-
依賴注入框架 dagger 和 androidannotations 利用 Java 的 APT 預編譯技術再結合編譯時註解做注入程式碼生成;
-
如果瞭解 Java 服務端開發,主流的開發框架 SpringBoot 其內部大量運用了注射和註解的技術;
publicclassDataManager {
private UserHelper userHelper = new UserHelper();
private GoodsHelper goodsHelper = new GoodsHelper();
private OrderHelper orderHelper = new OrderHelper();
}
publicclassDataManager{
private UserHelper userHelper;
private GoodsHelper goodsHelper;
private OrderHelper orderHelper;
publicDataManager(){
// 注入物件例項 (內部透過反射+註解實現)
InjectManager.inject(this);
}
}
publicclassManager {
privatevoiddoSomething(String name) {
// ...
}
}
try {
Class<?> managerType = manager.getClass();
Method doSomethingMethod = managerType.getMethod("doSomething", String.class);
doSomethingMethod.setAccessible(true);
doSomethingMethod.invoke(manager, "<name引數>");
} catch (Exception e) {
e.printStackTrace();
}
4 動態代理
背景
publicclassHttpUtil{
/**
* 執行網路請求
*
* @param relativePath url相對路徑
* @param params 請求引數
* @param callback 回撥函式
* @param <T> 響應結果型別
*/
publicstatic <T> voidrequest(String relativePath, Map<String, Object> params, Callback<T> callback){
// 實現略..
}
}
publicinterfaceGoodsApi{
/**
* 分頁查詢商品列表
*
* @param pageNum 頁面索引
* @param pageSize 每頁資料量
* @param callback 回撥函式
*/
voidgetPage(int pageNum, int pageSize, Callback<Page<Goods>> callback);
}
publicclassGoodsApiImplimplementsGoodsApi {
@Override
publicvoidgetPage(int pageNum, int pageSize, Callback<Page<Goods>> callback) {
Map<String, Object> params = new HashMap<>();
params.put("pageNum", pageNum);
params.put("pageSize", pageSize);
HttpUtil.request("goods/page", params, callback);
}
}
GoodsApi goodsApi = new GoodsApiImpl();
goodsApi.getPage(1, 50, new Callback<Page<Goods>>() {
publicvoidonSuccess(Page<Goods> data){
// 成功回撥
}
publicvoidonError(Error error){
// 失敗回撥
}
});
問題
/**
* 查詢商品詳情
*
* @param id 商品ID
* @param callback 回撥函式
*/
voidgetDetail(long id, Callback<Goods> callback);
@Override
publicvoidgetDetail(long id, Callback<Goods> callback) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
HttpUtil.request("goods/detail", params, callback);
}
@Override
publicvoidcreate(Goods goods, Callback<Goods> callback) {
Map<String, Object> params = new HashMap<>();
params.put("goods", goods);
HttpUtil.request("goods/create", params, callback);
}
@Override
publicvoidupdate(Goods goods, Callback<Void> callback) {
Map<String, Object> params = new HashMap<>();
params.put("goods", goods);
HttpUtil.request("goods/update", params, callback);
}
分析
Map<String, Object> params = new HashMap<>();
// 遍歷當前方法引數, 執行以下語句
params.put("<引數名>", <引數值>);
HttpUtil.request("<介面路徑>", params, callback);
-
手動寫一個 Map;
-
往 Map 中塞引數鍵值對;
-
呼叫 HttpUtil#request 執行網路請求;
封裝
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Path {
/**
* @return 介面路徑
*/
Stringvalue();
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
/**
* @return 引數名稱
*/
Stringvalue();
}
@SuppressWarnings("unchecked")
publicstatic <T> T getApi(Class<T> apiType) {
return (T) Proxy.newProxyInstance(apiType.getClassLoader(), new Class[]{apiType}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 解析介面路徑
String path = method.getAnnotation(Path.class).value();
// 解析介面引數
Map<String, Object> params = new HashMap<>();
Parameter[] parameters = method.getParameters();
// 注: 此處多偏移一位, 為了跳過最後一項callback引數
for (int i = 0; i < method.getParameterCount() - 1; i++) {
Parameter parameter = parameters[i];
Param param = parameter.getAnnotation(Param.class);
params.put(param.value(), args[i]);
}
// 取最後一項引數為回撥函式
Callback<?> callback = (Callback<?>) args[args.length - 1];
// 執行網路請求
HttpUtil.request(path, params, callback);
returnnull;
}
});
}
效果
publicinterfaceGoodsApi{
void getPage( int pageNum, int pageSize, Callback<Page<Goods>> callback);
void getDetail( long id, Callback<Goods> callback);
void create( Goods goods, Callback<Goods> callback);
void update(Void> callback); Goods goods, Callback<
}
// 之前
GoodsApi goodsApi = new GoodsApiImpl();
// 現在
GoodsApi goodsApi = ApiProxy.getApi(GoodsApi.class);

五 通用設計方案
通訊設計
直接依賴關係

publicclassMainActivityextendsActivity{
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
// A呼叫B
button.setText("");
button.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
// B回撥A
}
});
}
}
間接依賴關係

publicclassGoodsCardViewextendsFrameLayout{
privatefinal Button button;
private OnFollowListener followListener;
publicGoodsCardView(Context context, AttributeSet attrs){
super(context, attrs);
// 略...
button.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
if (followListener != null) {
// C回撥B
followListener.onFollowClick();
}
}
});
}
publicvoidsetFollowText(String followText){
// C呼叫B
button.setText(followText);
}
publicvoidsetOnFollowClickListener(OnFollowListener followListener){
this.followListener = followListener;
}
}
publicclassMainActivityextendsActivity{
private GoodsCardView goodsCard;
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
// A呼叫C
goodsCard.setFollowText("點選商品即可關注");
goodsCard.setOnFollowClickListener(new OnFollowListener() {
publicvoidonFollowClick(){
// C回撥A
}
});
}
}
組合關係

publicclassMainActivityextendsActivity{
private RecyclerView recyclerView;
private ImageView topIcon;
protectedvoidonCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// 略...
topIcon.setOnClickListener(new View.OnClickListener() {
publicvoidonClick(View v){
// B回撥C
onTopIconClick();
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
publicvoidonScrollStateChanged(RecyclerView recyclerView, int newState){
// A回撥C
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
onFirstItemVisibleChanged(layoutManager.findFirstVisibleItemPosition() == 0);
}
}
});
}
privatevoidonFirstItemVisibleChanged(boolean visible){
// C呼叫B
topIcon.setVisibility(visible ? View.GONE : View.VISIBLE);
}
privatevoidonTopIconClick(){
// C呼叫A
recyclerView.scrollToPosition(0);
// C呼叫B
topIcon.setVisibility(View.GONE);
}
}
深依賴/組合關係

publicclassEventManagerextendsObservable<EventManager.OnEventListener> {
publicinterfaceOnEventListener{
voidonEvent(String action, Object... args);
}
publicvoiddispatch(String action, Object... args){
synchronized (mObservers) {
for (OnEventListener observer : mObservers) {
observer.onEvent(action, args);
}
}
}
}
publicclassAComponent{
publicstaticfinal String ACTION_SOMETHING = "a_do_something";
privatefinal EventManager eventManager;
publicAComponent(EventManager eventManager){
this.eventManager = eventManager;
}
publicvoidsendMessage(){
// A呼叫X
eventManager.dispatch(ACTION_SOMETHING);
}
}
publicclassBComponent{
privatefinal EventManager eventManager;
publicBComponent(EventManager eventManager){
this.eventManager = eventManager;
eventManager.registerObserver(new EventManager.OnEventListener() {
publicvoidonEvent(String action, Object... args){
if (AComponent.ACTION_SOMETHING.equals(action)) {
// X分發B
}
}
});
}
}
無關係
可拓展回撥函式設計
背景
publicinterfaceCallback {
voidonCall1();
}
publicclassSDKManager {
private Callback callback;
publicvoidsetCallback(Callback callback) {
this.callback = callback;
}
privatevoiddoSomething1() {
// 略...
if (callback != null) {
callback.onCall1();
}
}
}
SDKManager sdkManager = new SDKManager();
sdkManager.setCallback(new Callback() {
publicvoidonCall1(){
}
});
問題
publicinterfaceCallback {
voidonCall1();
voidonCall2();
}
sdkManager.setCallback(new Callback() {
publicvoidonCall1(){
}
});
publicinterfaceCallback2 {
voidonCall2();
}
publicclassSDKManager {
// 略..
private Callback2 callback2;
publicvoidsetCallback2(Callback2 callback2) {
this.callback2 = callback2;
}
privatevoiddoSomething2() {
// 略...
if (callback2 != null) {
callback2.onCall2();
}
}
}
sdkManager.setCallback2(new Callback2() {
publicvoidonCall2(){
}
});
對外最佳化
publicinterfaceCallback {
}
publicinterfaceCallback1extendsCallback{
voidonCall1();
}
publicinterfaceCallback2extendsCallback{
voidonCall2();
}
publicclassSDKManager{
private Callback callback;
publicvoidsetCallback(Callback callback){
this.callback = callback;
}
privatevoiddoSomething1(){
// 略...
if ((callback instanceof Callback1)) {
((Callback1) callback).onCall1();
}
}
privatevoiddoSomething2(){
// 略...
if ((callback instanceof Callback2)) {
((Callback2) callback).onCall2();
}
}
}
publicclassSimpleCallbackimplementsCallback1, Callback2{
publicvoidonCall1(){
}
publicvoidonCall2(){
}
}
// 單介面方式設定回撥
sdkManager.setCallback(new Callback1() {
publicvoidonCall1(){
// ..
}
});
// 組合介面方式設定回撥
interfaceCombineCallbackextendsCallback1, Callback2{
}
sdkManager.setCallback(new CombineCallback() {
publicvoidonCall1(){
// ..
}
publicvoidonCall2(){
// ...
}
});
// 空實現類方式設定回撥
sdkManager.setCallback(new SimpleCallback() {
publicvoidonCall1(){
// ..
}
publicvoidonCall2(){
//..
}
});
publicinterfaceCallback3extendsCallback{
voidonCall3();
}
privatevoiddoSomething3(){
// 略...
if ((callback instanceof Callback3)) {
((Callback3) callback).onCall3();
}
}
對內最佳化
privatevoiddoSomething1(){
// 略...
if ((callback instanceof Callback1)) {
((Callback1) callback).onCall1();
}
}
publicclassCallbackProxyimplementsCallback1, Callback2, Callback3{
private Callback callback;
publicvoidsetCallback(Callback callback){
this.callback = callback;
}
publicvoidonCall1(){
if (callback instanceof Callback1) {
((Callback1) callback).onCall1();
}
}
publicvoidonCall2(){
if (callback instanceof Callback2) {
((Callback2) callback).onCall2();
}
}
publicvoidonCall3(){
if (callback instanceof Callback3) {
((Callback3) callback).onCall3();
}
}
}
publicclassSDKManager{
privatefinal CallbackProxy callbackProxy = new CallbackProxy();
publicvoidsetCallback(Callback callback){
callbackProxy.setCallback(callback);
}
privatevoiddoSomething1(){
// 略...
callbackProxy.onCall1();
}
privatevoiddoSomething2(){
// 略...
callbackProxy.onCall2();
}
privatevoiddoSomething3(){
// 略...
callbackProxy.onCall3();
}
}
《Android 中子執行緒真的不能更新 UI 嗎?》:https://juejin.cn/post/6844904131136618510
《移動跨平臺開發框架解析與選型》:https://segmentfault.com/a/1190000039122907