文章內容
1. 實例代碼分析
經過《應用小部件(App Widget)---- 基礎篇(1)》的介紹後,並對Android 4.0.3上的一個實例代碼進行
了部分修改,然後將其作爲例子進行具體地講解。
實例代碼來源可以參考:http://developer.android.com/resources/samples/WiktionarySimple/index.html
1. 實例代碼分析
正如《應用小部件(App Widget)---- 基礎篇(1)》中的第二部分(App Widgets的基本要素 )所述,在該
實例程序中可以很明顯區分出構成部件的基本組成要素。
1.1 應用小部件提供器信息對象(AppWidgetProviderInfo object)
在工程目錄res/xml/下的widget_word.xml文件中設置部件提供器信息對象的基本信息,內容如下:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="72dip"
android:updatePeriodMillis="100000"
android:initialLayout="@layout/widget_message"
android:resizeMode="horizontal"
/>
在此,我們設置了部件的大小尺寸,更新週期,初始佈局,及調整大小的模式。
1.2 小部件視圖佈局(View layout)
在工程目錄res/layout/下的widget_word.xml文件中定義了部件,內容如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
style="@style/WidgetBackground">
<TextView
android:id="@+id/time_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
style="@style/Text.Time"/>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="@drawable/star_logo" />
<TextView
android:id="@+id/word_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/time_update"
android:layout_marginBottom="1dip"
android:includeFontPadding="false"
android:singleLine="true"
android:ellipsize="end"
style="@style/Text.WordTitle" />
<TextView
android:id="@+id/word_type"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/word_title"
android:layout_toLeftOf="@id/icon"
android:layout_alignBaseline="@id/word_title"
android:paddingLeft="4dip"
android:includeFontPadding="false"
android:singleLine="true"
android:ellipsize="end"
style="@style/Text.WordType" />
<TextView
android:id="@+id/show_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/word_title"
android:paddingRight="4dip"
android:includeFontPadding="false"
android:singleLine="true"
style="@style/BulletPoint" />
<TextView
android:id="@+id/definition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/word_title"
android:layout_toRightOf="@id/show_id"
android:paddingRight="5dip"
android:paddingBottom="4dip"
android:includeFontPadding="false"
android:lineSpacingMultiplier="0.9"
android:maxLines="4"
android:fadingEdge="vertical"
style="@style/Text.Definition" />
</RelativeLayout>
這個小部件裏主要包括了顯示更新時間的視圖元素,標題視圖元素,標題的定義視圖元素,顯示部件實例ID的視圖元素及一個小圖標視圖元素。
1.3 小部件提供器(AppWidgetProvider)類
該類的定義如下:
package com.example.android.simplewiktionary;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.IBinder;
import android.os.SystemClock;
import android.text.format.DateFormat;
import android.text.format.Time;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.example.android.simplewiktionary.SimpleWikiHelper.ApiException;
import com.example.android.simplewiktionary.SimpleWikiHelper.ParseException;
/**
* Define a simple widget that shows the Wiktionary "Word of the day." To build
* an update we spawn a background {@link Service} to perform the API queries.
*/
public class WordWidget extends AppWidgetProvider
{
public final static String MARK_WIDGET_ID = "appWidgetId";
public final static String ACTION_UPDATE = "android.test.appwidget.action_updated";
public Intent intent;
public void onUpdateWidgets(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
final int N = appWidgetIds.length;
Log.w("wanjf", "Id count is :" + String.valueOf(N));
for (int i = 0; i < N; i++)
{
int appWidgetId = appWidgetIds[i];
String strId = String.format("This widget's id = %d ", appWidgetId);
Intent intent = new Intent(context, UpdateService.class);
appWidgetManager.getAppWidgetInfo(appWidgetId).updatePeriodMillis = 0;
intent.putExtra(MARK_WIDGET_ID, appWidgetId);
context.startService(intent);
Log.w("wanjf", strId);
}
}
@Override
public void onReceive(Context context, Intent intent)
{
Log.w("wanjf","onReceive is called!");
this.intent = intent;
if(ACTION_UPDATE.equals(intent.getAction()))
{
AppWidgetManager gm = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context, WordWidget.class);
int[] appWidgetIds = gm.getAppWidgetIds(thisWidget);
Log.w("wanjf", "Recieve the broadcast !!");
Log.w("wanjf", "Widgets' count is:" + String.valueOf(appWidgetIds.length));
onUpdateWidgets(context, gm, appWidgetIds);
}
else
{
//
super.onReceive(context, intent);
//
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Log.w("wanjf","onUpdate is called!");
onUpdateWidgets(context, appWidgetManager, appWidgetIds);
super.onUpdate( context, appWidgetManager, appWidgetIds);
}
@Override
public void onDeleted(Context context,int[] widgetIds)
{
Log.w("wanjf","onDeleted is called!");
//
CharSequence prompt = "Oh! Widget id : " + String.valueOf(widgetIds[0]) + " id deleted.";
Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();
//
context.stopService(new Intent(context, UpdateService.class));
// TODO Auto-generated method stub
super.onDisabled(context);
}
@Override
public void onDisabled(Context context)
{
Log.w("wanjf","onDisabled is called!");
//
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
PendingIntent sender = PendingIntent.getBroadcast (context, 0, new Intent(ACTION_UPDATE), 0);
am.cancel(sender);
//
CharSequence prompt = "Oh! I am the last Widget";
Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();
// TODO Auto-generated method stub
super.onDisabled(context);
//
}
@Override
public void onEnabled(Context context)
{
Log.w("wanjf","onEnabled is called!");
AppWidgetManager gm = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context, WordWidget.class);
int[] appWidgetIds = gm.getAppWidgetIds(thisWidget);
//
PendingIntent sender = PendingIntent.getBroadcast (context, 0, new Intent(ACTION_UPDATE), 0);
// We want the alarm to go off 30 seconds from now.
long firstTime = SystemClock.elapsedRealtime();
firstTime += 20*1000;
// Schedule the alarm!
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME,firstTime, 15*1000, sender);
//
CharSequence prompt = "Oh! I am the first Widget id : " + String.valueOf(appWidgetIds[0]);
Toast.makeText(context, prompt ,Toast.LENGTH_SHORT).show();
// TODO Auto-generated method stub
super.onEnabled(context);
//
}
public static class UpdateService extends Service
{
@Override
public void onStart(Intent intent, int startId)
{
Log.w("wanjf","Service's onStart() is called!");
// Build the widget update for today
int appWidgetId = intent.getIntExtra(MARK_WIDGET_ID, -1);
RemoteViews updateViews = buildUpdate(this, appWidgetId);
if(appWidgetId == -1)
stopSelf();
// Push update for this widget to the home screen
//ComponentName thisWidget = new ComponentName(this, WordWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
//manager.updateAppWidget(thisWidget, updateViews);
manager.updateAppWidget(appWidgetId, updateViews);
}
/**
* Build a widget update to show the current Wiktionary
* "Word of the day." Will block until the online API returns.
*/
public RemoteViews buildUpdate(Context context, int widgetId)
{
// Pick out month names from resources
Resources res = context.getResources();
String[] monthNames = res.getStringArray(R.array.month_names);
// Find current month and day
Time today = new Time();
today.setToNow();
// Build today's page title, like "Wiktionary:Word of the day/March 21"
String pageName = res.getString(R.string.template_wotd_title, monthNames[today.month], today.monthDay);
//Log.w("wanjf",pageName);
RemoteViews updateViews = null;
String pageContent = "";
try
{
// Try querying the Wiktionary API for today's word
SimpleWikiHelper.prepareUserAgent(context);
pageContent = SimpleWikiHelper.getPageContent(pageName, false);
Log.w("wanjf",pageContent);
}
catch (ApiException e)
{
Log.e("WordWidget", "Couldn't contact API", e);
}
catch (ParseException e)
{
Log.e("WordWidget", "Couldn't parse API response", e);
}
// Use a regular expression to parse out the word and its definition
Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
Matcher matcher = pattern.matcher(pageContent);
if (matcher.find())
{
CharSequence time = DateFormat.format("hh:mm:ss", Calendar.getInstance()) ;
// Build an update that holds the updated widget contents
updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);
String wordTitle = matcher.group(1);
Log.w("wanjf",wordTitle);
updateViews.setTextViewText(R.id.word_title, wordTitle);
updateViews.setTextViewText(R.id.word_type, matcher.group(2));
updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
updateViews.setTextViewText(R.id.show_id, "[" + String.valueOf(widgetId) + "]");
updateViews.setTextViewText(R.id.time_update, time);
// When user clicks on widget, launch to Wiktionary definition page
String definePage = res.getString(R.string.template_define_url,Uri.encode(wordTitle));
Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
PendingIntent pendingIntent = PendingIntent.getActivity(context,
0 /* no requestCode */, defineIntent, 0 /* no flags */);
updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);
}
else
{
// Didn't find word of day, so show error message
updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
CharSequence errorMessage = context.getText(R.string.widget_error);
updateViews.setTextViewText(R.id.message, errorMessage);
}
return updateViews;
}
@Override
public IBinder onBind(Intent intent)
{
// We don't need to bind to this service
return null;
}
}
}
在原始代碼的基礎上,我們做了如下的修改:
1. 重寫了基類appWidgetProvider的所有方法;
2. 設置警報來更新小部件;
3. 增加對部件進行操作的提示;
4. 對某些操作(警報,服務)的設置和清理做了控制;
接下來,我們重點對上述做了修改的部分進行分析。
修改後的代碼重寫了基類的onReceive,onUpdate,onDeleted,onDisabled,onEnabled,onUpdate等
方法。有了這些方法,我們便很容易發現當他們被調用時在部件上究竟發生了什麼。
首先,當我們把第一個部件實例添加到宿主視圖內時,onReceive方法首先被調用,然後是onEnabled方法,接
着還是onReceive方法,最後是onUpdate方法。由於我們在onEnabled內設置了一個重複觸發的警報,因此當警報
在隨後被觸發時,還將調用onReceive方法。
提醒:由於onEnabled方法只在部件實例的首次被創建時纔會被調用,因此在此方法內執行一些對所有部件實例都適用的操作是最佳場所。
接着,當繼續向宿主視圖內添加一個(或多個)該部件的實例時,首先調用的是onReceive方法,然後是
onUpdate方法。
然後,從宿主內刪除一個該部件的實例時,首先調用的是onReceive方法,然後是onDeleted方法。
最後,當該部件的所有實例從宿主內被移除時,首先調用的是onReceive方法,然後是onDeleted方法,接着由
是onReceive方法,最後是onDisabled方法。
提醒:onDisabled方法也只被調用一次,因此也是執行清理操作的最佳場所。此例中,在該方法內取消了警報,停止服務。
由於我們重寫了基類的諸方法,因此別忘記調用該方法的基類版本。尤其是在重寫onReceive方法後,如果忘記
調用基類版本的onReceive,我們重寫其他方法也不會被調用。除非在重寫的onReceive方法對收到的廣播進行過濾
後再手動調用其他方法。
除了以上方法外,我們需要留意自定義方法onUpdateWidgets。在該方法內我們啓動了一個“服務”來執行部
件的更新(主要是更細其上的顯示內容)。之所以這麼做主要是因爲部件上的內容中有些是自於網絡上的數據,而對
於網絡數據的請求可能耗時幾秒鐘,甚至更多。在服務內執行網絡數據請求可以保證WordWidget的onReceive及時
返回。否則,可能會因爲ANR錯誤,活動管理器在後臺將其結束掉(Kill)。
此例中,“服務”內除了獲取網絡數據外,還對部件與用戶接口上的交互進行了設置。於是當用戶點擊部件時,
將會打開相應的網頁。
現在我們來看一下運行後的效果:
Home Screen(宿主)內的這一大一小兩個部件都是我們剛剛創建的,由於在部件提供器信息對象對應的xml文
件內設置了 android:resizeMode="horizontal",因此我們可以在水平方向上調整部件的大小。除此,我們需要留意
在部件上的藍色數字和紅色數字。藍色數字是每個部件實例最後一次更新的時間,而紅色數字是該部件的實例ID。
部件的實例id是由部件管理器維護的一組數字,表示每個實例對應一個不同的id,因此在更新時可以獲取他們並
對其代表的部件實例進行更新。
通過觀察部件上時間變化可知,每個實例上的時間變化並不是一致進行的。這是因爲在onUpdateWidgets方法內通過迭代id數組後,再使用指定的id對其表示的實例進行更新。如果要是所有實例上的時間同時更新,可以將onUpdateWidgets方法修改成:
public void onUpdateWidgets(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
Intent intent = new Intent(context, UpdateService.class);
context.startService(intent);
}
然後再將內部類UpdateService的onStart方法修改成:
public void onStart(Intent intent, int startId)
{
Log.w("wanjf","Service's onStart() is called!");
// Build the widget update for today
RemoteViews updateViews = buildUpdate(this, appWidgetId);
updateViews.setViewVisibility(R.id.show_id, View.INVISIBLE);
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, WordWidget.class);
manager.updateAppWidget(thisWidget, updateViews);
}
至此,創建一個簡單的小部件程序就分析到這裏。當然,在此實例代碼裏還有涉及到網絡編程及正則表達式方面的內容。關於網絡的那部分代碼可以參考Android SDK上的介紹來理解具體接口的用法和作用。而關於正則表達的理解可以參考:http://www.java3z.com/cwbwebhome/article/article8/Regex/Java.Regex.Tutorial.html#note01
2012年4月27日,畢