天天看點

Android《第一行代碼》酷歐天氣

代碼下載下傳位址:GitHub

在原來項目的基礎上,針對文章中天氣資訊為假資料,做了一些對接口和類的修改

一、功能需求

  1. 可以羅列出全國所有的省市縣
  2. 可以檢視全國任意城市的天氣資訊
  3. 可以自由切換城市。檢視其它城市的天氣
  4. 提供手動更新及背景自動更新的功能

二、擷取資料

擷取全國省市縣的資料資訊:

使用作者架設的供學習使用的伺服器(傳回JSON資料格式):
http://guolin.tech/api/china
           

擷取詳細的天氣資訊:

使用和風天氣免費接口:https://free-api.heweather.net/s6
           

和風天氣首頁

  • .注意,使用和風天氣的接口需要自行申請key,注冊一個自己的賬号,并建立自己的應用(控制台->應用管理->建立應用),然後點選添加key,類型選擇Web API
    Android《第一行代碼》酷歐天氣
    有了API key,我們就能擷取到任意城市的天氣資訊了。

三、建立資料庫和表

為了讓項目有更好的結構,我們在包下建立幾個包:

Android《第一行代碼》酷歐天氣
  • 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個類。

Android《第一行代碼》酷歐天氣

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中加入聲明權限的代碼:

如果沒什麼意外的話,程式運作以後,我們就能得到全國省市縣的清單了

Android《第一行代碼》酷歐天氣

五、顯示天氣資訊

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了(有空再寫)