天天看點

【第一行代碼】Weather

簡介

完整的模拟了一個天氣軟體

功能清單

  • 羅列出全國所有的省、市、縣
  • 檢視全國任意城市的天氣資訊
  • 自由的切換城市,檢視其他城市的天氣
  • 能手動更新以及背景自動更新天氣

擷取全國的省市縣資訊

利用 作者的伺服器 去擷取資料資訊

通過資料資訊中的 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

繼續閱讀