[干貨] Glow Android 優(yōu)化實(shí)踐 | wingjay

版權(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 里面,我們可以通過判斷 throwableIOException 還是 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 MonitorHierarchy 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 相對比較簡單酸些,不過,我們還需要在 CommunityBase-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)該可以按如下方式在 CommunityActivityinject 一個(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)該把屬于 Communityinject 放到App 里,這樣的代碼太ugly贸伐,而且更重要的是勘天,隨著 Community 越來越多 Activity 需要 inject ,每個(gè) inject 都要在各個(gè) App 里重復(fù)加捉邢,這太煩了脯丝,也太笨了。

因此伏伐,我們采用了一個(gè)簡潔有效的方法來改進(jìn)宠进。

Community 里創(chuàng)建一個(gè) CommunityComponent,所有屬于 Communityinject 直接寫在 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);
  }
}
dagger

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

https://github.com/wingjay

wingjay

版權(quán)聲明:轉(zhuǎn)載必須得到本人授權(quán)欲诺。謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渺鹦,一起剝皮案震驚了整個(gè)濱河市扰法,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毅厚,老刑警劉巖塞颁,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吸耿,居然都是意外死亡祠锣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門咽安,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伴网,“玉大人,你說我怎么就攤上這事妆棒≡杼冢” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵糕珊,是天一觀的道長动分。 經(jīng)常有香客問我,道長红选,這世上最難降的妖魔是什么澜公? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喇肋,結(jié)果婚禮上坟乾,老公的妹妹穿的比我還像新娘迹辐。我一直安慰自己,他們只是感情好甚侣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布右核。 她就那樣靜靜地躺著,像睡著了一般渺绒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菱鸥,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天宗兼,我揣著相機(jī)與錄音,去河邊找鬼氮采。 笑死殷绍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹊漠。 我是一名探鬼主播主到,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躯概!你這毒婦竟也來了登钥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤娶靡,失蹤者是張志新(化名)和其女友劉穎牧牢,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姿锭,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塔鳍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呻此。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轮纫。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖焚鲜,靈堂內(nèi)的尸體忽然破棺而出掌唾,到底是詐尸還是另有隱情,我是刑警寧澤恃泪,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布郑兴,位于F島的核電站,受9級(jí)特大地震影響贝乎,放射性物質(zhì)發(fā)生泄漏情连。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一览效、第九天 我趴在偏房一處隱蔽的房頂上張望却舀。 院中可真熱鬧虫几,春花似錦、人聲如沸挽拔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽螃诅。三九已至啡氢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間术裸,已是汗流浹背倘是。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袭艺,地道東北人搀崭。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像猾编,于是被迫代替她去往敵國和親瘤睹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評論 25 707
  • 用兩張圖告訴你答倡,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料轰传? 從這篇文章中你...
    hw1212閱讀 12,730評論 2 59
  • afinalAfinal是一個(gè)android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,434評論 2 45
  • 有沒有這樣一種感情瘪撇,經(jīng)歷人情世故的染指绸吸,依然原滋原味。 ――題記 破曉前的清冽與隱落后的...
    蒙蠻閱讀 297評論 0 0
  • 人格修習(xí)的目標(biāo)是什么?每個(gè)人都有不同的答案设江。 今天羅胖在60秒語音分享中說锦茁,人格修習(xí)的目標(biāo)是成為一...
    潘多拉簡書閱讀 786評論 0 1