代碼下載下傳位址:GitHub
在原來項目的基礎上,針對文章中天氣資訊為假資料,做了一些對接口和類的修改
一、功能需求
- 可以羅列出全國所有的省市縣
- 可以檢視全國任意城市的天氣資訊
- 可以自由切換城市。檢視其它城市的天氣
- 提供手動更新及背景自動更新的功能
二、擷取資料
擷取全國省市縣的資料資訊:
使用作者架設的供學習使用的伺服器(傳回JSON資料格式):
http://guolin.tech/api/china
擷取詳細的天氣資訊:
使用和風天氣免費接口:https://free-api.heweather.net/s6
和風天氣首頁
- .注意,使用和風天氣的接口需要自行申請key,注冊一個自己的賬号,并建立自己的應用(控制台->應用管理->建立應用),然後點選添加key,類型選擇Web API 有了API key,我們就能擷取到任意城市的天氣資訊了。
三、建立資料庫和表
為了讓項目有更好的結構,我們在包下建立幾個包:
- db:儲存資料庫模型相關代碼
- gson:存放GSON模型相關代碼
- service:存放服務相關代碼
- util:存放工具相關代碼
接下來,我們就可以開始建立資料庫了。
1、添加庫依賴:
dependencies {
...
implementation 'org.litepal.android:java:3.0.0'//對資料庫進行的操作
implementation "com.squareup.okhttp3:okhttp:4.4.0"//進行網絡請求
implementation 'com.google.code.gson:gson:2.8.6'//解析JSON資料
implementation 'com.github.bumptech.glide:glide:4.11.0'//加載和展示圖檔
}
2、建立三張表:province、city、county,分别存放省、市、縣的資料資訊,對應到實體類,就應該建立Province、City、County這3個類。
3、配置litepal.xml檔案
目錄:app/src/main/assets/litepal.xml
這一步将3個實體類添加到映射表中
4、配置LitePalApplication:
修改AndroidManifest.xml中的代碼:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.coolweather.zsyweather">
<application
android:name="org.litepal.LitePalApplication"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
</application>
</manifest>
四、周遊全國省市縣資料
由于資料都是從伺服器端擷取到的,故我們需要與伺服器進行互動。通常情況下,我們将這些通用的網絡操作提取到一個公共的類裡,并提供一個靜态方法,當想要發起網絡請求時,隻需簡單地調用一下這個方法即可。
我們在util包下增加一個HttpUtil類
public class HttpUtil {
public static void sendOkHttpRequest(String address, okhttp3.Callback callback){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
}
現在,我們發起一條HTTP請求隻需要調用sendOkHttpRequest()方法,傳入請求位址,然後注冊一個回調來處理伺服器響應就可以了。
是以,我們在util包下建立一個Utility類,用來解析和處理傳回的JSON資料。對于省級、市級、縣級的資料,處理的方式都是類似的:先使用JSONArray和JSONObject将資料解析出來,然後組裝成實體類對象,再調用save()方法将資料存儲到資料庫當中。
代碼示例如下:
/**
* 解析和處理伺服器傳回的省級資料
*/
public static boolean handleProvinceResponse(String response){
if(!TextUtils.isEmpty(response)){
try{
JSONArray allProvince = new JSONArray(response);
for (int i = 0; i < allProvince.length(); i++){
JSONObject provinceObject = allProvince.getJSONObject(i);
Province province = new Province();
province.setProvinceName(provinceObject.getString("name"));
province.setProvinceCode(provinceObject.getInt("id"));
province.save();
}
return true;
}catch (JSONException e){
e.getStackTrace();
}
}
return false;
}
接下來的步驟就是周遊省市縣了,我們通過碎片的方式編寫,因為之後還會複用。寫完之後不要忘了将碎片添加到活動裡。
- 由于我們自定義了标題欄,是以不需要原生的ActionBar了,修改res/values/styles.xml中的代碼,将主題改為Theme.AppCompat.Light.NoActionBar。
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
最後,我們在AndroidManifest.xml中加入聲明權限的代碼:
如果沒什麼意外的話,程式運作以後,我們就能得到全國省市縣的清單了
五、顯示天氣資訊
5.1 資料解析
檢視和風天氣的官方文檔,我們可以總結出傳回資料的大緻格式:
{
"HeWeather6":[
{
"status":"ok",
"basic":{},
"update":{},
"now":{},
"daily_forecast":[]
"lifestyle":[]
}
]
}
可以看到,和風天氣傳回的資料十分複雜,如果使用JSONObject來解析就會很麻煩,這裡我們使用GSON對天氣資訊進行解析。
其中,basic、update、now、daily_forecast、lifestyle的内部又會有具體的内容,我們可以将這5個部分定義成5個實體類。
例如basic,我們看看裡面的具體内容:
"basic":{
"cid": "CN101010100",
"location": "北京"
}
按照此結構,可以在歌頌包下定義一個basic類:
public class Basic {
@SerializedName("cid")
public String weatherId;
@SerializedName("location")
public String cityName;
}
由于JSON中的一些字段不太适合直接作為Java字段來命名,是以使用@SerializedName注解的方式讓二者之間建立映射關系。
其餘幾個類也按照同樣的方式定義就可以了。
最後建立一個總的執行個體類來引用剛剛建立的各個實體類:
public class Weather {
public String status;
public Basic basic;
public Now now;
public Update update;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
@SerializedName("lifestyle")
public List<Lifestyle> lifestyleList;
}
由于daily_forecast和lifestyle中包含的是一個數組,是以這裡使用了集合的方式來引用,另外增加了status字段。
5.2 布局部分
由于代碼冗長,我們将整體布局分成幾個部分去寫,最後将它們引入到activity_weather.xml中。
5.3 将天氣顯示到界面上
在Utility類中添加一個用于解析天氣JSON資料的方法:
/**
* 将傳回的JSON資料解析成Weather實體類
*/
public static Weather handleWeatherResponse(String response){
try{
Log.d("Utility", response);
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather6");
String weatherContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(weatherContent, Weather.class);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
先是通過JSONObject和JSONArray将天氣資料中的主體結構内容解析出來,然後通過調用fromJson()方法就能将JSON資料轉換成Weather對象了(之前已經按照資料格式定義過相應的GSON實體類)。
關鍵代碼示例如下:
/**
* 根據天氣id請求城市天氣資訊
*/
public void requestWeather(final String weatherId){
Log.d("WeatherActivity", weatherId);
String weatherUrl = "https://api.heweather.net/s6/weather?location=" +
weatherId + "&key=138948890c4349baab0893d52848bf7b";//參照文檔接口示例拼裝接口位址
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WeatherActivity.this, "擷取天氣資訊失敗", Toast.LENGTH_SHORT).show();
swipeRefresh.setRefreshing(false);
}
});
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String responseText = response.body().string();
final Weather weather = Utility.handleWeatherResponse(responseText);//将傳回的JSON資料轉換成Weather對象
runOnUiThread(new Runnable() {//将目前線程切換回主線程
@Override
public void run() {
if(weather != null && "ok".equals(weather.status)){//如果請求成功,則将資料緩存到SharePreference中,并顯示資料
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(WeatherActivity.this).edit();
editor.apply();
showWeatherInfo(weather);
}else{
Toast.makeText(WeatherActivity.this, "擷取天氣資訊失敗", Toast.LENGTH_SHORT).show();
}
swipeRefresh.setRefreshing(false);
}
});
}
});
loadBingPic();
}
5.4 完成從清單跳轉到天氣頁面的邏輯
略
5.5 天氣情況緩存
為了避免每次打開程式都要重新選擇城市,我們将天氣資料緩存,并在MainActivity中進行判斷,如果緩存中有資料,則直接跳轉到天氣界面顯示。
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if(prefs.getString("weather", null) != null){
Intent intent = new Intent(this, WeatherActivity.class);
startActivity(intent);
finish();
}
- 剩下的代碼沒什麼難點,跟着書上敲就完全ok了(有空再寫)