简易Android天气软件实战

软件具备的功能:

  1. 可以罗列出全国所有的省、市、县。
  2. 可以查看全国任意城市的天气信息。
  3. 可以自由切换城市、其查看其它城市的天气。
  4. 提供手动更新以及后台自动更新天气的功能。

源码下载:源码链接地址

目录

一、 开发需求分析

  • 1、开发环境
  • 2、城市信息获取的api
  • 3、天气信息获取的api
1.开发环境

  Win10+Android Studio+JDK1.8。

2.城市信息获取的api

  城市信息这里我直接使用的是Android前辈搭建的一个服务器获取的,数据链接是http://guolin.tech/api/china,访问返回的是JSON数据类型的省份信息(JSON数据类型的解析后面会再详细说明),需要返回城市时只需要在本链接后加上“/对应省份id”即可获取到相应的城市信息,县市信息也是一样的,原链接加上“/对应省份id/对应城市id”即可。 

这里其实也可以从其它天气服务商提供的api接口获取城市信息。

当你想得到省份信息时只需访问:http://guolin.tech/api/china/.返回的Json数据如下:

[{"id":1,"name":"北京"},
{"id":2,"name":"上海"},{"id":3,"name":"天津"},{"id":4,"name":"重庆"},{"id":5,"name":"香港"},{"id":6,"name":"澳门"},{"id":7,"name":"台湾"},{"id":8,"name":"黑龙江"},{"id":9,"name":"吉林"},{"id":10,"name":"辽宁"},{"id":11,"name":"内蒙古"},{"id":12,"name":"河北"},{"id":13,"name":"河南"},{"id":14,"name":"山西"},{"id":15,"name":"山东"},{"id":16,"name":"江苏"},{"id":17,"name":"浙江"},{"id":18,"name":"福建"},{"id":19,"name":"江西"},{"id":20,"name":"安徽"},{"id":21,"name":"湖北"},{"id":22,"name":"湖南"},{"id":23,"name":"广东"},{"id":24,"name":"广西"},{"id":25,"name":"海南"},{"id":26,"name":"贵州"},{"id":27,"name":"云南"},{"id":28,"name":"四川"},{"id":29,"name":"西藏"},{"id":30,"name":"陕西"},{"id":31,"name":"宁夏"},{"id":32,"name":"甘肃"},{"id":33,"name":"青海"},{"id":34,"name":"新疆"}]

当你想得到具体城市信息时只需访问(每个城市所属省份有其所属的id,此处以江西id为19):http://guolin.tech/api/china/19.返回的Json数据如下:

[{"id":147,"name":"南昌"},
{"id":148,"name":"九江"},{"id":149,"name":"上饶"},{"id":150,"name":"抚州"},{"id":151,"name":"宜春"},{"id":152,"name":"吉安"},{"id":153,"name":"赣州"},{"id":154,"name":"景德镇"},{"id":155,"name":"萍乡"},{"id":156,"name":"新余"},{"id":157,"name":"鹰潭"}]

当你想得到具体县、区信息时只需访问:http://guolin.tech/api/china/19/148.返回的Json如下:

[{"id":1159,"name":"九江","weather_id":"CN101240201"},
{"id":1160,"name":"瑞昌","weather_id":"CN101240202"},{"id":1161,"name":"庐山","weather_id":"CN101240203"},{"id":1162,"name":"武宁","weather_id":"CN101240204"},{"id":1163,"name":"德安","weather_id":"CN101240205"},{"id":1164,"name":"永修","weather_id":"CN101240206"},{"id":1165,"name":"湖口","weather_id":"CN101240207"},{"id":1166,"name":"彭泽","weather_id":"CN101240208"},{"id":1167,"name":"星子","weather_id":"CN101240209"},{"id":1168,"name":"都昌","weather_id":"CN101240210"},{"id":1169,"name":"修水","weather_id":"CN101240212"}]
3.天气信息获取的api

 此处是用的是和风天气提供的数据接口,注册链接:天气api接口注册链接

有了APT KEY再配合刚才的weather_id,我们就可以随时获取任意城市天气信息了。比如九江市的weather_id是CN101240201,

那么访问:http://guolin.tech/api/weather?cityid=CN101240201&key=ba9079704cc44512bb3af201ef10af15即可查看九江市区的天气信息了。cityid就是我们要填入的weather_id, key部分就是我们申请到的API Key。 如此,服务器就会把九江市区的详细天气信息已Json格式返回给我们:

{"HeWeather": 
[{"basic":{"cid":"CN101240201","location":"九江","parent_city":"九江","admin_area":"江西","cnty":"中国","lat":"29.71203423","lon":"115.99281311","tz":"+8.00","city":"九江","id":"CN101240201","update":{"loc":"2018-04-23 14:47","utc":"2018-04-23 06:47"}},
"update":{"loc":"2018-04-23 14:47","utc":"2018-04-23 06:47"},
"status":"ok",
"now":{"cloud":"100","cond_code":"104","cond_txt":"阴","fl":"18","hum":"88","pcpn":"0.0","pres":"1010","tmp":"18","vis":"12","wind_deg":"201","wind_dir":"西南风","wind_sc":"2","wind_spd":"11","cond":{"code":"104","txt":"阴"}},"daily_forecast":[{"date":"2018-04-23","cond":{"txt_d":"小雨"},"tmp":{"max":"18","min":"15"}},{"date":"2018-04-24","cond":{"txt_d":"多云"},"tmp":{"max":"21","min":"14"}},{"date":"2018-04-25","cond":{"txt_d":"晴"},"tmp":{"max":"23","min":"15"}}],"aqi":{"city":{"aqi":"15","pm25":"10","qlty":"优"}},"suggestion":{"comf":{"brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。","type":"comf"},"sport":{"brf":"较不宜","txt":"有降水,且风力较强,气压较低,推荐您在室内进行低强度运动;若坚持户外运动,须注意避雨防风。","type":"sport"},"cw":{"brf":"不宜","txt":"不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。","type":"cw"}}}]}

数据得到后,在后面我们需要完成对JSON的解析。

二、数据库的创建

此处使用Litepal完成对数据库的操作,因此需要在app/build.gradle文件中声明Litepal依赖库。

compile 'org.litepal.android:core:1.3.2'

 然后创建3张表分别用于存放省、市、县、的数据信息。创建Privince、City、County这3个实体类即可(哈哈,Litepal对数据库的操作简直太方便了!!!吐舌头,注意Litepal实体类都必须继承自DataSupport类)。

import org.litepal.crud.DataSupport;

public class Province extends DataSupport {

    private int id;

    private String provinceName;

    private int provinceCode;   //省的代号

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProvinceName() {
        return provinceName;
    }

    public void setProvinceName(String provinceName) {
        this.provinceName = provinceName;
    }

    public int getProvinceCode() {
        return provinceCode;
    }

    public void setProvinceCode(int provinceCode) {
        this.provinceCode = provinceCode;
    }
}
import org.litepal.crud.DataSupport;

public class County extends DataSupport {

    private int id;

    private String countyName;

    private String weatherId;  //天气id

    private int cityId;  //所属城市id

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountyName() {
        return countyName;
    }

    public void setCountyName(String countyName) {
        this.countyName = countyName;
    }

    public String getWeatherId() {
        return weatherId;
    }

    public void setWeatherId(String weatherId) {
        this.weatherId = weatherId;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }

}
 
import org.litepal.crud.DataSupport;

public class City extends DataSupport {

    private int id;

    private String cityName;

    private int cityCode;  //城市代号

    private int provinceId; //所属省份id

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public int getCityCode() {
        return cityCode;
    }

    public void setCityCode(int cityCode) {
        this.cityCode = cityCode;
    }

    public int getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(int provinceId) {
        this.provinceId = provinceId;
    }

}
接着需要配置litepal.xml文件,在app\src\main目录下创建一个文件夹,并在此文件夹中新建litepal.xml文件,代码如下:
 
<?xml version="1.0" encoding="utf-8"?>
<litepal>

    <dbname value="good_weather" />  //数据库名称

    <version value="1" />  //数据库版本

    <list>
        <mapping class="com.yuyang.weather.db.Province" />  //将3个实体类即3张数据表添加到映射表中。
        <mapping class="com.yuyang.weather.db.City" />
        <mapping class="com.yuyang.weather.db.County" />
    </list>

</litepal>

接下来配置LitepalApplication, 修改清单中,修改代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yuyang.weather">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="org.litepal.LitePalApplication"  //此处为LitepalApplication的配置

       
    </application>

</manifest>

配置好Litepal后,我们就完成了数据库的所有配置了。Litepal的使用给数据库的操作带来了极大的方便,给郭霖大神一个赞

ヾ(◍°∇°◍)ノ゙。

三、遍历全国省、市、县数据

省市县的数据都是从服务器端获得到的,因此这里少不了和服务器的交互,此处我们导入OkHttp网络通信库使用前别忘了在app/build.gradle文件中声明库的依赖,此处新建HttpUtil类,完成此功能:

compile 'com.squareup.okhttp3:okhttp:3.4.1'

 
import okhttp3.OkHttpClient;
import okhttp3.Request;

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);

    }

}


  就是这样,这里得益于OkHttp出色的封装,仅仅3行代码就完成了和服务器的交互。给Square公司点个赞ヾ(◍°∇°◍)ノ゙

  现在我们我们需要发出一条HTTP请求只需调用sendOkHttpRequest()方法,传入请求地址,并注册一个回调来处理服务器响应就可以了。

   接着我们需要新建一个工具类来处理服务器返回的数据, 服务器返回的数据为Json格式


import android.text.TextUtils;

import com.weather.db.City;
import com.weather.db.County;
import com.weather.db.Province;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Utility {

    /**
     * 解析和处理服务器返回的省级数据
     */
    public static boolean handleProvinceResponse(String response) {
        if (!TextUtils.isEmpty(response)) {
            try {
                JSONArray allProvinces = new JSONArray(response);
                for (int i = 0; i < allProvinces.length(); i++) {
                    JSONObject provinceObject = allProvinces.getJSONObject(i);
                    Province province = new Province();
                    province.setProvinceName(provinceObject.getString("name"));
                    province.setProvinceCode(provinceObject.getInt("id"));
                    province.save();
                }
                return true;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 解析和处理服务器返回的市级数据
     */
    public static boolean handleCityResponse(String response, int provinceId) {
        if (!TextUtils.isEmpty(response)) {
            try {
                JSONArray allCities = new JSONArray(response);
                for (int i = 0; i < allCities.length(); i++) {
                    JSONObject cityObject = allCities.getJSONObject(i);
                    City city = new City();
                    city.setCityName(cityObject.getString("name"));
                    city.setCityCode(cityObject.getInt("id"));
                    city.setProvinceId(provinceId);
                    city.save();
                }
                return true;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 解析和处理服务器返回的县级数据
     */
    public static boolean handleCountyResponse(String response, int cityId) {
        if (!TextUtils.isEmpty(response)) {
            try {
                JSONArray allCounties = new JSONArray(response);
                for (int i = 0; i < allCounties.length(); i++) {
                    JSONObject countyObject = allCounties.getJSONObject(i);
                    County county = new County();
                    county.setCountyName(countyObject.getString("name"));
                    county.setWeatherId(countyObject.getString("weather_id"));
                    county.setCityId(cityId);
                    county.save();
                }
                return true;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    

}

这里我们定义了3个方法,对服务器返回的省市县三级数据分别进行了处理和解析,先使用JSONArray和JSONObject将数据解析出来,然后组装成实体类对象, 再调用save()方法将数据存储到数据库当中。

 接下来我们开始写界面,遍历全国的省市县功能后面还要复用到,所以我们就不写在活动里,而是写在碎片里。这样当我们需要复用时,直接在布局中引用碎片即可。界面效果如下:


这里在设置布局时,采用自定义标题栏,因为如果在碎片中直接使用ActionBar或ToolBar 会在服用碎片时产生不好的界面效果。

 接下来进行关键一步,编写用于遍历省市县数据的碎片(碎片类需继承Fragment)。代码如下:

import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.yuyang.weather.db.City;
import com.yuyang.weather.db.County;
import com.yuyang.weather.db.Province;
import com.yuyang.weather.util.HttpUtil;
import com.yuyang.weather.util.Utility;

import org.litepal.crud.DataSupport;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

/**
 * Created by YuYang on 2018/4/23 0023.
 */

public class ChooseAreaFragment extends Fragment {

    public static final int LEVEL_PROVINCE = 0;

    public static final int LEVEL_CITY = 1;

    public static final int LEVEL_COUNTY = 2;

    private ProgressDialog progressDialog;

    private TextView titleText;

    private Button backButton;

    private ListView listView;

    private ArrayAdapter<String> adapter;

    private List<String> dataList = new ArrayList<>();

    /**
     * 省列表
     */
    private List<Province> provinceList;

    /**
     * 市列表
     */
    private List<City> cityList;

    /**
     * 县列表
     */
    private List<County> countyList;

    /**
     * 选中的省份
     */
    private Province selectedProvince;

    /**
     * 选中的城市
     */
    private City selectedCity;

    /**
     * 当前选中的级别
     */
    private int currentLevel;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.choose_area, container, false);
        titleText = (TextView) view.findViewById(R.id.title_text);
        backButton = (Button) view.findViewById(R.id.back_button);
        listView = (ListView) view.findViewById(R.id.list_view);
        adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, dataList);
        listView.setAdapter(adapter);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (currentLevel == LEVEL_PROVINCE) {
                    selectedProvince = provinceList.get(position);
                    queryCities();
                } else if (currentLevel == LEVEL_CITY) {
                    selectedCity = cityList.get(position);
                    queryCounties();
                } else if (currentLevel == LEVEL_COUNTY) {
                    String weatherId = countyList.get(position).getWeatherId();
                    if (getActivity() instanceof MainActivity) {
                        Intent intent = new Intent(getActivity(), WeatherActivity.class);
                        intent.putExtra("weather_id", weatherId);
                        startActivity(intent);
                        getActivity().finish();
                    } else if (getActivity() instanceof WeatherActivity) {
                        WeatherActivity activity = (WeatherActivity) getActivity();
                        activity.drawerLayout.closeDrawers();
                        activity.swipeRefresh.setRefreshing(true);
                        activity.requestWeather(weatherId);
                    }
                }
            }
        });
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (currentLevel == LEVEL_COUNTY) {
                    queryCities();
                } else if (currentLevel == LEVEL_CITY) {
                    queryProvinces();
                }
            }
        });
        queryProvinces();
    }

    /**
     * 查询全国所有的省,优先从数据库查询,如果没有查询到再去服务器上查询。
     */
    private void queryProvinces() {
        titleText.setText("中国");
        backButton.setVisibility(View.GONE);
        provinceList = DataSupport.findAll(Province.class);
        if (provinceList.size() > 0) {
            dataList.clear();
            for (Province province : provinceList) {
                dataList.add(province.getProvinceName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_PROVINCE;
        } else {
            String address = "http://guolin.tech/api/china";
            queryFromServer(address, "province");
        }
    }

    /**
     * 查询选中省内所有的市,优先从数据库查询,如果没有查询到再去服务器上查询。
     */
    private void queryCities() {
        titleText.setText(selectedProvince.getProvinceName());
        backButton.setVisibility(View.VISIBLE);
        cityList = DataSupport.where("provinceid = ?", String.valueOf(selectedProvince.getId())).find(City.class);
        if (cityList.size() > 0) {
            dataList.clear();
            for (City city : cityList) {
                dataList.add(city.getCityName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_CITY;
        } else {
            int provinceCode = selectedProvince.getProvinceCode();
            String address = "http://guolin.tech/api/china/" + provinceCode;
            queryFromServer(address, "city");
        }
    }

    /**
     * 查询选中市内所有的县,优先从数据库查询,如果没有查询到再去服务器上查询。
     */
    private void queryCounties() {
        titleText.setText(selectedCity.getCityName());
        backButton.setVisibility(View.VISIBLE);
        countyList = DataSupport.where("cityid = ?", String.valueOf(selectedCity.getId())).find(County.class);
        if (countyList.size() > 0) {
            dataList.clear();
            for (County county : countyList) {
                dataList.add(county.getCountyName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_COUNTY;
        } else {
            int provinceCode = selectedProvince.getProvinceCode();
            int cityCode = selectedCity.getCityCode();
            String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
            queryFromServer(address, "county");
        }
    }

    /**
     * 根据传入的地址和类型从服务器上查询省市县数据。
     */
    private void queryFromServer(String address, final String type) {
        showProgressDialog();
        HttpUtil.sendOkHttpRequest(address, new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String responseText = response.body().string();
                boolean result = false;
                if ("province".equals(type)) {
                    result = Utility.handleProvinceResponse(responseText);
                } else if ("city".equals(type)) {
                    result = Utility.handleCityResponse(responseText, selectedProvince.getId());
                } else if ("county".equals(type)) {
                    result = Utility.handleCountyResponse(responseText, selectedCity.getId());
                }
                if (result) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            closeProgressDialog();
                            if ("province".equals(type)) {
                                queryProvinces();
                            } else if ("city".equals(type)) {
                                queryCities();
                            } else if ("county".equals(type)) {
                                queryCounties();
                            }
                        }
                    });
                }
            }

            @Override
            public void onFailure(Call call, IOException e) {
                // 通过runOnUiThread()方法回到主线程处理逻辑
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        closeProgressDialog();
                        Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }

    /**
     * 显示进度对话框
     */
    private void showProgressDialog() {
        if (progressDialog == null) {
            progressDialog = new ProgressDialog(getActivity());
            progressDialog.setMessage("正在加载...");
            progressDialog.setCanceledOnTouchOutside(false);
        }
        progressDialog.show();
    }

    /**
     * 关闭进度对话框
     */
    private void closeProgressDialog() {
        if (progressDialog != null) {
            progressDialog.dismiss();
        }
    }
}
 接下来分析一下上述代码:  1.onCreatView()中获得了一些控件, 然后去初始化了ArrayAdapter,并将它设置为ListView的适配器。然后在onActivityCreated()中给ListView和Buttton设置点击事件, 到这初始化工作算是完成。

此处有一个知识点,ArrayAdapter在ListView中的实现,还蛮常用的,具体用法可以点击这里查看

这里还有发起进攻(  ´-ω ・)▄︻┻┳══━一

2.在onActivityCreated()中给listview设置了点击事件并调用了queryProvinces(),从这里开始加载省级数据,调用Litepa接口从数据库接口查询省级数据,如果读取到了就直接显示在界面上,没有读取到就组装一个地址,然后调用queryFromServer()方法从服务器中获取数据。

此处涉及到Litepal对数据库的操作,操作的具体用法点击这里这里查看

此处涉及到关于notifyDataSetChanged()的使用,具体用法点击这里这里查看

3.queryFromServer()会调用HttpUtil的sendOkHttpRequest()方法向服务器发送请求,响应的数据会回调到onResponse()方法中。

给返回按钮注册点击事件时,需判断当前列表的级别,因为这决定着返回列表的级别。

如此,我们就把全国省市县的数据遍历完成了, 可是碎片是不能直接显示在界面上的, 所以我们需要将碎片添加到活动中。  只需修改activity_main.xml,如下:

<?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">

    <fragment
        android:id="@+id/choose_area_fragment"
        android:name="com.yuyang.weather.ChooseAreaFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

定义布局文件,添加碎片并使其充满整个布局;另外,我们刚才在碎片中自定义了标题栏,就不在需要原生的标题栏了, 修改res/values/styles.xml文件:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

再在清单中添加如下代码,让设备联网获取服务器数据。

<uses-permission android:name="android.permission.INTERNET" />

四、显示天气信息

1.定义GSON实体类

  由于服务器返回的天气信息非常复杂,因此我们需要借助GSON来对数据进行解析处理了,GSON解析非常方便,只需要一行代码即可,但前提是要先将数据对应的实体类创建好。

服务器返回的json数据如下:

{
	"HeWeather": [{
		"basic": {
			"cid": "CN101240201",
			"location": "九江",
			"parent_city": "九江",
			"admin_area": "江西",
			"cnty": "中国",
			"lat": "29.71203423",
			"lon": "115.99281311",
			"tz": "+8.00",
			"city": "九江",
			"id": "CN101240201",
			"update": {
				"loc": "2018-04-23 19:47",
				"utc": "2018-04-23 11:47"
			}
		},
		"update": {
			"loc": "2018-04-23 19:47",
			"utc": "2018-04-23 11:47"
		},
		"status": "ok",
		"now": {
			"cloud": "100",
			"cond_code": "300",
			"cond_txt": "阵雨",
			"fl": "16",
			"hum": "99",
			"pcpn": "0.2",
			"pres": "1010",
			"tmp": "15",
			"vis": "17",
			"wind_deg": "293",
			"wind_dir": "西北风",
			"wind_sc": "1",
			"wind_spd": "3",
			"cond": {
				"code": "300",
				"txt": "阵雨"
			}
		},
		"daily_forecast": [{
			"date": "2018-04-23",
			"cond": {
				"txt_d": "大雨"
			},
			"tmp": {
				"max": "18",
				"min": "15"
			}
		}, {
			"date": "2018-04-24",
			"cond": {
				"txt_d": "多云"
			},
			"tmp": {
				"max": "22",
				"min": "13"
			}
		}, {
			"date": "2018-04-25",
			"cond": {
				"txt_d": "多云"
			},
			"tmp": {
				"max": "23",
				"min": "15"
			}
		}],
		"aqi": {
			"city": {
				"aqi": "31",
				"pm25": "19",
				"qlty": "优"
			}
		},
		"suggestion": {
			"comf": {
				"brf": "舒适",
				"txt": "白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。",
				"type": "comf"
			},
			"sport": {
				"brf": "较不宜",
				"txt": "有降水,且风力较强,气压较低,推荐您在室内进行低强度运动;若坚持户外运动,须注意避雨防风。",
				"type": "sport"
			},
			"cw": {
				"brf": "不宜",
				"txt": "不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。",
				"type": "cw"
			}
		}
	}]
}
这里我们只创建5个实体类,分别对应"basic","aqi","now","suggestion""daily_forecast"五类数据。
import com.google.gson.annotations.SerializedName;

public class Forecast {

    public String date;

    @SerializedName("tmp")
    public Temperature temperature;

    @SerializedName("cond")
    public More more;

    public class Temperature {

        public String max;

        public String min;

    }

    public class More {

        @SerializedName("txt_d")
        public String info;

    }

}

这里只列出了两个实体类,其余实体类按照同样格式依次创建即可(需要详情请自行下载源码)。

这里需要注意,daily_forcast中包含的是一个数组,数组中的每一项都代表着未来一天的天气信息,但我们只需定义出单日天气的实体类即可,然后在声明实体类引用的时候使用集合类型来进行声明。

"daily_forecast": [{
			"date": "2018-04-23",
			"cond": {
				"txt_d": "大雨"
			},
			"tmp": {
				"max": "18",
				"min": "15"
			}
		}, {
			"date": "2018-04-24",
			"cond": {
				"txt_d": "多云"
			},
			"tmp": {
				"max": "22",
				"min": "13"
			}
		}, {
			"date": "2018-04-25",
			"cond": {
				"txt_d": "多云"
			},
			"tmp": {
				"max": "23",
				"min": "15"
			}
		}],

完成全部的实体类的创建后,还需再创建一个总的实体类来引用刚刚创建的各个实体类。

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class Weather {

    public String status;

    public Basic basic;

    public AQI aqi;

    public Now now;

    public Suggestion suggestion;

    @SerializedName("daily_forecast")
    public List<Forecast> forecastList;  //daily_forecast中包含的是一个数组,因此引用此实体类时,声明为集合类型

}

@SerializedName让JSON字段和java字段之间建立映射关系。

此时就完成了所有的GSON实体类的定义了,接下来开始编写天气的界面吧!(  ´-ω ・)▄︻┻┳══━一

2、编写天气界面

activity_weather.xml会是一个很长的布局文件。为了让布局文件代码简介和提高可读性,我们使用引用布局技术,其实就是将界面不同的部分写在不同的布局文件里面,再通过引用布局的方式集成到activity_weather.xml中,这样整个布局文件看起来很简洁。

具体代码详情请见源码ヾ(◍°∇°◍)ノ゙ヾ(◍°∇°◍)ノ゙

将每个部分的布局文件编写好后,将其依次引入到activity_weather.xml当中,代码如下:

<?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" />

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v4.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>

</FrameLayout>

此处在FrameLayout中嵌套了一个ScrollView,这是因为界面内容较多,使用ScrollerView可以允许我们通过滚动看到屏幕外的内容。 由于ScrollerView只允许一个直接子布局,因此这里又嵌套了一个垂直方向的LinearLayout,然后在LinearLayout将刚才的布局追个引入。效果图如下:


如此,我们就完成了界面的编写, 接下来开始编写业务逻辑,将天气数据显示在界面。

3、将天气显示到界面上

首先需要在Utility类中添加一个用于解析JSON数据的方法,如下:

public class Utility{
/**
     * 将返回的JSON数据解析成Weather实体类
     */
    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;
    }


}

 此处handleWeatherResponse(0方法先是通过 JSONObject 和 JSONArray将天气数据中的主体内容解析出来, 即如下内容

{
   "status" :"ok"
    "basic" : {},
    "aqi" :    {},
     "now" :   {},
     "suggestion" :  {},
    "daily_forecast" :  []  }

 然后由于我们之前已经按照上面的数据格式定义过相应的GSON实体类, 因此只需要通过调用 Gson的fromJson()方法就能直接将JSON数据转换成Weather对象了。Gson的fromJson()方法的相关用法可以参照这里

  接下来的工作就是解决在活动中请求天气数据, 以及将数据展示到界面上,修改WeatherActivity,如下:

public class WeatherActivity extends AppCompatActivity {

    public DrawerLayout drawerLayout;

    public SwipeRefreshLayout swipeRefresh;

    private ScrollView weatherLayout;

    private Button navButton;

    private TextView titleCity;

    private TextView titleUpdateTime;

    private TextView degreeText;

    private TextView weatherInfoText;

    private LinearLayout forecastLayout;

    private TextView aqiText;

    private TextView pm25Text;

    private TextView comfortText;

    private TextView carWashText;

    private TextView sportText;

    private ImageView bingPicImg;

    private String mWeatherId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }
        setContentView(R.layout.activity_weather);
        // 初始化各控件
        bingPicImg = (ImageView) findViewById(R.id.bing_pic_img);
        weatherLayout = (ScrollView) findViewById(R.id.weather_layout);
        titleCity = (TextView) findViewById(R.id.title_city);
        titleUpdateTime = (TextView) findViewById(R.id.title_update_time);
        degreeText = (TextView) findViewById(R.id.degree_text);
        weatherInfoText = (TextView) findViewById(R.id.weather_info_text);
        forecastLayout = (LinearLayout) findViewById(R.id.forecast_layout);
        aqiText = (TextView) findViewById(R.id.aqi_text);
        pm25Text = (TextView) findViewById(R.id.pm25_text);
        comfortText = (TextView) findViewById(R.id.comfort_text);
        carWashText = (TextView) findViewById(R.id.car_wash_text);
        sportText = (TextView) findViewById(R.id.sport_text);
        swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        navButton = (Button) findViewById(R.id.nav_button);
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String weatherString = prefs.getString("weather", null);
        if (weatherString != null) {
            // 有缓存时直接解析天气数据
            Weather weather = Utility.handleWeatherResponse(weatherString);
            mWeatherId = weather.basic.weatherId;
            showWeatherInfo(weather);
        } else {
            // 无缓存时去服务器查询天气
            mWeatherId = getIntent().getStringExtra("weather_id");
            weatherLayout.setVisibility(View.INVISIBLE);
            requestWeather(mWeatherId);
        }
        swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                requestWeather(mWeatherId);
            }
        });
        navButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                drawerLayout.openDrawer(GravityCompat.START);
            }
        });
        String bingPic = prefs.getString("bing_pic", null);
        if (bingPic != null) {
            Glide.with(this).load(bingPic).into(bingPicImg);
        } else {
            loadBingPic();
        }
    }

    /**
     * 根据天气id请求城市天气信息。
     */
    public void requestWeather(final String weatherId) {
        String weatherUrl = "http://guolin.tech/api/weather?cityid=" + weatherId + "&key=bc0418b57b2d4918819d3974ac1285d9";
        HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseText = response.body().string();
                final Weather weather = Utility.handleWeatherResponse(responseText);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (weather != null && "ok".equals(weather.status)) {
                            SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
                            editor.putString("weather", responseText);
                            editor.apply();
                            mWeatherId = weather.basic.weatherId;
                            showWeatherInfo(weather);
                        } else {
                            Toast.makeText(WeatherActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
                        }
                        swipeRefresh.setRefreshing(false);
                    }
                });
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(WeatherActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
                        swipeRefresh.setRefreshing(false);
                    }
                });
            }
        });
        loadBingPic();
    }

    /**
     * 加载必应每日一图
     */
    private void loadBingPic() {
        String requestBingPic = "http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String bingPic = response.body().string();
                SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(WeatherActivity.this).edit();
                editor.putString("bing_pic", bingPic);
                editor.apply();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Glide.with(WeatherActivity.this).load(bingPic).into(bingPicImg);
                    }
                });
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 处理并展示Weather实体类中的数据。
     */
    private void showWeatherInfo(Weather weather) {
        String cityName = weather.basic.cityName;
        String updateTime = weather.basic.update.updateTime.split(" ")[1];
        String degree = weather.now.temperature + "℃";
        String weatherInfo = weather.now.more.info;
        titleCity.setText(cityName);
        titleUpdateTime.setText(updateTime);
        degreeText.setText(degree);
        weatherInfoText.setText(weatherInfo);
        forecastLayout.removeAllViews();
        for (Forecast forecast : weather.forecastList) {
            View view = LayoutInflater.from(this).inflate(R.layout.forecast_item, forecastLayout, false);
            TextView dateText = (TextView) view.findViewById(R.id.date_text);
            TextView infoText = (TextView) view.findViewById(R.id.info_text);
            TextView maxText = (TextView) view.findViewById(R.id.max_text);
            TextView minText = (TextView) view.findViewById(R.id.min_text);
            dateText.setText(forecast.date);
            infoText.setText(forecast.more.info);
            maxText.setText(forecast.temperature.max);
            minText.setText(forecast.temperature.min);
            forecastLayout.addView(view);
        }
        if (weather.aqi != null) {
            aqiText.setText(weather.aqi.city.aqi);
            pm25Text.setText(weather.aqi.city.pm25);
        }
        String comfort = "舒适度:" + weather.suggestion.comfort.info;
        String carWash = "洗车指数:" + weather.suggestion.carWash.info;
        String sport = "运行建议:" + weather.suggestion.sport.info;
        comfortText.setText(comfort);
        carWashText.setText(carWash);
        sportText.setText(sport);
        weatherLayout.setVisibility(View.VISIBLE);
        Intent intent = new Intent(this, AutoUpdateService.class);
        startService(intent);
    }


}

在onCreate()方法中仍然是获取部分控件,  然后会尝试从本地缓存中去读取天气数据。 本地无缓存无缓存就从intent中获得天气的id, 调用requestWeather()方法来从服务器请求天气数据。 handleWeatherResponse()方法将返回的JSON数据转换成weather对象,  再调用runOnUiThread() 进行ui更新。关于此方法的介绍可以点击这里

如果服务器返回的status为true,即请求天气信息成功, 将数据保存至SharePregerencrs中, 并调用showWeatherInfo()方法来进行内容显示。  然后的showWeatherInfo()方法中的逻辑就比较简单了, 其实就是从Weather对象中获取数据,然后将其显示到相应的控件上。

注意在未来几天的天气预报中我们使用了for循环来处理每天的天气信息,在循环中动态加载forecast_item.xml布局并设置相应数据,然后添加到父布局当中。 这样我们就将首次进入WeatherActivity时的逻辑全部梳理完毕。

处理完全WeatherActivity中的逻辑,接下来处理从省市县列表跳转到天气界面的逻辑了, 修改ChooseAreaFragment,如下:

public class ChooseAreaFragment extends Fragment {
....

 @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (currentLevel == LEVEL_PROVINCE) {
                    selectedProvince = provinceList.get(position);
                    queryCities();
                } else if (currentLevel == LEVEL_CITY) {
                    selectedCity = cityList.get(position);
                    queryCounties();
                } else if (currentLevel == LEVEL_COUNTY) {
                    String weatherId = countyList.get(position).getWeatherId();
                    if (getActivity() instanceof MainActivity) {
                        Intent intent = new Intent(getActivity(), WeatherActivity.class);
                        intent.putExtra("weather_id", weatherId);
                        startActivity(intent);
                        getActivity().finish();
                    } }
});
...
}
...
}

非常简单,这里在onItemClick()方法中加入了一个if判断,如果当前级别是LEVEL_COUNTY,就启动 WeatherActivity,并把当前选中县的天气id传递过去。

另外我们还需要在MainActivity中加入一个缓存数据的判断, 修改代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        if (prefs.getString("weather", null) != null) {
            Intent intent = new Intent(this, WeatherActivity.class);
            startActivity(intent);
            finish();
        }
    }
}

可以看到,在onCreate()方法的一开始先从SharePreferences文件中读取缓存数据,如果不为null就说明已经请求过天气数据了, 那么就直接跳转到WeatherActivity即可,无需再次选择城市。

4、为背景获取动态更新图片

每日一图接口地址:http://guolin.tech/api/bing_pic

访问这个接口,服务器会返回一个图片的连接:

http://cn.bing.com/az/hprichbg/rb/SatelliteGlades_ROW11389308210_1920x1080.jpg

我们只需要使用Glide去加载这张图片就行。

  首先修改activity_weather.xml代码:

<?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" />
......
</FrameLayout>

  接着修改WeatherActivity,使其能加载背景图片,其中使用到了Glide,关于Glide的使用可以点击这里进行学习。


此处涉及到SharedPreferences存储技术,点击这里可以学习。


当你加载完图片后会发现,背景图片和状态栏没有融合到一起,视觉上有瑕疵, 这里使用一个小技巧解决这个问题,修改WeatherActivity代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }
.....}

主要这个功能只有安卓5.0及以上的系统才支持

关于此处涉及到的getWindow()知识,可以点击这里进行学习。

但是此处,由于系统状态栏已经成为我们的布局一部分, 因此没有为它单独留出空间, 会导致界面头布局几乎和状态栏贴在一起, 视觉效果不好, 这是只需借助android:fitsSystemWindows属性为系统状态栏留出空间即可, 修改布局文件,

<?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">
...
<LinearLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"

                    android:fitsSystemWindows="true"
                    >

...

五、手动更新天气和切换城市

1、手动更新天气

为了更新缓存中的天气信息数据,我们继续完善获取信息的手段,添加手动更新。 触发事件我们采用下拉刷新,实现简单,使用也很方便。

首先修改天气布局文件:

<?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">
......
<android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

...

</android.support.v4.widget.SwipeRefreshLayout>

....

这里在ScrollView的外面嵌套了一层SwipeRefreshLayout, 这样ScrollView就具备了下拉刷新的功能了。

然后修改WeatherActivity, 加入更新天气的处理逻辑,具体修改内容查看源码。

下来刷新会调用一个setOnRefreshListener()来设置一个下拉刷新的监听器,触发后就会回调监听器的onRefresh()方法, 我们在这里去调用requrstWeather()方法请求天气信息就行。最后一定要调用SwipRefreshLayout的setRefreshing()方法并传入false,用于表示刷新时间结束, 并隐藏刷新条。


2、切换城市

  既然是切换城市,那么肯定要复用我们前面实现的遍历全国省市县数据功能, 前面特意选择了在碎片中实现这个功能,因此现在只需要在天气界面的布局引用这个碎片,就可以完成切换城市功能了。我们这里采用通过滑动的方式将菜单(Material Design)显示出来, 视觉效果也蛮好。 首先在头布局中增加一个切换城市的按钮, 以引导用户完成切换城市的功能。

  添加了按钮之后,接着修改activity_weather.xml布局来加入滑动菜单功能, 如下所示:

<?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" />

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
......
</android.support.v4.widget.DrawerLayout>
......

<fragment
            android:id="@+id/choose_area_fragment"
            android:name="com.yuyang.weather.ChooseAreaFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            />

    </android.support.v4.widget.DrawerLayout>

</FrameLayout>

  这里在SwipRefreshLayout的外面又嵌套了一层DrawerLAyout。   DrawerLAyout的第一个子控件用于作为主屏幕中间的显示内容, 第二个显子控件用于作为滑动菜单中显示的内容,因此在这里添加了用于遍历各地数据的碎片。

   好的接下来需要在WeatherActivity中加入滑动菜单的逻辑处理,修改WeatherActivity,     

 此处知识点,DrawLayout,点击这里学习。

  打开菜单后,还需处理切换城市后的逻辑,这个处理必须在ChooseAreaFragment中完成, 因为之前选中城市后是直接跳转到天气活动,  而现在界面就处于天气活动 无需跳转, 那么狠明显 这里需要根据ChooseAreaFragment的不同状态进行不同的逻辑处理,看源码。

 

......

else if (currentLevel == LEVEL_COUNTY) {
                    String weatherId = countyList.get(position).getWeatherId();
                    if (getActivity() instanceof MainActivity) {
                        Intent intent = new Intent(getActivity(), WeatherActivity.class);
                        intent.putExtra("weather_id", weatherId);
                        startActivity(intent);
                        getActivity().finish();
                    } else if (getActivity() instanceof WeatherActivity) {
                        WeatherActivity activity = (WeatherActivity) getActivity();
                        activity.drawerLayout.closeDrawers();
                        activity.swipeRefresh.setRefreshing(true);
                        activity.requestWeather(weatherId);
                    }
                }
......

 这里使用了Java的instancsof关键字 可以用来判断一个对象是否属于某各类的实力, 此处当地域级别为县区级别时, 判断还为主活动时, 就立即跳转至天气活动, 执行更新天气信息,下拉刷新等操作。 

okokok,现在进入最后一个阶段

六、后台自动更新天气

 这样可以保证用户每次打开时看到的都是最新的天气信息。 需要实现自动更新的功能,就需要创建一个长期在后台运行的定时任务, 即创建服务,。

public class AutoUpdateService extends Service {

    @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 * 60 * 1000; // 这是8小时的毫秒数
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        Intent i = new Intent(this, AutoUpdateService.class);
        PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
        manager.cancel(pi);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 更新天气信息。
     */
    private void updateWeather(){
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String weatherString = prefs.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=bc0418b57b2d4918819d3974ac1285d9";
            HttpUtil.sendOkHttpRequest(weatherUrl, new Callback() {
                @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();
                    }
                }

                @Override
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    /**
     * 更新必应每日一图
     */
    private void updateBingPic() {
        String requestBingPic = "http://guolin.tech/api/bing_pic";
        HttpUtil.sendOkHttpRequest(requestBingPic, new Callback() {
            @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();
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
        });
    }

此处知识点AlarmManager, 点击这里学习。

注意:完成服务的创建后, 还需去活动中激活服务才行。

public class WeatherActivity ectends AppCompatActivity{
... 
private void showWeatherInfo(Weather weather){
Intent intent = new Intent(this, AutoUpdateService.class);
        startService(intent);
    }


}

这样,只要一旦选择了某个城市的天气并成功更新天气之后, AutoUpdataService就会一直在后台运行,并保证8小时更新一次天气。

  





  














 













  • 0
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值