據說郭大的此係列的第二本快要出版了。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仍是很全的。