個人博客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的公眾號