簡介
完整的模拟了一個天氣軟體
功能清單
- 羅列出全國所有的省、市、縣
- 檢視全國任意城市的天氣資訊
- 自由的切換城市,檢視其他城市的天氣
- 能手動更新以及背景自動更新天氣
擷取全國的省市縣資訊
利用 作者的伺服器 去擷取資料資訊
通過資料資訊中的 weather_id 去通路和風天氣的接口,即可得到該地區的天氣
注冊 和風天氣擷取 API Key。
最後利用 weather_id 和 Key 便可拿到天氣相關的資料。
建立資料庫和表
建立 db 目錄存放資料庫的表,利用 LitePal 管理資料庫;
在 build.gradle 添加依賴
implementation 'org.litepal.android:core:1.3.2'
建立類 Province,City 和 County 繼承 DataSupport,分别儲存省市縣的資料資訊。
建立 assets 目錄,建立檔案 litepal.xml,内容如下:
<litepal>
<dbname value="weather"/>
<version value="1"/>
<list>
<mapping class="com.example.weather.db.Province"/>
<mapping class="com.example.weather.db.City"/>
<mapping class="com.example.weather.db.County"/>
</list>
</litepal>
配置 LitePalApplication,在 MyApplication 中初始化
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
LitePalApplication.initialize(mContext);
}
周遊全國省市縣資料
擷取資料
資料最開始去需要從伺服器擷取的,是以需要和伺服器互動,利用 OkHttp 開源庫
在 build.gradle 添加依賴
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
用法:
public class HttpUtil {
private static final String TAG = "ZLTEST";
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(address)
.build();
LogUtil.d(TAG, "request success");
client.newCall(request).enqueue(callback);
}
}
解析資料
由于伺服器傳回的資料是 json 格式的,是以需要利用 gson 去解析處理
在 build.gradle 添加依賴
implementation 'com.google.code.gson:gson:2.8.2'
用法:
public class Utility {
public static Weather handleWeatherResponse(String response) {
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray jsonArray = jsonObject.getJSONArray("HeWeather");
String weatherContent = jsonArray.getJSONObject(0).toString();
return new Gson().fromJson(weatherContent, Weather.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
展示資料
利用 Fragment 去顯示界面,用 fragment 是因為這個界面後面需要複用
建立布局檔案,在 res/layout 建立檔案 choose_area.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<Button
android:id="@+id/back_button"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/back"/>
</RelativeLayout>
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
建立 ChooseAreaFragment 類去處理相關邏輯,可以參考 源碼檔案
顯示天氣資訊
定義 GSON 實體類
天氣傳回的資料格式:
{
"HeWeather": [
{
"status" : "ok",
"basic" : {},
"aqi" : {},
"now" : {},
"suggestion" : {},
"daily_forecast" : []
}
]
}
其中 basic,aqi,now,suggestion 和 daily_forecast 的内部又有具體的内容,是以可以将這個五個部分定義成五個類
建立 Weather 類, 根據每個部分不同的屬性分别建類,這裡不再贅述,可以參考源碼
public class Weather {
public String status;
public AQI aqi;
public Basic basic;
public Now now;
public Suggestion suggestion;
@SerializedName("daily_forecast")
public List<Forecast> forecastList;
}
編寫天氣界面
建立一個 title.xml 作為頭布局,用來顯示城市名以及時間
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<Button
android:id="@+id/nav_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/home"/>
<TextView
android:id="@+id/title_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#fff"
android:textSize="20sp"/>
<TextView
android:id="@+id/title_update_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textColor="#fff"
android:textSize="16sp"/>
</RelativeLayout>
建立天氣主界面布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/bing_pic_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/weather_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:overScrollMode="never">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<include layout="@layout/title"/>
<include layout="@layout/now"/>
<include layout="@layout/forecast"/>
<include layout="@layout/aqi"/>
<include layout="@layout/suggestion"/>
</LinearLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<fragment
android:id="@+id/choose_area_fragment"
android:name="com.example.weather.ChooseAreaFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start">
</fragment>
</androidx.drawerlayout.widget.DrawerLayout>
</FrameLayout>
建立 WeatherActivity 類去處理相關邏輯,可以參考 源碼檔案
背景自動更新天氣
利用 service 和 AlarmManager 實作定時更新
public class AutoUpdateService extends Service {
private static final String TAG = "ZLTEST";
public AutoUpdateService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
updateWeather();
updateBingPic();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 8*60*1000;
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent intent1 = new Intent(this, AutoUpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(this,
0, intent1, 0);
manager.cancel(pendingIntent);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
return super.onStartCommand(intent, flags, startId);
}
private void updateBingPic() {
LogUtil.d(TAG,"updateBingPic");
String requestBingPic = "http://guolin.tech/api/bing_pic";
HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String bingPic = response.body().string();
SharedPreferences.Editor editor = PreferenceManager.
getDefaultSharedPreferences(AutoUpdateService.this).edit();
editor.putString("bing_pic", bingPic);
editor.apply();
}
});
}
private void updateWeather() {
LogUtil.d(TAG,"updateWeather");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String weatherString = preferences.getString("weather", null);
if (weatherString != null) {
Weather weather = Utility.handleWeatherResponse(weatherString);
String weatherId = weather.basic.weatherId;
String weatherUrl = "http://guolin.tech/api/weather?cityid=" +
weatherId + "&key=a6ddec4e22e145eabb7e2ccafb24e0bd";
HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseText = response.body().string();
Weather weather = Utility.handleWeatherResponse(responseText);
if (weather != null && "ok".equals(weather.status)) {
SharedPreferences.Editor editor = PreferenceManager.
getDefaultSharedPreferences(AutoUpdateService.this)
.edit();
editor.putString("weather", responseText);
editor.apply();
}
}
});
}
}
}
修改圖示和名稱
修改檔案 AndroidManifest.xml
android:icon 對應圖示,android:label 對應名稱
<application
android:name="com.example.weather.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/we"
android:label="@string/app_name"
android:roundIcon="@mipmap/we"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
</application>
項目源碼下載下傳
Github