一 本片文章主要记录了如何开发一个桌面小部件,以及如何获取天气预报信息,并实时异步更新界面。
一个典型的桌面小部件开发步骤可以参考:http://www.cnblogs.com/playing/archive/2011/04/12/2013386.html
二 开发步骤
1.创建res/layout/weather_widget_layout.xml用以描述部件的布局,代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/weather_widget_back"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/city_name" android:text="城市名称" android:textSize="20sp" android:layout_alignParentLeft="true" android:layout_marginTop="10dp" android:layout_marginLeft="15dp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/cloud" android:text="风向" android:textSize="20sp" android:layout_marginTop="10dp" android:layout_marginRight="15dp" android:layout_alignParentRight="true"/> <LinearLayout android:layout_below="@id/city_name" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:layout_marginBottom="10dp"> <ImageView android:id="@+id/weather" android:layout_width="0dp" android:layout_height="match_parent" android:src="@drawable/little_rain" android:layout_weight="1"/> <TextView android:id="@+id/cur_temp" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="20" android:textSize="50sp"/> <LinearLayout android:layout_height="match_parent" android:layout_width="0dp" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/low_temp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="最低 13" android:textSize="30sp"/> <TextView android:id="@+id/high_temp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="最高 20" android:textSize="30sp"/> </LinearLayout> </LinearLayout> </RelativeLayout>
2.创建一个小部件的内容提供者文件res/xml/weather_info.xml,代码如下:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="214dp" android:minHeight="142dp" android:updatePeriodMillis="1000" android:initialLayout="@layout/weather_widget_layout" > </appwidget-provider>
3.创建一个AppWidgetProvider的子类WeatherWidget,代码如下:
public class WeatherWidget extends AppWidgetProvider { public static WeatherFm wf = new WeatherFm(); @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); Log.v("totoro","totoro1:" + appWidgetIds.length); LoadWeatherService.appWidgetIds = appWidgetIds; Log.v("totoro","totoro2:" + LoadWeatherService.appWidgetIds.length); context.startService(new Intent(context,LoadWeatherService.class)); } public static RemoteViews updateRemoteViews(Context context) { RemoteViews view = new RemoteViews(context.getPackageName(),R.layout.weather_widget_layout); if (null == wf) { return null; } else { view.setTextViewText(R.id.cur_temp, wf.getCurTemperature()); view.setTextViewText(R.id.low_temp, "低" + wf.getLowestTemperature()); view.setTextViewText(R.id.high_temp, "高" + wf.getHighestTemperature()); return view; } } }
关于类中的逻辑,稍后解释。4. 在AndroidManifest.xml中注册:
<receiver android:name="com.monde.mondewidget.weatherwidget.WeatherWidget" android:icon="@drawable/ic_weather_widget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/weather_info"/> </receiver>
以上是这个天气预报widget的基本创建步骤,而要实现天气的实时更新,还需要做更多的工作。
![]()
这是这个桌面部件所要用到的所有类,由于涉及到网络访问,所以在WeatherWidget类的onReceive方法中,无法直接对UI进行更新。
context.startService(new Intent(context,LoadWeatherService.class));这句话是用以启动网络访问获取天气信息的服务,这个类代码如下:
public class LoadWeatherService extends Service implements Runnable{ private static Object isLock = new Object(); private static boolean isThreadRun = false; public static int[] appWidgetIds; @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.v("onStartCommand","totoro:" + intent.toString()); new Thread(this).start(); // synchronized (isLock) { // if (!isThreadRun) { // isThreadRun = true; // new Thread(this).start(); // } // } return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void run() { Looper.prepare(); Log.v("onStartCommand","totoro"); BDLocationUtils utils = new BDLocationUtils(this.getApplicationContext()); utils.requestBDLocation(); AppWidgetManager manager = AppWidgetManager.getInstance(this); WeatherQueryImpl impl = new WeatherQueryImpl(this); WeatherWidget.wf = impl.weatherQuery(utils.cityCode); RemoteViews view = WeatherWidget.updateRemoteViews(this); if (null != view) { manager.updateAppWidget(appWidgetIds, view); } else { Log.e("run", "更新失败"); } stopSelf(); Looper.loop(); }
}
在这个服务中,启动了一个线程去处理网络访问,获取天气信息并使用返回的数据,更新了UI,实际的天气预报获取操作,均在WeatherQueryImpl中实现:
public class WeatherQueryImpl implements WeatherQuery { private Context context; public WeatherQueryImpl(Context context) { this.context = context; } @Override public WeatherFm weatherQuery(String cityCode) { Log.v("weatherQuery","totoro:" + cityCode); String URL1 = "http://www.weather.com.cn/data/sk/" + cityCode + ".html"; String URL2 = "http://www.weather.com.cn/data/cityinfo/" + cityCode + ".html"; WeatherFm wf = new WeatherFm(); wf.setCityCode(cityCode); String Weather_Result = ""; HttpGet httpRequest = new HttpGet(URL1); // 获得当前温度 try { HttpClient httpClient = new DefaultHttpClient(); HttpResponse httpResponse = httpClient.execute(httpRequest); if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { // 取得返回的数据 Weather_Result = EntityUtils.toString(httpResponse.getEntity()); Log.v("totoro",Weather_Result); } } catch (Exception e) { e.printStackTrace(); return null; } //以下是对返回JSON数据的解析 if(null != Weather_Result&&!"".equals(Weather_Result)){ try { JSONObject JO = new JSONObject(Weather_Result).getJSONObject("weatherinfo"); wf.setCurTemperature(JO.getString("temp")); wf.setCityName(JO.getString("city")); } catch (JSONException e) { e.printStackTrace(); return null; } } Weather_Result = ""; httpRequest = new HttpGet(URL2); // 获得HttpResponse对象 try { HttpClient httpClient = new DefaultHttpClient(); HttpResponse httpResponse = httpClient.execute(httpRequest); if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { // 取得返回的数据 Weather_Result = EntityUtils.toString(httpResponse.getEntity()); Log.v("totoro",Weather_Result); } } catch (Exception e) { e.printStackTrace(); return null; } //以下是对返回JSON数据的解析 if(null != Weather_Result&&!"".equals(Weather_Result)){ try { JSONObject JO = new JSONObject(Weather_Result).getJSONObject("weatherinfo"); wf.setWeatherCondition(JO.getString("weather")); wf.setLowestTemperature(JO.getString("temp2")); wf.setHighestTemperature(JO.getString("temp1")); } catch (JSONException e) { e.printStackTrace(); return null; } } return wf; } @Override public String cityQuery() { try { return LocationUtils.getCNByGPSlocation(context); // return LocationUtils.getCNByWIFILocation(context); } catch (Exception e) { e.printStackTrace(); return null; } } public WeatherFm getLocalWeather () { return weatherQuery(LocationCode.CHINESE_LOCAL_CODE.get(cityQuery())); }
}
关于天气预报的获取,来自中国天气网:http://www.weather.com.cn/,比较权威;此前共有3个接口可供使用:
http://m.weather.com.cn/data/101280601.html
http://www.weather.com.cn/data/sk/101280601.html
http://www.weather.com.cn/data/cityinfo/101010100.html
而第一个接口现在已经停止更新,第二个接口返回的数据格式如下:
"weatherinfo": { "city": "深圳", "cityid": "101280601", "temp": "31", "WD": "东南风", "WS": "3级", "SD": "58%", "WSE": "3", "time": "17:10", "isRadar": "1", "Radar": "JC_RADAR_AZ9755_JB" }
第三个接口返回的数据格式如下:
"weatherinfo": { "city": "深圳", "cityid": "101280601", "temp1": "26℃", "temp2": "32℃", "weather": "多云", "img1": "n1.gif", "img2": "d1.gif", "ptime": "18:00" }
都是json格式,处理起来都很方便,而关于城市代码的获取,网上的资料很多。
location相关的类都是与城市定位有关系,由于还未实现完全,就不介绍了。