【比你想的簡(jiǎn)單很多!從0開(kāi)始完成一款A(yù)pp】7.在歡迎頁(yè)加載并緩存數(shù)據(jù)

個(gè)人博客CoorChice,https://chenbingx.github.io/ ,最新文章將會(huì)首發(fā)CoorChice的博客,歡迎探索哦 !
同時(shí),搜索CoorChice關(guān)注我的微信公眾號(hào),同期文章也將會(huì)優(yōu)先推送到公眾號(hào)中,以提醒您有新鮮文章出爐。亦可在文章末尾掃描二維碼關(guān)注。

封面.jpg

本系列文章列表

上一篇我們已經(jīng)把歡迎頁(yè)面的UI和交互完成了,由于篇幅問(wèn)題,所以把數(shù)據(jù)請(qǐng)求放到這篇來(lái)繼續(xù)擼。下面進(jìn)入正題,來(lái)看看如何在歡迎頁(yè)面出現(xiàn)時(shí),加載數(shù)據(jù)并緩存,已備進(jìn)入主頁(yè)后能夠快速展示。

細(xì)化需求

“啟動(dòng)數(shù)據(jù)加載,并將數(shù)據(jù)緩存共享。”這看起來(lái)是一個(gè)需求,但它太籠統(tǒng)了,所以我們需要把它拆分細(xì)化,然后一個(gè)一個(gè)點(diǎn)完成。

  • 一進(jìn)入歡迎頁(yè)就啟動(dòng)數(shù)據(jù)加載;
  • 能夠定位當(dāng)前位置并緩存,默認(rèn)請(qǐng)求定位城市的天氣數(shù)據(jù);
  • 由于數(shù)據(jù)具有時(shí)效性,所以僅緩存在內(nèi)存。

功能實(shí)現(xiàn)

因?yàn)檫@個(gè)項(xiàng)目是使用MVP模式編寫的,而上一篇由于關(guān)注點(diǎn)僅僅在UI層面,所以SplashActivity 并不具備MVP的結(jié)構(gòu)?,F(xiàn)在,我們一起來(lái)補(bǔ)全它。

View模塊

  • 創(chuàng)建View層接口,并讓SplashActivity繼承它;
public interface SplashActivityView extends MvpView {
}  

public class SplashActivity extends BaseActivity implements SplashActivityView {
    .
    .
    .
    
}
  • onCreate() 中初始化Presenter(Presenter 的在下面),并在initData() 啟動(dòng)數(shù)據(jù)加載;
private SplashActivityPresenterApi presenter; //注意這里需要依賴抽象,以提高程序的靈活性

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);
    presenter = new SplashActivityPresenter(this); //初始化Presenter
    ButterKnife.bind(this);
    initData();
    initView();
    addListener();
  }  
  
  @Override
  protected void initData() {
    presenter.requestWeatherData(); //啟動(dòng)數(shù)據(jù)加載
  }
  • onDestroy() 時(shí)釋放Presenter。因?yàn)槊總€(gè)Activity幾乎都會(huì)涉及到Presenter的釋放,所以我把這個(gè)操作放到了我們的BaseActivity中,并新增getPresenter()抽象方,要求在這個(gè)方法中返回Presenter實(shí)例。來(lái)看看現(xiàn)在的BaseActivity長(zhǎng)啥樣?
public abstract class BaseActivity extends AppCompatActivity {
  abstract protected void initData();

  abstract protected void initView();

  abstract protected void addListener();

  /**
   * 創(chuàng)建Presenter后必須重寫這個(gè)方法,將其作為返回值
   */
  abstract protected BasePresenter getPresenter();

  @Override
  protected void onDestroy() {
    super.onDestroy();
    if (getPresenter() != null){
      getPresenter().destroy(); //銷毀Presenter,避免Activity對(duì)象因被Presenter持有而不能被銷毀
    }
  }
}

Presenter模塊

上面一直在用Presenter,很疑惑?沒(méi)關(guān)系,現(xiàn)在來(lái)看看Presenter。

  • 創(chuàng)建Presenter接口,并實(shí)現(xiàn)一個(gè)實(shí)例。
public interface SplashActivityPresenterApi extends BasePresenter{

  void requestWeatherData(); //這就是我們?cè)贏ctivity中調(diào)用的方法。
}

我來(lái)說(shuō)說(shuō)多出來(lái)的這個(gè)BasePresenter,這是BaseActivity中抽象方法要求返回的Presenter基類,以后我們的Presenter接口全部都需要繼承它,這樣便于對(duì)Presenter的統(tǒng)一特性進(jìn)行控制。

public interface BasePresenter {
  void destroy(); //BaseActivity中調(diào)用的Presenter銷毀方法
}
  • 實(shí)現(xiàn)SplashActivityPresenter接口,實(shí)現(xiàn)對(duì)Model模塊和View模塊的協(xié)調(diào)調(diào)用。
public class SplashActivityPresenter
    implements
      SplashActivityPresenterApi,
      //實(shí)現(xiàn)數(shù)據(jù)加載的回調(diào)
      WeatherDataModelApi.RequestWeatherDataListener {

  //同樣依賴抽象
  private WeatherDataModelApi model;
  private SplashActivityView view;

  public SplashActivityPresenter(SplashActivityView view) {
    this.model = new WeatherDataModel(); //實(shí)例化一個(gè)Model
    this.model.setRequestWeatherDataListener(this); //設(shè)置監(jiān)聽(tīng),具體的看Model模塊
    this.view = view;
  }


  @Override
  public void requestWeatherData() {
    model.requestWeatherData(); //讓model去請(qǐng)求數(shù)據(jù)
  }

  @Override
  public void onRequestWeatherDataSuccess(WeatherData data) {
    //model請(qǐng)求數(shù)據(jù)成功
    DataCache.getInstance().setWeatherData(data); //緩存數(shù)據(jù)到DataCache(用于一些通用數(shù)據(jù)的緩存,見(jiàn)下)中。
  }

  @Override
  public void onRequestWeatherDataFailure(String message) {
    LogUtils.e("請(qǐng)求出錯(cuò):" + message);
  }

  @Override
  public void destroy() {
    model.setRequestWeatherDataListener(null);  //置null,為了釋放內(nèi)存
    model = null;
    view = null;
  }
}
  • DataCache
//單例實(shí)現(xiàn),緩存一些公用數(shù)據(jù)
public class DataCache {

  private WeatherData weatherData;
  private BDLocation currentLocation;

  private DataCache(){

  }

  public static DataCache getInstance(){
    return DataCacheHolder.instance;
  }

  private static class DataCacheHolder{

    private static final DataCache instance = new DataCache();
  }
  public WeatherData getWeatherData() {
    return weatherData;
  }

  public void setWeatherData(WeatherData weatherData) {
    this.weatherData = weatherData;
  }

  public BDLocation getCurrentLocation() {
    return currentLocation;
  }

  public void setCurrentLocation(BDLocation currentLocation) {
    this.currentLocation = currentLocation;
  }
}

Model模塊

最后一步啦,我們來(lái)編寫Model。Model模塊在這里需要負(fù)責(zé)兩個(gè)事務(wù):

  • 定位
  • 根據(jù)定位獲取相應(yīng)位置的天氣數(shù)據(jù)

下面我們來(lái)一個(gè)個(gè)完成。

  • 編寫天氣的Model接口
public interface WeatherDataModelApi {
  void requestWeatherData();

  void setRequestWeatherDataListener(RequestWeatherDataListener requestWeatherDataListener);

  // 因?yàn)槊總€(gè)Model可能需要請(qǐng)求多個(gè)接口,所以每個(gè)回調(diào)可能不同,就把它寫到內(nèi)部了
  interface RequestWeatherDataListener {
    void onRequestWeatherDataSuccess(WeatherData data);

    void onRequestWeatherDataFailure(String message);
  }
}
  • 實(shí)現(xiàn)天氣Model接口
public class WeatherDataModel implements WeatherDataModelApi {
  private RequestWeatherDataListener requestWeatherDataListener;

  @Override
  public void requestWeatherData() {
    locationThenGetWeatherData();
  }

  //定位使用的是百度定位SDK
  private void locationThenGetWeatherData() {
    LocationHelper.getInstance().startLocation(); //這是封裝的一個(gè)定位幫助類,在Utils->Helpers包下
    LocationHelper.getInstance().setListener(location -> {
      //成功定位
      String cityname = location.getCity();
      if (cityname != null) {
        getWeatherData(cityname);
      }

      DataCache.getInstance().setCurrentLocation(location); //緩存定位信息
    });
  }

  private void getWeatherData(String cityname) {
    //請(qǐng)求天氣數(shù)據(jù)
    ApiClient.getWeatherData(cityname, data -> {
      LogUtils.i("requestWeatherData: " + GsonUtils.getSingleInstance().toJson(data));
      if (requestWeatherDataListener != null) {
        requestWeatherDataListener.onRequestWeatherDataSuccess(data); // 請(qǐng)求成功
      }
    }, message -> {
      if (requestWeatherDataListener != null) {
        requestWeatherDataListener.onRequestWeatherDataFailure(message);
      }
    });
  }

  @Override
  public void setRequestWeatherDataListener(RequestWeatherDataListener requestWeatherDataListener) {
    //暴露該方法,讓Presnter能夠監(jiān)聽(tīng)到數(shù)據(jù)加載完成
    this.requestWeatherDataListener = requestWeatherDataListener;
  }
}
public class LocationHelper {

  private LocationClient mLocationClient = null;

  private LocationHelper() {
    mLocationClient = new LocationClient(ChiceApplication.getAppContext()); // 聲明LocationClient類
    initLocation();
  }

  public static LocationHelper getInstance() {
    return LocationHelperHolder.instance;
  }

  public void setListener(BDLocationListener listener) {
    if (listener != null){
      mLocationClient.registerLocationListener(listener);
    }
  }

  private static class LocationHelperHolder {
    private static final LocationHelper instance = new LocationHelper();
  }

  private void initLocation() {
    LocationClientOption option = new LocationClientOption();
    option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);// 可選,默認(rèn)高精度,設(shè)置定位模式,高精度,低功耗,僅設(shè)備
    option.setCoorType("bd09ll");// 可選,默認(rèn)gcj02,設(shè)置返回的定位結(jié)果坐標(biāo)系
    int span = 1000;
    option.setScanSpan(span);// 可選,默認(rèn)0,即僅定位一次,設(shè)置發(fā)起定位請(qǐng)求的間隔需要大于等于1000ms才是有效的
    option.setIsNeedAddress(true);// 可選,設(shè)置是否需要地址信息,默認(rèn)不需要
    // option.setOpenGps(true);//可選,默認(rèn)false,設(shè)置是否使用gps
    // option.setLocationNotify(true);//可選,默認(rèn)false,設(shè)置是否當(dāng)GPS有效時(shí)按照1S/1次頻率輸出GPS結(jié)果
    option.setIsNeedLocationDescribe(true);// 可選,默認(rèn)false,設(shè)置是否需要位置語(yǔ)義化結(jié)果,可以在BDLocation.getLocationDescribe里得到,結(jié)果類似于“在北京天安門附近”
    // option.setIsNeedLocationPoiList(true);//可選,默認(rèn)false,設(shè)置是否需要POI結(jié)果,可以在BDLocation.getPoiList里得到
    option.setIgnoreKillProcess(false);// 可選,默認(rèn)true,定位SDK內(nèi)部是一個(gè)SERVICE,并放到了獨(dú)立進(jìn)程,設(shè)置是否在stop的時(shí)候殺死這個(gè)進(jìn)程,默認(rèn)不殺死
    option.SetIgnoreCacheException(false);// 可選,默認(rèn)false,設(shè)置是否收集CRASH信息,默認(rèn)收集
    option.setEnableSimulateGps(false);// 可選,默認(rèn)false,設(shè)置是否需要過(guò)濾GPS仿真結(jié)果,默認(rèn)需要
    mLocationClient.setLocOption(option);
  }

  public void startLocation() {
    if (mLocationClient != null) {
      mLocationClient.start();
    }
  }

  public void stopLocation() {
    if (mLocationClient != null) {
      mLocationClient.stop();
    }
  }
}

基本完成了,運(yùn)行程序看下輸出:

輸出

我對(duì)網(wǎng)絡(luò)請(qǐng)求底層也進(jìn)行了一點(diǎn)調(diào)整,具體的請(qǐng)到GitHub查看。

項(xiàng)目地址GitHub

CoorChice的公眾號(hào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,596評(píng)論 25 708
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,779評(píng)論 18 399
  • CameraManager 收集攝像頭硬件信息 Parameters 包含的信息有:預(yù)覽相關(guān)的:getPrevie...
    貓俠閱讀 1,134評(píng)論 0 1
  • 星星滿天空,密密小路中,每當(dāng)想起這句話,女孩貝娜就仰望星空,看著那么多的星星在天上放光芒,還有月亮姐姐在陪伴著。貝...
    南扼清寒閱讀 676評(píng)論 0 0