最近買了本書《Android第一行代碼》,通篇看了下感受不錯,書本最後有個實戰項目酷歐天氣,閒來無事就照着敲了一遍代碼,主要在請求天氣接口和背景優化作了些小改動,如今來記錄下。html
(1) android studio完成代碼目錄結構java
其中activity包存放天氣全部活動有關的代碼,db包用於存放全部數據庫相關的代碼,model包存放全部模型相關的代碼,receiver包用於存放全部廣播接收器相關的代碼,service包用於存放全部服務相關的代碼,util包用於存放全部工具相關的代碼。android
(2) 建立好數據庫和表數據庫
在db包下新建一個BtWeatherOpenHelper類,這裏我準備創建三張表,Provice、City、County,分別用於存放省、市、縣,代碼以下:json
1 public class BtWeatherOpenHelper extends SQLiteOpenHelper { 2 3 /** 4 * Province表建表語句 5 */ 6 public static final String CREATE_PROVINCE = "create table Province (" 7 + "id integer primary key autoincrement," 8 + "province_name text," 9 + "province_code text)"; 10 11 /** 12 * City表建表語句 13 */ 14 public static final String CREATE_CITY = "create table City (" 15 + "id integer primary key autoincrement," 16 + "city_name text, " 17 + "city_code text, " 18 + "province_id integer)"; 19 20 /** 21 * County表建表語句 22 */ 23 public static final String CREATE_COUNTY = "create table County (" 24 + "id integer primary key autoincrement, " 25 + "county_name text, " 26 + "county_code text, " 27 + "city_id integer)"; 28 29 30 public BtWeatherOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { 31 super(context, name, factory, version); 32 } 33 34 @Override 35 public void onCreate(SQLiteDatabase db) { 36 db.execSQL(CREATE_PROVINCE); // 建立Province表 37 db.execSQL(CREATE_CITY); // 建立City表 38 db.execSQL(CREATE_COUNTY); // 建立County表 39 } 40 41 @Override 42 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 43 44 } 45 }
對於Province建表語句裏,其中id是自增加主鍵,province_name表示省名,province_code表示省級代號;服務器
對於City建表語句裏,其中id是自增加主鍵,city_name表示城市名,city_code表示市級代號,province_id是City表關聯Province表的外鍵;網絡
對於County建表語句裏,其中id是自增加主鍵,county_name表示縣名,county_code表示縣級代號,city_id是County表關聯City表的外鍵。app
接下來咱們要在每張表寫一個對應的實體類,這樣方便咱們後續的開發工做。所以,在model包下新建一個Province類,以下:異步
1 public class Province { 2 private int id; 3 private String provinceName; 4 private String provinceCode; 5 6 7 public int getId() { 8 return id; 9 } 10 11 public void setId(int id) { 12 this.id = id; 13 } 14 15 public String getProvinceName() { 16 return provinceName; 17 } 18 19 public void setProvinceName(String provinceName) { 20 this.provinceName = provinceName; 21 } 22 23 public String getProvinceCode() { 24 return provinceCode; 25 } 26 27 public void setProvinceCode(String provinceCode) { 28 this.provinceCode = provinceCode; 29 } 30 }
同理也在model包下新建一個City和County類,能夠看到實體類的內容很是簡單,基本就是生成數據庫表對應字段的get和set方法就能夠了。接着咱們還須要建立一個BtWeatherDB類,這個類會把一些經常使用的數據庫操做封裝起來,以方便咱們後面實用,代碼以下:ide
1 public class BtWeatherDB { 2 /** 3 * 數據庫名 4 */ 5 public static final String DB_NAME = "Bt_weather"; 6 7 /** 8 * 數據庫版本 9 */ 10 public static final int VERSION = 1; 11 private static BtWeatherDB btWeatherDB; 12 private SQLiteDatabase db; 13 14 /** 15 * 將構造方法私有化 16 */ 17 private BtWeatherDB(Context context){ 18 BtWeatherOpenHelper dbHelper = new BtWeatherOpenHelper(context,DB_NAME,null,VERSION); 19 db = dbHelper.getWritableDatabase(); 20 } 21 22 /** 23 * 獲取BtWeatherDB的實例 24 */ 25 public synchronized static BtWeatherDB getInstance(Context context){ 26 if (btWeatherDB == null){ 27 btWeatherDB = new BtWeatherDB(context); 28 } 29 return btWeatherDB; 30 } 31 32 /** 33 * 將Province實例存儲到數據庫 34 */ 35 public void saveProvince(Province province){ 36 if (province != null){ 37 ContentValues values = new ContentValues(); 38 values.put("province_name",province.getProvinceName()); 39 values.put("province_code",province.getProvinceCode()); 40 db.insert("Province",null,values); 41 } 42 } 43 44 /** 45 * 從數據庫獲取全國全部省份信息 46 */ 47 public List<Province> loadProvince(){ 48 List<Province> list = new ArrayList<Province>(); 49 Cursor cursor = db.query("Province",null,null,null,null,null,null); 50 if (cursor.moveToFirst()){ 51 do{ 52 Province province = new Province(); 53 province.setId(cursor.getInt(cursor.getColumnIndex("id"))); 54 province.setProvinceName(cursor.getString(cursor.getColumnIndex("province_name"))); 55 province.setProvinceCode(cursor.getString(cursor.getColumnIndex("province_code"))); 56 list.add(province); 57 } while(cursor.moveToNext()); 58 } 59 if (cursor != null){ 60 cursor.close(); 61 } 62 return list; 63 } 64 65 /** 66 * 將City實例存儲到數據庫 67 */ 68 public void saveCity(City city){ 69 if (city != null){ 70 ContentValues values = new ContentValues(); 71 values.put("city_name",city.getCityName()); 72 values.put("city_code",city.getCityCode()); 73 values.put("province_id",city.getProvinceId()); 74 db.insert("City",null,values); 75 } 76 } 77 78 /** 79 * 將數據庫讀取某省下全部的城市信息 80 */ 81 public List<City> loadCities(int provinceId){ 82 List<City> list = new ArrayList<City>(); 83 Cursor cursor = db.query("City",null,"province_id = ?", 84 new String[]{String.valueOf(provinceId)},null,null,null); 85 86 if (cursor.moveToFirst()){ 87 do{ 88 City city = new City(); 89 city.setId(cursor.getInt(cursor.getColumnIndex("id"))); 90 city.setCityName(cursor.getString(cursor.getColumnIndex("city_name"))); 91 city.setCityCode(cursor.getString(cursor.getColumnIndex("city_code"))); 92 city.setProvinceId(provinceId); 93 list.add(city); 94 }while (cursor.moveToNext()); 95 } 96 if (cursor != null){ 97 cursor.close(); 98 } 99 return list; 100 } 101 102 /** 103 * 將County實例存儲到數據庫 104 */ 105 public void saveCounty(County county){ 106 if (county != null){ 107 ContentValues values = new ContentValues(); 108 values.put("county_name",county.getCountyName()); 109 values.put("county_code",county.getCountyCode()); 110 values.put("city_id",county.getCityId()); 111 db.insert("County",null,values); 112 } 113 } 114 115 /** 116 * 從數據庫讀取某城市下全部的縣信息 117 */ 118 public List<County> loadCounties(int cityId){ 119 List<County> list = new ArrayList<County>(); 120 Cursor cursor = db.query("County",null,"City_id = ?", 121 new String[] {String.valueOf(cityId)},null,null,null); 122 if (cursor.moveToFirst()){ 123 do{ 124 County county = new County(); 125 county.setId(cursor.getInt(cursor.getColumnIndex("id"))); 126 county.setCountyName(cursor.getString(cursor.getColumnIndex("county_name"))); 127 county.setCountyCode(cursor.getString(cursor.getColumnIndex("county_code"))); 128 county.setCityId(cityId); 129 list.add(county); 130 } while(cursor.moveToNext()); 131 } 132 if (cursor != null){ 133 cursor.close(); 134 } 135 return list; 136 } 137 }
從上面能夠看到,BtWeatherDB是一個單例類,咱們將它的構造方法私有化,並提供一個getInstance()方法來獲取BtWeatherDB的實例,這樣就能夠保證全局範圍內只有一個BtWeathereDB的實例。接下來咱們在BtWeatherDB中提供了六組方法,saveProvince()、loadProvince()、saveCity()、loadCities()、saveCounty()、loadCounties(),分別用於存儲省份數據、讀取全部省份數據、存儲城市數據、讀取某省內全部城市數據、存儲縣數據、讀取某市內全部縣的數據。
(3) 遍歷全國省市縣數據
咱們知道,全國省市縣的數據都是經過網絡請求服務器端得到的,所以這裏和服務器的交互必不可少,因此咱們在util包下先增長一個HttpUtil類,代碼以下:
1 public class HttpUtil { 2 public static void sendHttpRequest(final String address, final HttpCallbackListener listener){ 3 new Thread( new Runnable() { 4 @Override 5 public void run() { 6 HttpURLConnection connection = null; 7 try{ 8 URL url = new URL(address); 9 connection = (HttpURLConnection)url.openConnection(); 10 connection.setRequestMethod("GET"); 11 connection.setConnectTimeout(8000); 12 connection.setReadTimeout(8000); 13 InputStream in = connection.getInputStream(); 14 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 15 StringBuilder response = new StringBuilder(); 16 String line; 17 while ((line = reader.readLine()) != null){ 18 response.append(line); 19 } 20 if (listener != null){ 21 // 回調onFinish()方法 22 listener.onFinish(response.toString()); 23 } 24 }catch (Exception e){ 25 if (listener != null){ 26 // 回調onError()方法 27 listener.onError(e); 28 } 29 }finally { 30 if (connection != null){ 31 connection.disconnect(); 32 } 33 } 34 } 35 }).start(); 36 } 37 }
HttpUtil類中使用到了HttpCallbackListener接口來回調服務返回的結果,所以咱們須要在util包下添加這個接口,以下:
1 public interface HttpCallbackListener { 2 void onFinish(String response); 3 4 void onError(Exception e); 5 }
另外,因爲服務器返回的省市縣數據都是「代號|城市,代號|城市」這種格式的,因此咱們最好在提供一個工具類來解析和處理這種數據,故在util包下新建一個Utility類,代碼以下:
1 public class Utility { 2 3 /** 4 * 解析和處理服務器返回的省級數據 5 */ 6 public synchronized static boolean handleProvinceResponse(BtWeatherDB btWeatherDB,String response){ 7 if (!(TextUtils.isEmpty(response))){ 8 String[] allProvince = response.split(","); 9 if (allProvince != null && allProvince.length > 0){ 10 for (String p : allProvince){ 11 String[] array = p.split("\\|"); 12 Province province = new Province(); 13 province.setProvinceCode(array[0]); 14 province.setProvinceName(array[1]); 15 // 將解析出來的數據存儲到Province類 16 btWeatherDB.saveProvince(province); 17 } 18 return true; 19 } 20 } 21 return false; 22 } 23 24 /** 25 * 解析和處理服務器返回的市級數據 26 */ 27 public static boolean handleCitiesResponse(BtWeatherDB btWeatherDB,String response,int provinceId){ 28 if (!TextUtils.isEmpty(response)){ 29 String[] allCities = response.split(","); 30 if (allCities != null && allCities.length > 0){ 31 for (String c: allCities){ 32 String[] array = c.split("\\|"); 33 City city = new City(); 34 city.setCityCode(array[0]); 35 city.setCityName(array[1]); 36 city.setProvinceId(provinceId); 37 // 將解析出來的數據存儲到City類 38 btWeatherDB.saveCity(city); 39 } 40 return true; 41 } 42 } 43 return false; 44 } 45 46 /** 47 * 解析和處理服務器返回的縣級數據 48 */ 49 public static boolean handleCountiesResponse(BtWeatherDB btWeatherDB,String response,int CityId){ 50 if (!TextUtils.isEmpty(response)){ 51 String[] allCounties = response.split(","); 52 if (allCounties != null && allCounties.length > 0){ 53 for (String c: allCounties){ 54 String[] array = c.split("\\|"); 55 County county = new County(); 56 county.setCountyCode(array[0]); 57 county.setCountyName(array[1]); 58 county.setCityId(CityId); 59 btWeatherDB.saveCounty(county); 60 } 61 return true; 62 } 63 } 64 return false; 65 } 66 67 /** 68 * 解析服務器返回的JSON數據,並將解析出的數據存儲到本地 69 */ 70 public static void handleWeatherResponse(Context context,String response){ 71 try{ 72 JSONObject jsonObject = new JSONObject(response); 73 JSONObject weatherInfo = jsonObject.getJSONObject("weatherinfo"); 74 String cityName = weatherInfo.getString("city"); 75 String weatherCode = weatherInfo.getString("cityid"); 76 String temp1 = weatherInfo.getString("temp1"); 77 String temp2 = weatherInfo.getString("temp2"); 78 String weatherDesp = weatherInfo.getString("weather"); 79 String publishTime = weatherInfo.getString("ptime"); 80 saveWeatherInfo(context, cityName, weatherCode, temp1, temp2, weatherDesp, publishTime); 81 } catch (JSONException e){ 82 e.printStackTrace(); 83 } 84 } 85 86 /** 87 * 將服務器返回的全部的天氣信息存儲到SharePreferences文件中 88 */ 89 public static void saveWeatherInfo(Context context, String cityName, String weatherCode, String temp1, String temp2, 90 String weatherDesp, String publishTime){ 91 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日", Locale.CANADA); 92 SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); 93 editor.putBoolean("city_selected",true); 94 editor.putString("city_name",cityName); 95 editor.putString("weather_code",weatherCode); 96 editor.putString("temp1",temp1); 97 editor.putString("temp2",temp2); 98 editor.putString("weather_desp",weatherDesp); 99 editor.putString("publish_time",publishTime); 100 editor.putString("current_date",sdf.format(new Date())); 101 editor.commit(); 102 } 103 }
能夠看到,咱們提供了 handleProvinceResponse()、handleCitiesResponse()、handleCountiesResponse()、handleWeatherResponse()、saveWeatherInfo()這五個方法,分別用於解析和處理服務器返回的省級、市級、縣級數據,用於JSON格式的天氣信息解析出來和保存天氣信息到文件裏。
而後咱們寫下界面,在res/layout目錄中新建choose_area.xml佈局,代碼以下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical"> 6 7 <RelativeLayout 8 android:layout_width="match_parent" 9 android:layout_height="50dp" 10 android:background="#484E61"> 11 12 <TextView 13 android:layout_width="wrap_content" 14 android:layout_height="wrap_content" 15 android:layout_centerInParent="true" 16 android:textColor="#fff" 17 android:textSize="24sp" 18 android:id="@+id/title_text"/> 19 </RelativeLayout> 20 21 <ListView 22 android:layout_width="match_parent" 23 android:layout_height="match_parent" 24 android:id="@+id/list_view"> 25 </ListView> 26 27 </LinearLayout>
接下來最關鍵一步,就是要編寫用於遍歷省市縣數據的活動了,在activity包下新建ChooseAreaActivity繼承於Activity,代碼以下:
1 public class ChooseAreaActivity extends Activity { 2 3 public static final String TAG = ChooseAreaActivity.class.getSimpleName(); 4 public static final int LEVEL_PROVINCE = 0; 5 public static final int LEVEL_CITY = 1; 6 public static final int LEVEL_COUNTY = 2; 7 8 private ProgressDialog progressDialog; 9 private TextView titleText; 10 private ListView listView; 11 private ArrayAdapter<String> adapter; 12 private BtWeatherDB btWeatherDB; 13 private List<String> dataList = new ArrayList<String>(); 14 15 // 省列表 16 private List<Province> provinceList; 17 18 // 市列表 19 private List<City> cityList; 20 21 // 縣列表 22 private List<County> countyList; 23 24 // 選中的省份 25 private Province selectedProvince; 26 27 // 選中的城市 28 private City selectedCity; 29 30 // 當前選中的級別 31 private int currentLevel; 32 33 /** 34 * 是否從WeatherActivity中跳轉過來 35 */ 36 private boolean isFromWeatherActivity; 37 38 protected void onCreate(Bundle savedInstanceState){ 39 super.onCreate(savedInstanceState); 40 requestWindowFeature(Window.FEATURE_NO_TITLE); 41 isFromWeatherActivity = getIntent().getBooleanExtra("from_weather_activity",false); 42 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 43 // 已經選擇了城市且不是從WeatherActivity跳轉過來,纔會直接跳轉到WeatherActivity 44 if (prefs.getBoolean("city_selected",false) && !isFromWeatherActivity){ 45 Intent intent = new Intent(this,WeatherActivity.class); 46 startActivity(intent); 47 finish(); 48 return; 49 } 50 requestWindowFeature(Window.FEATURE_NO_TITLE); 51 setContentView(R.layout.choose_area); 52 listView = (ListView)findViewById(R.id.list_view); 53 titleText = (TextView)findViewById(R.id.title_text); 54 adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,dataList); 55 listView.setAdapter(adapter); 56 btWeatherDB = BtWeatherDB.getInstance(this); 57 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 58 @Override 59 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 60 if (currentLevel == LEVEL_PROVINCE){ 61 selectedProvince = provinceList.get(position); 62 queryCities(); 63 } else if (currentLevel == LEVEL_CITY){ 64 selectedCity = cityList.get(position); 65 queryCounties(); 66 } else if (currentLevel == LEVEL_COUNTY){ 67 String countyCode = countyList.get(position).getCountyCode(); 68 Intent intent = new Intent(ChooseAreaActivity.this,WeatherActivity.class); 69 intent.putExtra("county_code",countyCode); 70 startActivityForResult(intent,1); 71 } 72 } 73 }); 74 queryProvinces(); // 加載省級數據 75 } 76 77 @Override 78 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 79 switch (resultCode){ 80 case RESULT_OK: 81 break; 82 default: 83 break; 84 } 85 } 86 87 /** 88 * 查詢全國全部的省,優先從數據庫查詢,若是沒有查詢到再去服務器上查詢 89 */ 90 private void queryProvinces(){ 91 provinceList = btWeatherDB.loadProvince(); 92 if (provinceList.size() > 0){ 93 dataList.clear(); 94 for (Province province : provinceList){ 95 dataList.add(province.getProvinceName()); 96 } 97 adapter.notifyDataSetChanged(); 98 listView.setSelection(0); 99 titleText.setText("中國"); 100 currentLevel = LEVEL_PROVINCE; 101 } else { 102 queryFromServer(null,"province"); 103 } 104 } 105 106 /** 107 * 查詢選中省內全部的市,優先從數據庫查詢,若是沒有查詢到再去服務器查詢 108 */ 109 private void queryCities(){ 110 cityList = btWeatherDB.loadCities(selectedProvince.getId()); 111 if (cityList.size() > 0){ 112 dataList.clear(); 113 for (City city : cityList){ 114 dataList.add(city.getCityName()); 115 } 116 adapter.notifyDataSetChanged(); 117 listView.setSelection(0); 118 titleText.setText(selectedProvince.getProvinceName()); 119 currentLevel = LEVEL_CITY; 120 } else { 121 queryFromServer(selectedProvince.getProvinceCode(),"city"); 122 } 123 } 124 125 /** 126 * 查詢選中市內全部的縣,優先從數據庫查詢,若是沒有查詢到再去服務器查詢 127 */ 128 private void queryCounties(){ 129 countyList = btWeatherDB.loadCounties(selectedCity.getId()); 130 if (countyList.size() > 0){ 131 dataList.clear(); 132 for (County county : countyList){ 133 dataList.add(county.getCountyName()); 134 } 135 adapter.notifyDataSetChanged(); 136 listView.setSelection(0); 137 titleText.setText(selectedCity.getCityName()); 138 currentLevel = LEVEL_COUNTY; 139 } else { 140 queryFromServer(selectedCity.getCityCode(),"county"); 141 } 142 } 143 144 /** 145 * 根據傳入的代號和類型從服務器上查詢省市縣數據 146 */ 147 private void queryFromServer(final String code ,final String type){ 148 String address; 149 if (!TextUtils.isEmpty(code)){ 150 address = "http://www.weather.com.cn/data/list3/city" + code + ".xml"; 151 } else { 152 address = "http://www.weather.com.cn/data/list3/city.xml"; 153 } 154 showProgressDialog(); 155 HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { 156 @Override 157 public void onFinish(String response) { 158 boolean result = false; 159 if ("province".equals(type)){ 160 result = Utility.handleProvinceResponse(btWeatherDB,response); 161 } else if ("city".equals(type)){ 162 result = Utility.handleCitiesResponse(btWeatherDB,response,selectedProvince.getId()); 163 } else if ("county".equals(type)){ 164 result = Utility.handleCountiesResponse(btWeatherDB,response,selectedCity.getId()); 165 } 166 if (result){ 167 // 經過runOnUiThread()方法回到主線程處理邏輯 168 runOnUiThread(new Runnable() { 169 @Override 170 public void run() { 171 closeProgressDialog(); 172 if ("province".equals(type)){ 173 queryProvinces(); 174 } else if ("city".equals(type)){ 175 queryCities(); 176 } else if ("county".equals(type)){ 177 queryCounties(); 178 } 179 } 180 }); 181 } 182 } 183 184 @Override 185 public void onError(Exception e) { 186 // 經過runOnUiThread()方法回到主線程處理邏輯 187 runOnUiThread(new Runnable() { 188 @Override 189 public void run() { 190 closeProgressDialog(); 191 Toast.makeText(ChooseAreaActivity.this,"加載失敗",Toast.LENGTH_SHORT).show(); 192 } 193 }); 194 } 195 }); 196 } 197 198 /** 199 * 顯示進度對話框 200 */ 201 private void showProgressDialog(){ 202 if (progressDialog == null){ 203 progressDialog = new ProgressDialog(this); 204 progressDialog.setMessage("正在加載..."); 205 progressDialog.setCanceledOnTouchOutside(false); 206 } 207 progressDialog.show(); 208 } 209 210 /** 211 * 關閉進度對話框 212 */ 213 private void closeProgressDialog(){ 214 if (progressDialog != null){ 215 progressDialog.dismiss(); 216 } 217 } 218 219 /** 220 * 捕獲Back鍵,根據當前的級別來判斷,此時應該返回市列表、省列表、仍是直接退出 221 */ 222 public void onBackPressed(){ 223 if (currentLevel == LEVEL_COUNTY){ 224 queryCities(); 225 } else if (currentLevel == LEVEL_CITY){ 226 queryProvinces(); 227 } else { 228 finish(); 229 } 230 } 231 232 }
這個類裏面代碼比較多,能夠邏輯並不複雜,首先onCreate()方法中先是獲取一些控件的實例,而後去初始化ArrayAdapter,將它設置爲ListView的適配器。以後又去獲取到了BtWeatherDB的實例,並給ListView設置了點擊事件,到這裏咱們的初始化工做就算是完成了。在onCreate()方法的最後,調用了queryProvince()方法,也就是從這裏開始加載省級數據。queryProvince()方法的內部會首先調用BtWeatherDB的loadProvince()方法來從數據庫中讀取省級數據,若是讀取到了就直接將數據顯示到桌面上,若是沒有讀取到就調用queryFromServer()方法來從服務器上查詢數據。
queryFromServer()方法會先根據傳入的參數來拼裝查詢地址,肯定了查詢地址以後,接下來就調用HttpUtil的sendHttpRequest()方法來向服務器發送請求,響應的數據會回調到onFinish()方法中,而後咱們在這裏去調用Utility的handleProvinceResponse()方法來解析和處理服務器返回的數據,並存儲到數據庫中。接下來在解析和處理完數據以後,咱們再次調用了queryProvince()方法來從新加載省級數據,因爲queryProvince()方法牽扯到了UI操做,所以必需要在主線程中調用,這裏藉助了runOnUiThread()方法來實現從子線程切換到主線程,它的實現原理其實也是基於異步消息處理機制的。如今數據庫中已經存在了數據,所以調用queryProvince()就會直接將數據顯示到界面上。當你點擊某個省的時候會進入到ListView的onItemClick()方法中,這個時候會根據當前的級別來判斷去調用queryCities()方法仍是queryCounties()方法,queryCities方法查詢市級數據,而queryCounties()方法是查詢縣級數據。至於onBackPressed()方法來覆蓋默認Back鍵的行爲,這裏會根據當前的級別來判斷返回市級列表、省級列表,仍是直接退出。
(4) 顯示天氣信息
在res/layout目錄中新建weather_layout.xml,代碼以下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical"> 6 7 <RelativeLayout 8 android:layout_width="match_parent" 9 android:layout_height="50dp" 10 android:background="#484E61"> 11 12 <Button 13 android:layout_width="30dp" 14 android:layout_height="30dp" 15 android:layout_centerVertical="true" 16 android:layout_marginLeft="12dp" 17 android:background="@drawable/home" 18 android:id="@+id/switch_city"/> 19 20 <TextView 21 android:layout_width="wrap_content" 22 android:layout_height="wrap_content" 23 android:layout_centerInParent="true" 24 android:textColor="#fff" 25 android:textSize="24sp" 26 android:id="@+id/city_name"/> 27 28 <Button 29 android:layout_width="30dp" 30 android:layout_height="30dp" 31 android:layout_alignParentRight="true" 32 android:layout_centerHorizontal="true" 33 android:layout_marginRight="16dp" 34 android:layout_marginTop="10dp" 35 android:background="@drawable/refresh" 36 android:id="@+id/refresh_weather"/> 37 </RelativeLayout> 38 39 <RelativeLayout 40 android:id="@+id/weather_background" 41 android:layout_width="match_parent" 42 android:layout_height="0dp" 43 android:layout_weight="1" 44 android:background="#27A5F9"> 45 46 <TextView 47 android:layout_width="wrap_content" 48 android:layout_height="wrap_content" 49 android:layout_alignParentRight="true" 50 android:layout_marginRight="10dp" 51 android:layout_marginTop="10dp" 52 android:textColor="#FFF" 53 android:textSize="18sp" 54 android:id="@+id/publish_text"/> 55 56 <LinearLayout 57 android:layout_width="wrap_content" 58 android:layout_height="wrap_content" 59 android:layout_centerInParent="true" 60 android:orientation="vertical" 61 android:id="@+id/weather_info_layout"> 62 63 <TextView 64 android:layout_width="wrap_content" 65 android:layout_height="40dp" 66 android:gravity="center" 67 android:textColor="#FFF" 68 android:textSize="18sp" 69 android:id="@+id/current_date"/> 70 71 <TextView 72 android:layout_width="wrap_content" 73 android:layout_height="60dp" 74 android:layout_gravity="center_horizontal" 75 android:gravity="center" 76 android:textColor="#FFF" 77 android:textSize="40sp" 78 android:id="@+id/weather_desp"/> 79 80 <LinearLayout 81 android:layout_width="wrap_content" 82 android:layout_height="60dp" 83 android:layout_gravity="center_horizontal" 84 android:orientation="horizontal"> 85 86 <TextView 87 android:layout_width="wrap_content" 88 android:layout_height="wrap_content" 89 android:layout_gravity="center_vertical" 90 android:textColor="#FFF" 91 android:textSize="40sp" 92 android:id="@+id/temp1"/> 93 94 <TextView 95 android:layout_width="wrap_content" 96 android:layout_height="wrap_content" 97 android:layout_gravity="center_vertical" 98 android:layout_marginLeft="10dp" 99 android:layout_marginRight="10dp" 100 android:text="~" 101 android:textColor="#FFF" 102 android:textSize="40sp"/> 103 104 <TextView 105 android:layout_width="wrap_content" 106 android:layout_height="wrap_content" 107 android:layout_gravity="center_vertical" 108 android:textColor="#FFF" 109 android:textSize="40sp" 110 android:id="@+id/temp2"/> 111 </LinearLayout> 112 </LinearLayout> 113 </RelativeLayout> 114 115 </LinearLayout>
接下來在activity包下新建WeatherActivity繼承自Activity,這裏要注意一點書中提供訪問天氣代號接口已經失效了, 就是這個地址"http://www.weather.com.cn/data/list3/city" + countyCode + ".xml"已經不能訪問了,另外經過天氣代號查詢天氣信息地址也要改成"http://www.weather.com.cn/adat/cityinfo/*/.html",不過我想到另外方法那就是下載個本地數據庫有縣級所對應的天氣代號就能夠得到天氣信息,在網上找了好久,終於找到比較全的有城市對應天氣代號數據庫文件,(數據庫文件我放在res本身建立raw目錄下),以下圖:
咱們能夠查詢城市CITY_ID找到對應的WEATHER_ID獲得天氣代號,代碼以下:
1 /** 2 * 從數據庫讀取縣對應的天氣代號 3 */ 4 private String initWeatherData(final String wt_id) { 5 String weatherdata = null; 6 String result_str = null; 7 String selection = "CITY_ID=?" ; 8 String[] selectionArgs = new String[]{ wt_id }; 9 // 導入外部數據庫複製到手機內存 10 copyDataBase(); 11 12 Cursor cursor = db.query("city_table",new String[]{"WEATHER_ID"},selection, selectionArgs, null, null, null); 13 while (cursor.moveToNext()){ 14 weatherdata = cursor.getString(0); 15 if (wt_id == weatherdata){ 16 break; 17 } 18 } 19 result_str = weatherdata; 20 return result_str; 21 } 22 23 /** 24 * 複製工程raw目錄下數據庫文件到手機內存裏 25 */ 26 private void copyDataBase() { 27 try { 28 String weatherfileName = getCacheDir() + "/" + DATABASE_FILENAME; 29 File dir = new File(DATABASE_PATH); 30 if (!dir.exists()) { 31 dir.mkdir(); 32 } 33 try { 34 InputStream is = this.getResources().openRawResource(R.raw.citychina); 35 FileOutputStream fos = new FileOutputStream(weatherfileName); 36 byte[] buffer = new byte[8192]; 37 int count = 0; 38 while ((count = is.read(buffer)) > 0) { 39 fos.write(buffer, 0, count); 40 } 41 fos.close(); 42 is.close(); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 // 根據數據庫文件路徑打開數據庫 47 db = SQLiteDatabase.openOrCreateDatabase( 48 weatherfileName, null); 49 if (db != null) { 50 KLog.v(TAG,"db build success!"); 51 } else { 52 KLog.v(TAG,"db build failed!"); 53 } 54 } catch (Exception e) { 55 e.printStackTrace(); 56 } 57 }
附數據庫文件下載地址:http://download.csdn.net/detail/wofa1648/9564085
(5) 切換城市和手動更新天氣
首先在佈局中加入切換城市和更新天氣的按鈕,初始化後再修改WeatherActivity中代碼,以下:
1 public void onClick(View v) { 2 switch (v.getId()) { 3 case R.id.switch_city: 4 Intent intent = new Intent(this, ChooseAreaActivity.class); 5 intent.putExtra("from_weather_activity", true); 6 startActivity(intent); 7 finish(); 8 break; 9 case R.id.refresh_weather: 10 publishText.setText("同步中..."); 11 new Handler().postDelayed(new Runnable() { 12 @Override 13 public void run() { 14 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 15 String weatherCode = prefs.getString("weather_code", ""); 16 if (!TextUtils.isEmpty(weatherCode)) { 17 queryWeatherChangeInfo(weatherCode); 18 } 19 else { 20 publishText.setText("今天" + prefs.getString("publish_time", "") + "發佈"); 21 } 22 } 23 },3000); 24 break; 25 default: 26 break; 27 } 28 }
(6) 後臺自動更新天氣
首先在service包下新建一個AutoUpdateService繼承自Service,代碼以下:
1 public class AutoUpdateService extends Service { 2 @Nullable 3 @Override 4 public IBinder onBind(Intent intent) { 5 return null; 6 } 7 8 public int onStartCommand(Intent intent, int flags, int startId){ 9 new Thread(new Runnable() { 10 @Override 11 public void run() { 12 updateWeather(); 13 } 14 }).start(); 15 AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE); 16 int anHour = 8 * 60 * 60 * 1000; // 這是8小時的毫秒數 17 long triggerAtTime = SystemClock.elapsedRealtime() + anHour; 18 Intent i = new Intent(this,AutoUpdateReceiver.class); 19 PendingIntent pi = PendingIntent.getBroadcast(this,0,i,0); 20 manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi); 21 return super.onStartCommand(intent,flags,startId); 22 } 23 24 /** 25 * 更新天氣信息 26 */ 27 private void updateWeather(){ 28 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 29 String weatherCode = prefs.getString("weather_code",""); 30 String address = "http://www.weather.com.cn/adat/cityinfo/" + "weatherCode" + ".html"; 31 HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { 32 @Override 33 public void onFinish(String response) { 34 Utility.handleWeatherResponse(AutoUpdateService.this,response); 35 } 36 @Override 37 public void onError(Exception e) { 38 e.printStackTrace(); 39 } 40 }); 41 } 42 }
能夠看到,在onStartCommand()方法中先是開啓了一個子線程,而後在子線程中調用updateWeather()方法來更新天氣,咱們仍然會將服務器返回的天氣數據交給Utility的handleWeatherResponse()方法去處理,這樣就能夠把最新的天氣信息存儲到SharedPreferences文件中,爲了保證軟件不會消耗過多的流量,這裏將時間間隔設置爲8小時,8小時後就應該執行到AutoUpdateReceiver的onReceive()方法中了,在receiver包下新建AutoUpdateReceiver繼承自BroadcastReceiver,代碼以下:
1 public class AutoUpdateReceiver extends BroadcastReceiver { 2 public void onReceive(Context context, Intent intent){ 3 Intent i = new Intent(context, AutoUpdateService.class); 4 context.startService(i); 5 } 6 }
這裏只是在onReceive()方法中再次去啓動AutoUpdateService,就能夠實現後臺定時更新的功能了。不過咱們還須要在代碼某處去激活AutoUpdateService這個服務才行,繼續修改WeatherActivity代碼:
1 private void showWeather() { 2 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 3 cityNameText.setText(prefs.getString("city_name", "")); 4 temp1Text.setText(prefs.getString("temp1", "")); 5 temp2Text.setText(prefs.getString("temp2", "")); 6 weatherDespText.setText(prefs.getString("weather_desp", "")); 7 publishText.setText("今天" + prefs.getString("publish_time", "") + "發佈"); 8 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); 9 String date = sdf.format(new java.util.Date()); 10 nowTime = date; 11 currentDateText.setText(nowTime); 12 WeatherKind myWeather = weather_kind.get(weatherDesp); 13 if (myWeather != null) { 14 changeBackground(myWeather); 15 } else { 16 changeBackground(WeatherKind.allwt); 17 } 18 currentDateText.setVisibility(View.VISIBLE); 19 weatherInfoLayout.setVisibility(View.VISIBLE); 20 cityNameText.setVisibility(View.VISIBLE); 21 Intent intent = new Intent(this, AutoUpdateService.class); 22 startService(intent); 23 }
最後記得在AndroidManifest.xml中註冊新增的服務和廣播接收器,以下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.weather.app.btweather"> 4 5 <application 6 android:allowBackup="true" 7 android:icon="@drawable/logo" 8 android:label="@string/app_name" 9 android:supportsRtl="true" 10 android:theme="@style/AppTheme"> 11 <activity 12 android:name=".activity.ChooseAreaActivity" 13 android:launchMode="singleTask"> 14 <intent-filter> 15 <action android:name="android.intent.action.MAIN"/> 16 17 <category android:name="android.intent.category.LAUNCHER"/> 18 </intent-filter> 19 </activity> 20 21 <activity android:name=".activity.WeatherActivity"></activity> 22 23 <service android:name=".service.AutoUpdateService"></service> 24 <receiver android:name=".receiver.AutoUpdateReceiver"></receiver> 25 </application> 26 27 <uses-permission android:name="android.permission.INTERNET"/> 28 29 </manifest>
(7) 優化軟件界面
打開軟件,咱們切換不一樣城市,發現軟件背景都同樣感受好醜,因此咱們來修改程序作到能夠根據不一樣的天氣狀況自動切換背景圖,修改WeatherActivity代碼,以下:
1 /** 2 * 枚舉各類天氣狀況 3 */ 4 private enum WeatherKind { 5 cloudy, fog, hailstone, light_rain, moderte_rain, overcast, rain_snow, sand_storm, rainstorm, 6 shower_rain, snow, sunny, thundershower,allwt; 7 } 8 9 private static Map<String,WeatherKind> weather_kind = new HashMap<String,WeatherKind>(); 10 static { 11 weather_kind.put("多雲",WeatherKind.cloudy); 12 weather_kind.put("霧", WeatherKind.fog); 13 weather_kind.put("冰雹", WeatherKind.hailstone); 14 weather_kind.put("小雨", WeatherKind.light_rain); 15 weather_kind.put("中雨",WeatherKind.moderte_rain); 16 weather_kind.put("陰",WeatherKind.overcast); 17 weather_kind.put("雨加雪",WeatherKind.rain_snow); 18 weather_kind.put("沙塵暴",WeatherKind.sand_storm); 19 weather_kind.put("暴雨",WeatherKind.rainstorm); 20 weather_kind.put("陣雨",WeatherKind.shower_rain); 21 weather_kind.put("小雪",WeatherKind.snow); 22 weather_kind.put("晴",WeatherKind.sunny); 23 weather_kind.put("雷陣雨",WeatherKind.thundershower); 24 weather_kind.put("晴轉陰",WeatherKind.allwt); 25 weather_kind.put("晴轉多雲",WeatherKind.allwt); 26 weather_kind.put("晴轉小雨",WeatherKind.allwt); 27 weather_kind.put("晴轉中雨",WeatherKind.allwt); 28 weather_kind.put("晴轉大雨",WeatherKind.allwt); 29 weather_kind.put("晴轉陣雨",WeatherKind.allwt); 30 weather_kind.put("晴轉雷陣雨",WeatherKind.allwt); 31 weather_kind.put("晴轉小雪",WeatherKind.allwt); 32 weather_kind.put("晴轉中雪",WeatherKind.allwt); 33 weather_kind.put("晴轉大雪",WeatherKind.allwt); 34 weather_kind.put("陰轉晴",WeatherKind.allwt); 35 weather_kind.put("陰轉多雲",WeatherKind.allwt); 36 weather_kind.put("陰轉小雨",WeatherKind.allwt); 37 weather_kind.put("陰轉中雨",WeatherKind.allwt); 38 weather_kind.put("陰轉大雨",WeatherKind.allwt); 39 weather_kind.put("陰轉陣雨",WeatherKind.allwt); 40 weather_kind.put("陰轉雷陣雨",WeatherKind.allwt); 41 weather_kind.put("陰轉小雪",WeatherKind.allwt); 42 weather_kind.put("陰轉中雪",WeatherKind.allwt); 43 weather_kind.put("陰轉大雪",WeatherKind.allwt); 44 weather_kind.put("多雲轉晴",WeatherKind.allwt); 45 weather_kind.put("多雲轉陰",WeatherKind.allwt); 46 weather_kind.put("多雲轉小雨",WeatherKind.allwt); 47 weather_kind.put("多雲轉中雨",WeatherKind.allwt); 48 weather_kind.put("多雲轉大雨",WeatherKind.allwt); 49 weather_kind.put("多雲轉陣雨",WeatherKind.allwt); 50 weather_kind.put("多雲轉雷陣雨",WeatherKind.allwt); 51 weather_kind.put("多雲轉小雪",WeatherKind.allwt); 52 weather_kind.put("多雲轉中雪",WeatherKind.allwt); 53 weather_kind.put("多雲轉大雪",WeatherKind.allwt); 54 weather_kind.put("小雨轉晴",WeatherKind.allwt); 55 weather_kind.put("小雨轉陰",WeatherKind.allwt); 56 weather_kind.put("小雨轉多雲",WeatherKind.allwt); 57 weather_kind.put("小雨轉中雨",WeatherKind.allwt); 58 weather_kind.put("小雨轉大雨",WeatherKind.allwt); 59 weather_kind.put("中雨轉小雨",WeatherKind.allwt); 60 weather_kind.put("中雨轉大雨",WeatherKind.allwt); 61 weather_kind.put("大雨轉中雨",WeatherKind.allwt); 62 weather_kind.put("大雨轉小雨",WeatherKind.allwt); 63 weather_kind.put("陣雨轉小雨",WeatherKind.allwt); 64 weather_kind.put("陣雨轉中雨",WeatherKind.allwt); 65 weather_kind.put("陣雨轉多雲",WeatherKind.allwt); 66 weather_kind.put("陣雨轉晴",WeatherKind.allwt); 67 weather_kind.put("陣雨轉陰",WeatherKind.allwt); 68 weather_kind.put("中雪轉小雪",WeatherKind.allwt); 69 weather_kind.put("中雪轉大雪",WeatherKind.allwt); 70 weather_kind.put("小雪轉大雪",WeatherKind.allwt); 71 weather_kind.put("小雪轉中雪",WeatherKind.allwt); 72 weather_kind.put("小雪轉晴",WeatherKind.allwt); 73 weather_kind.put("小雪轉陰",WeatherKind.allwt); 74 weather_kind.put("小雪轉多雲",WeatherKind.allwt); 75 weather_kind.put("大雪轉小雪",WeatherKind.allwt); 76 weather_kind.put("大雪轉中雪",WeatherKind.allwt); 77 weather_kind.put("霧轉小雨",WeatherKind.allwt); 78 weather_kind.put("霧轉中雨",WeatherKind.allwt); 79 weather_kind.put("霧轉大雨",WeatherKind.allwt); 80 }
1 /** 2 * 設置對應天氣背景圖 3 */ 4 private void changeBackground(WeatherKind weather){ 5 view = findViewById(R.id.weather_background); 6 switch (weather){ 7 case cloudy: 8 view.setBackgroundResource(R.drawable.cloudy); 9 break; 10 case fog: 11 view.setBackgroundResource(R.drawable.fog); 12 break; 13 case hailstone: 14 view.setBackgroundResource(R.drawable.hailstone); 15 break; 16 case light_rain: 17 view.setBackgroundResource(R.drawable.light_rain); 18 break; 19 case moderte_rain: 20 view.setBackgroundResource(R.drawable.moderte_rain); 21 break; 22 case overcast: 23 view.setBackgroundResource(R.drawable.overcast); 24 break; 25 case rain_snow: 26 view.setBackgroundResource(R.drawable.rain_snow); 27 break; 28 case sand_storm: 29 view.setBackgroundResource(R.drawable.sand_storm); 30 break; 31 case rainstorm: 32 view.setBackgroundResource(R.drawable.rainstorm); 33 break; 34 case shower_rain: 35 view.setBackgroundResource(R.drawable.shower_rain); 36 break; 37 case snow: 38 view.setBackgroundResource(R.drawable.snow); 39 break; 40 case sunny: 41 view.setBackgroundResource(R.drawable.sunny); 42 break; 43 case thundershower: 44 view.setBackgroundResource(R.drawable.thundershower); 45 break; 46 case allwt: 47 view.setBackgroundResource(R.drawable.allwt); 48 break; 49 default: 50 break; 51 } 52 }
(8) 效果顯示