版權(quán)聲明:本文原創(chuàng)發(fā)布于公眾號(hào) wingjay梅割,轉(zhuǎn)載請務(wù)必注明出處! http://www.reibang.com/p/a8b5278cdbcd
了解 Glow 的朋友應(yīng)該知道耀找,我們主營四款 App,分別是 Eve博脑、Glow驻谆、Nuture和Baby炭玫。作為創(chuàng)業(yè)公司奈嘿,我們的四款 App 都處于高速開發(fā)中,平均每個(gè) Android App 由兩人負(fù)責(zé)開發(fā)吞加,同時(shí)負(fù)責(zé) Android 和 Server 開發(fā)裙犹,在滿足 PM 各種需求的同時(shí),我們的 session crash free 率保持不低于 99.8%衔憨,其中兩款 App 接近 100%叶圃。
本文將對 Glow 當(dāng)前 Android App 中對現(xiàn)有工具的探索及優(yōu)化進(jìn)行講解,希望對讀者有所啟發(fā)践图。
整體結(jié)構(gòu)概覽
下面是 Glow Android 端的大體結(jié)構(gòu):
我們有四個(gè) Android App掺冠,它們共用同一個(gè) Community 社區(qū),最底層是 Base-Library灵份,存放公用的模塊組件泽艘,如支付模塊,Logging模塊等等楷力。
下面眉厨,我將依次從以下幾個(gè)方面進(jìn)行講解:
- 網(wǎng)絡(luò)層優(yōu)化
- 內(nèi)存優(yōu)化實(shí)踐
- 在 App 和 Library 中集成依賴注入
- etc.
網(wǎng)絡(luò)層優(yōu)化
1. Retrofit2 + OkHttp3 + RxJava
上面這套結(jié)構(gòu)是目前最為流行的網(wǎng)絡(luò)層架構(gòu)锌奴,可以幫我們寫出簡潔而穩(wěn)定的網(wǎng)絡(luò)請求代碼,比起以前復(fù)雜的異步回調(diào)憾股、主次線程切換等代碼更為易用鹿蜀,而且能支持 https
請求。
基本用法如下:
UserApi userApi = retrofit.create(UserApi.class);
@Get("/{id}")
Observable<User> getUser(@Path("id") long id);
userApi.getUser(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<User>() {
@Override
public void call(User user) {
// handle user
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
// handle throwable
}
});
這只是通用做法服球。下面我們要根據(jù)實(shí)際情況進(jìn)行優(yōu)化茴恰。
2. 封裝線程切換代碼
上面的代碼中可以看到,為了執(zhí)行網(wǎng)絡(luò)請求有咨,我們會(huì)利用RxJava
提供的Schedulers
工具來方便切換線程琐簇。
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
上面的代碼的作用是:讓網(wǎng)絡(luò)請求進(jìn)入 io線程
執(zhí)行,并將返回結(jié)果轉(zhuǎn)入 UI線程
去進(jìn)行渲染座享。
不過婉商,我們 app 有非常多的網(wǎng)絡(luò)請求,而且除了網(wǎng)絡(luò)請求
渣叛,其他的數(shù)據(jù)庫操作
或者 文件讀寫操作
都需要一樣的線程切換丈秩。因此,為了代碼復(fù)用淳衙,我們利用 RxJava
提供的 Transformer
來進(jìn)行封裝蘑秽。
// RxUtil.java
public static <T> Observable.Transformer<T, T> normalSchedulers() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
return source.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
然后,我們可以把網(wǎng)絡(luò)請求代碼轉(zhuǎn)化為
userApi.getUser(1)
.compose(RxUtil.normalSchedulers())
.subscribe(...)
這雖然只是很簡單的改進(jìn)箫攀,但能讓我們的代碼更簡潔肠牲,更不易出錯(cuò)。
3. 封裝響應(yīng)結(jié)果 JsonDataResponse
我們 server 的所有返回結(jié)果都符合如下格式:
{
'rc': 0,
'data': {...},
'msg': "Successful Call"
}
其中 rc
是自定義的結(jié)果標(biāo)志靴跛,server 用來告訴我們該請求的邏輯處理是否成功(此時(shí) rc = 0
)缀雳。data
是這個(gè)請求需要的 json 數(shù)據(jù)。msg
一般用來存放錯(cuò)誤提示信息梢睛。
于是我們創(chuàng)建了一個(gè)通用類來封裝所有的 Response
肥印。
public class JsonDataResponse<T> {
@SerializedName("rc")
private int rc;
@SerializedName("msg")
private String msg;
@SerializedName("data")
T data;
public int getRc() { return rc; }
public T getData() { return data; }
}
于是,我們的請求變成如下:
@Get("/{id}")
Observable<JsonDataResponse<User>> getUser(@Path("id") long id);
userApi.getUser(1)
.compose(RxUtil.normalSchedulers())
.subscribe(new Action1<JsonDataResponse<User>>() {
@Override
public void call(JsonDataResponse<User> response) {
if (response.getRc() == 0) {
User user = response.getData();
// handle user
} else {
Toast.makeToast(context, response.getMsg())
}
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
// handle throwable
}
});
4. 異常處理
上面已經(jīng)能完成正常的網(wǎng)絡(luò)請求了绝葡,但是深碱,卻還沒有對錯(cuò)誤進(jìn)行處理。
一次網(wǎng)絡(luò)請求中藏畅,可能發(fā)生以下幾種錯(cuò)誤:
- 沒有網(wǎng)絡(luò)
- 網(wǎng)絡(luò)正常敷硅,但 http 請求失敗,即 http 狀態(tài)碼不在
[200, 300)
之間,如404
竞膳、500
等 - 網(wǎng)絡(luò)正常航瞭,http 請求成功,但是 server 在處理請求時(shí)出了問題坦辟,使得返回結(jié)果的
rc != 0
不同的錯(cuò)誤刊侯,我們希望給用戶不同的提示,并且統(tǒng)計(jì)這些錯(cuò)誤锉走。
目前我們的網(wǎng)絡(luò)請求里已經(jīng)能夠處理第三種情況滨彻,另外兩種都在 throwable
里面,我們可以通過判斷 throwable
是 IOException
還是 retrofit2.HttpException
來區(qū)分這兩種情況挪蹭。
因此亭饵,我們可得到如下異常處理代碼:
userApi.getUser(1)
.compose(RxUtil.normalSchedulers())
.subscribe(new Action1<JsonDataResponse<User>>() {
@Override
public void call(JsonDataResponse<User> response) {
if (response.getRc() == 0) {
User user = response.getData();
// handle user
handleUser();
} else {
// such as: customized errorMsg: "cannot find this user".
Toast.makeToast(context, response.getMsg(), Toast.LENGTH_SHORT).show();
}
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
String errorMsg = "";
if (throwable instanceof IOException) {
// io Exception
errorMsg = "Please check your network status";
} else if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
// http error.
errorMsg = httpException.response();
} else {
errorMsg = "unknown error";
}
Toast.makeToast(...);
}
});
5. 封裝異常處理代碼
當(dāng)然,我們并不想在每一個(gè)網(wǎng)絡(luò)請求里都寫上面一大段代碼來處理 error
梁厉,那樣太傻了辜羊。比如上面 getUser()
請求,我希望只要寫 handleUser()
這個(gè)方法词顾,至于是網(wǎng)絡(luò)問題還是 server 自己問題我都不想每次去 handle八秃。
接下來我們來封裝上面兩個(gè) Action
。我們可以自定義兩個(gè) Action
:
WebSuccessAction<T extends JsonDataResponse> implements Action1<T>
WebFailureAction implements Action1<Throwable>
其中肉盹,WebSuccessAction
用來處理一切正常(網(wǎng)絡(luò)正常昔驱,請求正常,rc=0
)后的處理上忍,WebFailureAction
用來統(tǒng)一處理上面三種 error
骤肛。
實(shí)現(xiàn)如下:
class WebSuccessAction<T extends JsonDataResponse> implements Action1<T> {
@Override
public void call(T response) {
int rc = response.getRc();
if (rc != 0) {
throw new ResponseCodeError(extendedResponse.getMessage());
}
onSuccess(extendedResponse);
}
public abstract void onSuccess(T extendedResponse);
}
// (rc != 0) Error
class ResponseCodeError extends RuntimeException {
public ResponseCodeError(String detailMessage) {
super(detailMessage);
}
}
在 WebSuccessAction
里,我們把 rc != 0
這種情況轉(zhuǎn)化成 ResponseCodeError
并拋出給 WebFailureAction
去統(tǒng)一處理窍蓝。
class WebFailAction implements Action1<Throwable> {
@Override
public void call(Throwable throwable) {
String errorMsg = "";
if (throwable instanceof IOException) {
errorMsg = "Please check your network status";
} else if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
// such as: "server internal error".
errorMsg = httpException.response();
} else {
errorMsg = "unknown error";
}
Toast.makeToast(...);
}
}
有了上面兩個(gè)自定義 Action
后腋颠,我們就可以把前面 getUser()
請求轉(zhuǎn)化如下:
userApi.getUser(1)
.compose(RxUtil.normalSchedulers())
.subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
@Override
public void onSuccess(JsonDataResponse<User> response) {
handleUser(response.getUser());
}
}, new WebFailAction())
Bingo! 至此我們能夠用非常簡潔的方式來執(zhí)行網(wǎng)絡(luò)操作,而且完全不用擔(dān)心異常處理吓笙。
內(nèi)存優(yōu)化實(shí)踐
在內(nèi)存優(yōu)化方面秕豫,Google 官方文檔里能找到非常多的學(xué)習(xí)資料,例如常見的內(nèi)存泄漏观蓄、bitmap官方最佳實(shí)踐。而且 Android studio 里也集成了很多有效的工具如 Heap Viewer, Memory Monitor 和 Hierarchy Viewer 等等祠墅。
下面侮穿,本文將從其它角度出發(fā),來對內(nèi)存作進(jìn)一步優(yōu)化毁嗦。
1. 當(dāng)Activity關(guān)閉時(shí)亲茅,立即取消掉網(wǎng)絡(luò)請求結(jié)果處理。
這一點(diǎn)很容易被忽略掉。大家最常用的做法是在 Activity
執(zhí)行網(wǎng)絡(luò)操作克锣,當(dāng) Http Response
回來后直接進(jìn)行UI渲染茵肃,卻并不會(huì)去判斷此時(shí) Activity
是否仍然存在,即用戶是否已經(jīng)離開了當(dāng)時(shí)的頁面袭祟。
那么验残,有什么方法能夠讓每個(gè)網(wǎng)絡(luò)請求都自動(dòng)監(jiān)聽 Activity(Fragment) 的 lifecycle 事件并且當(dāng)特定 lifecycle 事件發(fā)生時(shí),自動(dòng)中斷
掉網(wǎng)絡(luò)請求的繼續(xù)執(zhí)行呢巾乳?
首先來看下我們的網(wǎng)絡(luò)請求代碼:
userApi.getUser(1)
.compose(RxUtil.normalSchedulers())
.subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
@Override
public void onSuccess(JsonDataResponse<User> response) {
handleUser(response.getUser());
}
}, new WebFailAction())
我們希望達(dá)到的是您没,當(dāng) Activity
進(jìn)入 onStop
時(shí)立即停掉網(wǎng)絡(luò)請求的后續(xù)處理。
這里我們參考了 RxLifecycle 的實(shí)現(xiàn)方式胆绊,之所以沒有直接使用 RxLifecycle 是因?yàn)楫?dāng)時(shí)集成時(shí)它必須我們的 BaseActivity 繼承其提供的 RxActivity 氨鹏,而 RxActivity 并未繼承我們需要的 AppCompatActivity
(不過現(xiàn)在已經(jīng)提供了)。因此本人只能在學(xué)習(xí)其源碼后压状,自己重新實(shí)現(xiàn)一套仆抵,并做了一些改動(dòng)以更符合我們自己的應(yīng)用場景。
具體實(shí)現(xiàn)如下:
- 首先种冬,我們在 BaseActivity 里镣丑,利用 RxJava 提供的
PublishSubject
把所有 lifecycle event 發(fā)送出來。
class BaseActivity extends AppCompatActivity {
protected final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject = PublishSubject.create();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleSubject.onNext(ActivityLifeCycleEvent.CREATE);
}
@Override
protected void onDestroy() {
lifecycleSubject.onNext(ActivityLifeCycleEvent.DESTROY);
super.onDestroy();
}
@Override
protected void onStop() {
lifecycleSubject.onNext(ActivityLifeCycleEvent.STOP);
super.onStop();
}
}
- 然后碌廓,在
BaseActivity
里传轰,提供bindUntilEvent(LifeCycleEvent)
方法
class BaseActivity extends AppCompatActivity {
@NonNull
@Override
public <T> Observable.Transformer<T, T> bindUntilEvent(@NonNull final ActivityLifeCycleEvent event) {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> sourceObservable) {
Observable<ActivityLifeCycleEvent> o =
lifecycleSubject.takeFirst(activityLifeCycleEvent -> {
return activityLifeCycleEvent.equals(event);
});
return sourceObservable.takeUntil(o);
}
};
}
}
這個(gè)方法可以用于每一個(gè)網(wǎng)絡(luò)請求 Observable 中,當(dāng)它監(jiān)聽到特定的 lifecycle event 時(shí)谷婆,就會(huì)自動(dòng)讓網(wǎng)絡(luò)請求 Observable 終止掉慨蛙,不會(huì)再去監(jiān)聽網(wǎng)絡(luò)請求結(jié)果。
- 具體使用如下:
userApi.getUser(1)
.compose(bindUntilEvent(ActivityLifeCycleEvent.PAUSE))
.compose(RxUtil.normalSchedulers())
.subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
@Override
public void onSuccess(JsonDataResponse<User> response) {
handleUser(response.getUser());
}
}, new WebFailAction())
利用 .compose(bindUntilEvent(ActivityLifeCycleEvent.STOP))
來監(jiān)聽 Activity 的 Stop 事件并終止 userApi.getUser(1)
的 subscription
纪挎,從而防止內(nèi)存泄漏期贫。
2. 圖片優(yōu)化實(shí)踐
Android開發(fā)者都知道,每個(gè)app的可用內(nèi)存時(shí)有限的异袄,一旦內(nèi)存占用太多或者在主線程突然請求較大內(nèi)存通砍,很有可能發(fā)生 OOM 問題。而其中烤蜕,圖片又是占用內(nèi)存的大頭封孙,因此我們必須采取多種方法來進(jìn)行優(yōu)化。
多數(shù)情況下我們是從 server 獲取一張高清圖片下來讽营,然后在內(nèi)存里進(jìn)行裁剪成需要的大小來進(jìn)行顯示虎忌。這里面存在兩個(gè)問題,
1:假設(shè)我們只需要一張小圖橱鹏,而server取回來的圖如果比較大膜蠢,那就會(huì)浪費(fèi)帶寬和內(nèi)存堪藐。
2:如果直接在主線程去為圖片請求大塊空間,很容易由于系統(tǒng)難于快速分配而 OOM挑围;
比較理想的情況是:需要顯示多大的圖片礁竞,就向server請求多大的圖片,既節(jié)省用戶帶寬流量杉辙,更減少內(nèi)存的占用模捂,減小 OOM 的機(jī)率。
為了實(shí)現(xiàn) server 端的圖片Resize奏瞬,我們采用了 Thumbor 來提供圖片 Resize 的功能枫绅。android端只需要提供一個(gè)原圖片 URL 和需要的 size 信息,就可以得到一張 Resize 好的圖片資源文件硼端。具體server端實(shí)現(xiàn)這里就不細(xì)講了并淋,感興趣的讀者可以閱讀官方文檔。
這里介紹下我們在 Android 端的實(shí)現(xiàn)珍昨,以 Picasso 為栗子县耽。
- 首先要引入 Square 提供的 pollexor 工具,它可以讓我們更簡便的創(chuàng)建 thumbor 的規(guī)范 URI镣典,參考如下:
thumbor.buildImage("http://example.com/image.png")
.resize(48, 48)
.toUrl()
- 然后兔毙,利用 Picasso 提供的 requestTransformer 來實(shí)時(shí)獲取當(dāng)前需要顯示的圖片的真實(shí)尺寸,同時(shí)設(shè)置圖片格式為 WebP兄春,這種格式的圖片可以保持圖片質(zhì)量的同時(shí)具有更小的體積:
Picasso picasso = new Picasso.Builder(context).requestTransformer(new Picasso.RequestTransformer() {
@Override
public Request transformRequest(Request request) {
String modifiedUrl = URLEncoder.encode(originUrl);
ThumborUrlBuilder thumborUrlBuilder = thumbor.buildImage(modifiedUrl);
String url = thumborUrlBuilder.resize(request.targetWidth, request.targetHeight)
.filter(ThumborUrlBuilder.format(ThumborUrlBuilder.ImageFormat.WEBP))
.toUrl();
Timber.i("SponsorAd Image Resize url to " + url);
return request.buildUpon().setUri(Uri.parse(url)).build();
}
}).build();
- 利用修改后的 picasso 對象來請求圖片
picasso.load(originUrl).fit().centerCrop().into(imageView);
利用上面這種方法澎剥,我們可以為不同的 ImageView 計(jì)算顯示需要的真實(shí)尺寸,然后去請求一張尺寸匹配的圖片下來赶舆,節(jié)約帶寬哑姚,減小內(nèi)存開銷。
當(dāng)然芜茵,在應(yīng)用這種方法的時(shí)候叙量,不要忘記考慮服務(wù)器的負(fù)載情況,畢竟這種方案意味著每張圖片會(huì)被生成各種尺寸的小圖緩存起來九串,而且Android設(shè)備分辨率不同绞佩,即使是同一個(gè) ImageView,真實(shí)的寬高 Pixel 值也會(huì)不同猪钮,從而生成不同的小圖品山。
在App和Library中集成依賴注入
依賴注入框架 Dagger 我們很早就開始用了,從早期的 Dagger1 到現(xiàn)在的 Dagger2烤低。雖然 Dagger 本身較為陡峭的學(xué)習(xí)曲線使得不少人止步肘交,不過一旦用過,根本停不下來拂玻。
如果只是在 App 里使用 Dagger 相對比較簡單酸些,不過,我們還需要在 Community
和 Base-Android
兩個(gè)公用 Library 里也集成 Dagger檐蚜,這就需要費(fèi)點(diǎn)功夫了魄懂。
下面我來逐步講解下我們是如何將 Dagger 同時(shí)集成進(jìn) App 和 Library 中。
1. 在App里集成Dagger
首先需要在 GlowApplication
里生成一個(gè)全局的 AppComponent
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(MainActivity mainActivity);
}
創(chuàng)建 AppModule
@Module
public class AppModule {
private final LexieApplication lexieApplication;
public AppModule(LexieApplication lexieApplication) {
this.lexieApplication = lexieApplication;
}
@Provides Context applicationContext() {
return lexieApplication;
}
// mock tool object
@Provides Tool provideTool() {
return new Tool();
}
}
集成進(jìn) Application
class GlowApplication extends Application {
private AppComponent appComponent;
@Override
public void onCreate() {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
在 MainActivity
中使用inject
一個(gè) tool
對象
class MainActivity extends Activity {
@Inject Tool tool;
@Override
public void onCreate() {
GlowApplication.getAppComponent().inject(this);
}
}
2. 在 Library 中集成 Dagger
(下面以公用Library:Community為例子)
逆向思維下闯第,先設(shè)想應(yīng)用場景:即 Dagger 已經(jīng)集成好了市栗,那么我們應(yīng)該可以按如下方式在 CommunityActivity
里 inject
一個(gè) tool
對象。
class CommunityActivity extends Activity {
@Inject Tool tool;
@Override
public void onCreate() {
GlowApplication.getAppComponent().inject(this);
}
}
關(guān)鍵在于: GlowApplication.getAppComponent().inject(this);
這一句咳短。
那么問題來了:
對于一個(gè) Library 而言填帽,它是無法拿到 GlowApplication 對象的,因?yàn)樽鳛橐粋€(gè)被別人調(diào)用的 Library咙好,它甚至不知道這個(gè)上層 class 的存在
為了解決這個(gè)問題篡腌,我們在community
里定義一個(gè)公用接口作為中間橋梁
,讓GlowApplication
實(shí)現(xiàn)這個(gè)公共接口即可勾效。
// 在Community定義接口CommunityComponentProvider
public interface CommunityComponentProvider {
AppComponent getAppComponent();
}
// 每個(gè)app的Application類都實(shí)現(xiàn)這個(gè)接口來提供AppComponent
class GlowApplication implements CommunityComponentProvider {
AppComponent getAppComponent() {
return appComponent;
}
}
然后 CommunityActivity
就可以實(shí)現(xiàn)如下:
class CommunityActivity extends Activity {
@Inject Tool tool;
@Override
public void onCreate() {
Context applicationContext = getApplicationContext();
CommunityComponentProvider provider = (CommunityComponentProvider) applicationContext;
provider.getAppComponent().inject(this);
}
}
3. 從 AppComponent 抽離 CommunityComponent
provider.getAppComponent().inject(this);
這一句里我們已經(jīng)實(shí)現(xiàn)前半句 provider.getAppComponent()
了嘹悼,但后半句的實(shí)現(xiàn)呢?
正常情況下层宫,我們要把
void inject(CommunityActivity communityActivity);
放入 AppComponent
中杨伙,如下:
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(MainActivity mainActivity);
// 加在這里
void inject(CommunityActivity communityActivity);
}
其實(shí)這樣我們就已經(jīng)幾乎完成了整個(gè) Library 和 App 的依賴注入了。
但細(xì)心的朋友應(yīng)該發(fā)現(xiàn)里面存在一個(gè)小問題萌腿,那就是
void inject(CommunityActivity communityActivity);
這句代碼如果放入了 App
里的 AppComponent
里限匣,那就意味著我們也需要在另外三個(gè) App
里的 AppComponent
都加上一句相同的代碼?這樣可以嗎毁菱?
理論上當(dāng)然是可行的米死。但是,從單一職責(zé)的角度來考慮鼎俘,AppComponent
只需要負(fù)責(zé) App
層的 inject
就行哲身,我們不應(yīng)該把屬于 Community
的 inject
放到App
里,這樣的代碼太ugly贸伐,而且更重要的是勘天,隨著 Community 越來越多 Activity 需要 inject ,每個(gè) inject 都要在各個(gè) App 里重復(fù)加捉邢,這太煩了脯丝,也太笨了。
因此伏伐,我們采用了一個(gè)簡潔有效的方法來改進(jìn)宠进。
在 Community
里創(chuàng)建一個(gè) CommunityComponent
,所有屬于 Community
的inject
直接寫在 CommunityComponent
里藐翎,不需要 App
再去關(guān)心材蹬。與此同時(shí)实幕,為了保持前面 provider.getAppComponent()
仍然有效,我們讓 AppComponent
繼承 CommunityComponent
堤器。
實(shí)現(xiàn)代碼如下:
class AppComponent extends CommunityComponent {...}
在 Community
里
class CommunityComponent {
void inject(CommunityActivity communityActivity);
}
class CommunityActivity extends Activity {
@Inject Tool tool;
@Override
public void onCreate() {
Context applicationContext = getApplicationContext();
CommunityComponentProvider provider = (CommunityComponentProvider) applicationContext;
provider.getAppComponent().inject(this);
}
}
Bingo! 至此我們已經(jīng)能夠優(yōu)雅簡潔地在 App 和 Library 里同時(shí)應(yīng)用依賴注入了昆庇。
關(guān)于demo
很多讀者提到想要demo,有需要的小伙伴可以先關(guān)注我的Github:https://github.com/wingjay 之后會(huì)抽空把demo上傳到Github上的闸溃。
小結(jié)
由于篇幅有限整吆,本文暫時(shí)先從網(wǎng)絡(luò)層、內(nèi)存優(yōu)化和依賴注入方面進(jìn)行講解辉川,之后會(huì)再考慮從 Logging模塊表蝙、數(shù)據(jù)同步模塊、Deep Linking模塊乓旗、多Library的Gradle發(fā)布管理府蛇、持續(xù)集成和崩潰監(jiān)測模塊等進(jìn)行講解。
謝謝寸齐!
wingjay
版權(quán)聲明:轉(zhuǎn)載必須得到本人授權(quán)欲诺。謝謝。