据说郭大的此系列的第二本快要出版了。java
首先咱们要作一个app须要实现哪些功能?android
因为书中提供的天气地址已通过时,基本不能用。天气更新基本就失效了。 因此我找了一个 和风天气api 注册下有天天有3000次免费试用git
省 市 县数据,整理了sql 项目地址sql
怎么玩?数据库
public class DbHelper extends SQLiteOpenHelper{ private static final String TAG = DbHelper.class.getSimpleName(); public static final String DB_NAME = "weather.db"; private Context mContext; public DbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } /** * Province表建表语句 */ public static final String CREATE_PROVINCE = "create table Province (" + "id integer primary key autoincrement, " + "province_name text, " + "province_code text)"; /** * City表建表语句 */ public static final String CREATE_CITY = "create table City (" + "id integer primary key autoincrement, " + "city_name text, " + "city_code text, " + "province_code text)"; /** * County表建表语句 */ public static final String CREATE_COUNTY = "create table County (" + "id integer primary key autoincrement, " + "county_name text, " + "county_code text, " + "city_code text)"; @Override public void onCreate(SQLiteDatabase db) { // 建立表 db.execSQL(CREATE_PROVINCE); // 建立Province表 db.execSQL(CREATE_CITY); // 建立City表 db.execSQL(CREATE_COUNTY); // 建立County表 // 初始化数据 executeAssetsSQL(db, "Province.sql"); executeAssetsSQL(db, "City.sql"); executeAssetsSQL(db, "County.sql"); Log.d(TAG, "init city data success!!!"); } /** * 读取数据库文件(.sql),并执行sql语句 * */ private void executeAssetsSQL(SQLiteDatabase db, String sqlFileName) { BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(mContext.getAssets().open(sqlFileName))); String line; while ((line = in.readLine()) != null) { db.execSQL(line); } } catch (IOException e) { Log.e(TAG, e.toString()); } finally { try { if (in != null) in.close(); } catch (IOException e) { Log.e(TAG, e.toString()); } } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
set... get... 就省了json
// 省 public class Province { private int id; private String provinceName; private String provinceCode; } // 市 public class City { private int id; private String cityName; private String cityCode; private String provinceCode; // 对应省 } // 县 public class County { private int id; private String countyName; private String countyCode; private String cityCode; //对应市 }
封装到 WeatherDbapi
public class WeatherDb { public static final int VERSION = 1; private SQLiteDatabase db; private static WeatherDb weatherDb; // 单例 private WeatherDb(Context context) { DbHelper dbHelper = new DbHelper(context, DbHelper.DB_NAME, null, VERSION); db = dbHelper.getWritableDatabase(); } /** * 获取 实例 * @param context * @return */ public synchronized static WeatherDb getInstance(Context context) { if (weatherDb == null) { weatherDb = new WeatherDb(context); } return weatherDb; } /** * 从数据库读取全国全部的省份信息。 */ public List<Province> loadProvinces() { List<Province> list = new ArrayList<Province>(); Cursor cursor = db.query("Province", null, null, null, null, null, "province_code asc"); if (cursor.moveToFirst()) { do { Province province = new Province(); province.setId(cursor.getInt(cursor.getColumnIndex("id"))); province.setProvinceName(cursor.getString(cursor .getColumnIndex("province_name"))); province.setProvinceCode(cursor.getString(cursor.getColumnIndex("province_code"))); list.add(province); } while (cursor.moveToNext()); } cursor.close(); return list; } /** * 从数据库读取某省下全部的城市信息。 */ public List<City> loadCities(String provinceCode) { List<City> list = new ArrayList<City>(); Cursor cursor = db.query("City", null, "province_code = ?", new String[] { provinceCode }, null, null, null); if (cursor.moveToFirst()) { do { City city = new City(); city.setId(cursor.getInt(cursor.getColumnIndex("id"))); city.setCityName(cursor.getString(cursor .getColumnIndex("city_name"))); city.setCityCode(cursor.getString(cursor .getColumnIndex("city_code"))); city.setProvinceCode(provinceCode); list.add(city); } while (cursor.moveToNext()); } cursor.close(); return list; } /** * 从数据库读取某城市下全部的县信息。 */ public List<County> loadCounties(String cityCode) { List<County> list = new ArrayList<County>(); Cursor cursor = db.query("County", null, "city_code = ?", new String[] { cityCode }, null, null, null); if (cursor.moveToFirst()) { do { County county = new County(); county.setId(cursor.getInt(cursor.getColumnIndex("id"))); county.setCountyName(cursor.getString(cursor .getColumnIndex("county_name"))); county.setCountyCode(cursor.getString(cursor .getColumnIndex("county_code"))); county.setCityCode(cityCode); list.add(county); } while (cursor.moveToNext()); } cursor.close(); return list; } }
用 URL 也好 , HttpClient 也行。服务器
choose_area.xmlapp
<?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"> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#484E61"> <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="24sp"/> </RelativeLayout> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>
为了识别 在哪一层级,是 省呢,仍是市呢,仍是县呢? 定义三个常量:ide
public static final int LEVEL_PROVINCE = 0; public static final int LEVEL_CITY = 1; public static final int LEVEL_COUNTY = 2;
而后得知道当前究竟是哪一个被选择了呢?
/** * 当前选中的级别 */ private int currentLevel; /** * 当前列表数据 */ private List<String> dataList = new ArrayList<String>();
主要代码 ChooseAreaActivity
... 省略变量 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.choose_area); titleView = (TextView) findViewById(R.id.title_text); listView = (ListView) findViewById(R.id.list_view); adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, dataList); listView.setAdapter(adapter); // 获取实例 ,初始化数据 weatherDb = WeatherDb.getInstance(this); // 加载省级数据 queryProvinces(); } /** * 查询所有省,同时设置当前级别和列表数据 */ private void queryProvinces() { provinceList = weatherDb.loadProvinces(); if (provinceList != null && provinceList.size() > 0 ) { dataList.clear(); for (Province province : provinceList) { dataList.add(province.getProvinceName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); titleView.setText("中国"); currentLevel = LEVEL_PROVINCE; } }
这样省的列表就展现出来了
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 countyCode = countyList.get(position).getCountyCode(); // ...具体待下一步操做 } } }); // 根据省code加载市数据,同时设置当前级别和列表数据 private void queryCities() { cityList = weatherDb.loadCities(selectedProvince.getProvinceCode()); if (cityList != null && cityList.size() > 0 ) { dataList.clear(); for (City city : cityList) { dataList.add(city.getCityName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); titleView.setText(selectedProvince.getProvinceName()); currentLevel = LEVEL_CITY; } } // 根据市code加载县数据,同时设置当前级别和列表数据 private void queryCounties() { countyList = weatherDb.loadCounties(selectedCity.getCityCode()); if (countyList != null && countyList.size() > 0) { dataList.clear(); for (County county : countyList) { dataList.add(county.getCountyName()); } adapter.notifyDataSetChanged(); listView.setSelection(0); titleView.setText(selectedCity.getCityName()); currentLevel = LEVEL_COUNTY; } }
别忘了配置AndroidManifest
<activity android:name="com.coolweather.app.activity.ChooseAreaActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
写到这里基本 切换省市县的效果就出来了
到县就能够根据县的code去请求api 查询相关天气的数据,而后展现出来。
public class WeatherInfoUtil { /** * 解析服务器返回的JSON数据,并将解析出的数据存储到本地。 */ public static void handleWeatherResponse(Context context, String response) { try { JSONObject jsonObject = new JSONObject(response); JSONArray jsonArray = jsonObject.getJSONArray("HeWeather data service 3.0"); JSONObject weatherInfo = jsonArray.getJSONObject(0); String status = weatherInfo.getString("status"); if ("ok".equals(status)) { // 基础信息 JSONObject basic = weatherInfo.getJSONObject("basic"); // 如今天气 JSONObject now = weatherInfo.getJSONObject("now"); // 天气预报, 1 -7 天 JSONArray daily_forecast = weatherInfo.getJSONArray("daily_forecast"); // 小时预报 3小时 JSONArray hourly_forecast = weatherInfo.getJSONArray("hourly_forecast"); // 空气质量 有可能没有 JSONObject aqi = weatherInfo.optJSONObject("aqi"); // 提醒 有可能没有 JSONObject suggestion = weatherInfo.optJSONObject("suggestion"); // =================基础信息================= // 城市 String cityName = basic.getString("city"); // ID String weatherCode = basic.getString("id"); JSONObject update = basic.getJSONObject("update"); // 更新时间 String publishTime = update.getString("loc"); // =================如今天气=============== // 当前温度 String temp = now.getString("tmp"); // 当前天气描述 JSONObject cond = now.getJSONObject("cond"); String weatherDesp = cond.getString("txt"); JSONObject wind = now.getJSONObject("wind"); // 风向 String dir = wind.getString("dir"); // 风力 String sc = wind.getString("sc"); // =================天气预报=============== for (int i = 0; i < daily_forecast.length(); i++) { JSONObject daily = daily_forecast.getJSONObject(i); String date = daily.getString("date"); JSONObject condDaily = daily.getJSONObject("cond"); String txt_d = condDaily.getString("txt_d"); String txt_n = condDaily.getString("txt_n"); JSONObject tmpDaily = daily.getJSONObject("tmp"); String minTemp = tmpDaily.getString("min"); String maxTemp = tmpDaily.getString("max"); JSONObject windDaily = daily.getJSONObject("wind"); String dirDaily = windDaily.getString("dir"); String scDaily = windDaily.getString("sc"); } saveWeatherInfo(context, cityName, weatherCode, temp, weatherDesp, publishTime, dir, sc); } if ("unknown city".equals(status)) { saveWeatherInfo(context, null, null, null, "未知城市", null, null, null); } if ("no more requests".equals(status)) { saveWeatherInfo(context, null, null, null, "超过访问次数", null , null, null); } } catch (JSONException e) { e.printStackTrace(); } } /** * 将服务器返回的全部天气信息存储到SharedPreferences文件中。 */ private static void saveWeatherInfo(Context context, String cityName, String weatherCode, String temp, String weatherDesp, String publishTime, String dir, String sc) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日", Locale.CHINA); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); editor.putBoolean("city_selected", true); editor.putString("city_name", cityName); editor.putString("weather_code", weatherCode); editor.putString("temp", temp + "℃"); editor.putString("weather_desp", weatherDesp); editor.putString("publish_time", publishTime); editor.putString("current_date", sdf.format(new Date())); editor.putString("wind_dir", dir); editor.putString("wind_sc", sc + "级"); editor.commit(); } }
以上就是解析json 数据,而后存到SharedPreferences 中,key-value 形式。
来想一想咱们要什么?
<?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"> <!--标题 城市名称--> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#484E61"> <Button android:id="@+id/switch_city" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:background="@drawable/home"/> <TextView android:id="@+id/city_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="#fff" android:textSize="24sp" /> <Button android:id="@+id/refresh_weather" android:layout_width="30dp" android:layout_height="30dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:background="@drawable/refresh"/> </RelativeLayout> <!--天气信息--> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#27A5F9"> <TextView android:id="@+id/publish_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:textColor="#fff" android:textSize="24sp" /> <LinearLayout android:id="@+id/weather_info_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="vertical"> <!--描述--> <TextView android:id="@+id/weather_desp" android:layout_width="wrap_content" android:layout_height="60dp" android:layout_gravity="center_horizontal" android:gravity="center" android:textColor="#fff" android:textSize="40sp" /> <!--温度--> <TextView android:id="@+id/temp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center" android:textColor="#FFF" android:textSize="40sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:id="@+id/wind_dir" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" android:textSize="18sp" android:gravity="center" /> <TextView android:id="@+id/wind_sc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" android:textSize="18sp" android:gravity="center" /> </LinearLayout> <!--日期--> <TextView android:id="@+id/current_date" android:layout_width="wrap_content" android:layout_height="40dp" android:gravity="center" android:textColor="#fff" android:textSize="18sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
首先 从 选择地区活动 到 天气活动 并把 code传递过来
ChooseAreaActivity
// }else if (currentLevel == LEVEL_COUNTY) { String countyCode = countyList.get(position).getCountyCode(); Intent intent = new Intent(ChooseAreaActivity.this, WeatherActivity.class); intent.putExtra("county_code", countyCode); startActivity(intent); finish(); }
可是若是以前已经选中,那就不要再加载了,直接跳到天气。 这个可能一开始不太理解,能够后面再加。
isFromWeatherActivity = getIntent().getBooleanExtra("from_weather_ activity", false); // 是否选中天气 , 选择就不用再加载了 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); if (preferences.getBoolean("city_selected", false) && !isFromWeatherActivity) { Intent intent = new Intent(this, WeatherActivity.class); startActivity(intent); finish(); return; }
WeatherActivity
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.weather_layout); // 初始化各控件 weatherInfoLayout = (LinearLayout) findViewById(R.id.weather_info_layout); cityNameText = (TextView) findViewById(R.id.city_name); publishText = (TextView) findViewById(R.id.publish_text); weatherDespText = (TextView) findViewById(R.id.weather_desp); tempText = (TextView) findViewById(R.id.temp); currentDateText = (TextView) findViewById(R.id.current_date); switchCity = (Button) findViewById(R.id.switch_city); refreshWeather = (Button) findViewById(R.id.refresh_weather); windDir = (TextView) findViewById(R.id.wind_dir); windSc = (TextView) findViewById(R.id.wind_sc); switchCity.setOnClickListener(this); refreshWeather.setOnClickListener(this); // ChooseAreaActivity 传过来的值 String countyCode = getIntent().getStringExtra("county_code"); // 有代号显示 选择城市的天气,没有就本地已存储的天气 if (!TextUtils.isEmpty(countyCode)) { publishText.setText("同步中..."); // 隐藏 weatherInfoLayout.setVisibility(View.INVISIBLE); cityNameText.setVisibility(View.VISIBLE); // 查天气 queryWeatherInfo(countyCode); } else { showWeather(); } } /** * 显示天气,直接从 SharedPreferences 去取,取不到就存。 */ private void showWeather() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); cityNameText.setText( prefs.getString("city_name", "")); tempText.setText(prefs.getString("temp", "")); weatherDespText.setText(prefs.getString("weather_desp", "")); String publish_time = prefs.getString("publish_time", ""); publish_time = publish_time.split(" ").length > 1 ? publish_time.split(" ")[1] : ""; publishText.setText("今天" + publish_time + "发布"); windDir.setText(prefs.getString("wind_dir", "")); windSc.setText(prefs.getString("wind_sc", "")); currentDateText.setText(prefs.getString("current_date", "")); // 显示 weatherInfoLayout.setVisibility(View.VISIBLE); cityNameText.setVisibility(View.VISIBLE); } // 查天气信息 private void queryWeatherInfo(String weatherCode) { String address = String.format("https://api.heweather.com/x3/weather?cityid=CN%s&key=%s", weatherCode, "填写api key"); Log.d(TAG, address); queryFromServer(address); } /** * 从服务器 获取 最新的天气数据 * @param address */ private void queryFromServer(String address) { HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { @Override public void onFinish(String response) { WeatherInfoUtil.handleWeatherResponse(WeatherActivity.this, response); runOnUiThread(new Runnable() { @Override public void run() { showWeather(); } }); } @Override public void onError(final Exception e) { runOnUiThread(new Runnable() { @Override public void run() { e.printStackTrace(); publishText.setText("同步失败"); } }); } }); } // 点击返回 和 刷新效果 @Override public void onClick(View v) { switch (v.getId()) { case R.id.switch_city: // 选择城市,返回上一层 Intent intent = new Intent(this, ChooseAreaActivity.class); intent.putExtra("from_weather_activity", true); startActivity(intent); finish(); break; case R.id.refresh_weather: // 刷新天气 publishText.setText("同步中..."); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); String weatherCode = preferences.getString("weather_code", ""); // 获取id 从新去访问天气信息 if (!TextUtils.isEmpty(weatherCode)) { queryWeatherInfo(weatherCode); } break; default: break; } }
加入权限和注册活动
<uses-permission android:name="android.permission.INTERNET"/> <activity android:name=".activity.WeatherActivity"/>
这部分功能不加也行,后续扩展吧。
public class AutoUpdateService extends Service{ @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run() { updateWeather(); } }).start(); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); // 8h int anHour = 8 * 60 * 60 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; Intent i = new Intent(this, AutoUpdateReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi); return super.onStartCommand(intent, flags, startId); } private void updateWeather() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); String weatherCode = preferences.getString("weather_code", ""); String address = String.format("https://api.heweather.com/x3/weather?cityid=CN%s&key=%s", weatherCode, "填写app key"); HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { @Override public void onFinish(String s) { WeatherInfoUtil.handleWeatherResponse(AutoUpdateService.this, s); } @Override public void onError(Exception e) { e.printStackTrace(); } }); } }
来个定时接收
public class AutoUpdateReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Intent i = new Intent(context, AutoUpdateService.class); context.startActivity(i); } }
别忘注册
<service android:name=".service.AutoUpdateService"/> <receiver android:name=".service.AutoUpdateReceiver"/>
在哪里启动这个服务呢?
成功载入天气数据的时候: showWeather()方法内
// 启动服务 Intent intent = new Intent(this, AutoUpdateService.class); startService(intent);
差很少就完了。
其实这个很简陋,只显示今天的,能够搞个折线图,把7天的天气预报也显示出来。
还有一些天气的图标,也能够获取到,这个api仍是很全的。