【比你想的简单很多!从0开始完成一款App】7.在欢迎页加载并缓存数据

个人博客CoorChice,https://chenbingx.github.io/ ,最新文章将会首发CoorChice的博客,欢迎探索哦 !
同时,搜索CoorChice关注我的微信公众号,同期文章也将会优先推送到公众号中,以提醒您有新鲜文章出炉。亦可在文章末尾扫描二维码关注。

img_c3845efbded49b71398d8cc95503e8f7.png
封面.jpg

本系列文章列表

上一篇我们已经把欢迎页面的UI和交互完成了,由于篇幅问题,所以把数据请求放到这篇来继续撸。下面进入正题,来看看如何在欢迎页面出现时,加载数据并缓存,已备进入主页后能够快速展示。

细化需求

“启动数据加载,并将数据缓存共享。”这看起来是一个需求,但它太笼统了,所以我们需要把它拆分细化,然后一个一个点完成。

  • 一进入欢迎页就启动数据加载;
  • 能够定位当前位置并缓存,默认请求定位城市的天气数据;
  • 由于数据具有时效性,所以仅缓存在内存。

功能实现

因为这个项目是使用MVP模式编写的,而上一篇由于关注点仅仅在UI层面,所以SplashActivity 并不具备MVP的结构。现在,我们一起来补全它。

View模块

  • 创建View层接口,并让SplashActivity继承它;
public interface SplashActivityView extends MvpView {
}  

public class SplashActivity extends BaseActivity implements SplashActivityView {
    .
    .
    .
    
}
  • onCreate() 中初始化Presenter(Presenter 的在下面),并在initData() 启动数据加载;
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(); //启动数据加载
  }
  • onDestroy() 时释放Presenter。因为每个Activity几乎都会涉及到Presenter的释放,所以我把这个操作放到了我们的BaseActivity中,并新增getPresenter()抽象方,要求在这个方法中返回Presenter实例。来看看现在的BaseActivity长啥样?
public abstract class BaseActivity extends AppCompatActivity {
  abstract protected void initData();

  abstract protected void initView();

  abstract protected void addListener();

  /**
   * 创建Presenter后必须重写这个方法,将其作为返回值
   */
  abstract protected BasePresenter getPresenter();

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

Presenter模块

上面一直在用Presenter,很疑惑?没关系,现在来看看Presenter。

  • 创建Presenter接口,并实现一个实例。
public interface SplashActivityPresenterApi extends BasePresenter{

  void requestWeatherData(); //这就是我们在Activity中调用的方法。
}

我来说说多出来的这个BasePresenter,这是BaseActivity中抽象方法要求返回的Presenter基类,以后我们的Presenter接口全部都需要继承它,这样便于对Presenter的统一特性进行控制。

public interface BasePresenter {
  void destroy(); //BaseActivity中调用的Presenter销毁方法
}
  • 实现SplashActivityPresenter接口,实现对Model模块和View模块的协调调用。
public class SplashActivityPresenter
    implements
      SplashActivityPresenterApi,
      //实现数据加载的回调
      WeatherDataModelApi.RequestWeatherDataListener {

  //同样依赖抽象
  private WeatherDataModelApi model;
  private SplashActivityView view;

  public SplashActivityPresenter(SplashActivityView view) {
    this.model = new WeatherDataModel(); //实例化一个Model
    this.model.setRequestWeatherDataListener(this); //设置监听,具体的看Model模块
    this.view = view;
  }


  @Override
  public void requestWeatherData() {
    model.requestWeatherData(); //让model去请求数据
  }

  @Override
  public void onRequestWeatherDataSuccess(WeatherData data) {
    //model请求数据成功
    DataCache.getInstance().setWeatherData(data); //缓存数据到DataCache(用于一些通用数据的缓存,见下)中。
  }

  @Override
  public void onRequestWeatherDataFailure(String message) {
    LogUtils.e("请求出错:" + message);
  }

  @Override
  public void destroy() {
    model.setRequestWeatherDataListener(null);  //置null,为了释放内存
    model = null;
    view = null;
  }
}
  • DataCache
//单例实现,缓存一些公用数据
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模块

最后一步啦,我们来编写Model。Model模块在这里需要负责两个事务:

  • 定位
  • 根据定位获取相应位置的天气数据

下面我们来一个个完成。

  • 编写天气的Model接口
public interface WeatherDataModelApi {
  void requestWeatherData();

  void setRequestWeatherDataListener(RequestWeatherDataListener requestWeatherDataListener);

  // 因为每个Model可能需要请求多个接口,所以每个回调可能不同,就把它写到内部了
  interface RequestWeatherDataListener {
    void onRequestWeatherDataSuccess(WeatherData data);

    void onRequestWeatherDataFailure(String message);
  }
}
  • 实现天气Model接口
public class WeatherDataModel implements WeatherDataModelApi {
  private RequestWeatherDataListener requestWeatherDataListener;

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

  //定位使用的是百度定位SDK
  private void locationThenGetWeatherData() {
    LocationHelper.getInstance().startLocation(); //这是封装的一个定位帮助类,在Utils->Helpers包下
    LocationHelper.getInstance().setListener(location -> {
      //成功定位
      String cityname = location.getCity();
      if (cityname != null) {
        getWeatherData(cityname);
      }

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

  private void getWeatherData(String cityname) {
    //请求天气数据
    ApiClient.getWeatherData(cityname, data -> {
      LogUtils.i("requestWeatherData: " + GsonUtils.getSingleInstance().toJson(data));
      if (requestWeatherDataListener != null) {
        requestWeatherDataListener.onRequestWeatherDataSuccess(data); // 请求成功
      }
    }, message -> {
      if (requestWeatherDataListener != null) {
        requestWeatherDataListener.onRequestWeatherDataFailure(message);
      }
    });
  }

  @Override
  public void setRequestWeatherDataListener(RequestWeatherDataListener requestWeatherDataListener) {
    //暴露该方法,让Presnter能够监听到数据加载完成
    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);// 可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
    option.setCoorType("bd09ll");// 可选,默认gcj02,设置返回的定位结果坐标系
    int span = 1000;
    option.setScanSpan(span);// 可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
    option.setIsNeedAddress(true);// 可选,设置是否需要地址信息,默认不需要
    // option.setOpenGps(true);//可选,默认false,设置是否使用gps
    // option.setLocationNotify(true);//可选,默认false,设置是否当GPS有效时按照1S/1次频率输出GPS结果
    option.setIsNeedLocationDescribe(true);// 可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
    // option.setIsNeedLocationPoiList(true);//可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
    option.setIgnoreKillProcess(false);// 可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
    option.SetIgnoreCacheException(false);// 可选,默认false,设置是否收集CRASH信息,默认收集
    option.setEnableSimulateGps(false);// 可选,默认false,设置是否需要过滤GPS仿真结果,默认需要
    mLocationClient.setLocOption(option);
  }

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

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

基本完成了,运行程序看下输出:

img_8a4276d4378281c72494a9438834e421.png
输出

我对网络请求底层也进行了一点调整,具体的请到GitHub查看。

项目地址GitHub

img_08b056b059ac831a2d371fa29bdef1e3.jpe
CoorChice的公众号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值