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

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

封面.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();
    }
  }
}

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

输出

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

项目地址GitHub

CoorChice的公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,558评论 18 399
  • CameraManager 收集摄像头硬件信息 Parameters 包含的信息有:预览相关的:getPrevie...
    猫侠阅读 1,086评论 0 1
  • 星星满天空,密密小路中,每当想起这句话,女孩贝娜就仰望星空,看着那么多的星星在天上放光芒,还有月亮姐姐在陪伴着。贝...
    南扼清寒阅读 631评论 0 0