1 App Widget簡介
App Widget是應用程序窗口小部件(Widget)是微型的應用程序視圖,它可以被嵌入到其它應用程序中(比如桌面)並接收週期性的更新。你可以通過一個App Widget Provider來發佈一個Widget。
本文參考Android官方文本,先介紹App Widget的主要組件,然後再以示例來詳細說明。
2 App Widget主要的相關類介紹
2.1 AppWidgetProvider
AppWidgetProvider 繼承自 BroadcastReceiver,它能接收
widget 相關的廣播,例如 widget 的更新、刪除、開啓和禁用等。
AppWidgetProvider中的廣播處理函數如下:
onUpdate()
當 widget 更新時被執行。
同樣,當用戶首次添加 widget 時,onUpdate() 也會被調用,這樣 widget 就能進行必要的設置工作(如果需要的話) 。但是,如果定義了 widget 的 configure屬性(即android:config,後面會介紹),那麼當用戶首次添加 widget 時,onUpdate()不會被調用;之後更新 widget 時,onUpdate纔會被調用。
onAppWidgetOptionsChanged()
當 widget 被初次添加 或者 當 widget 的大小被改變時,執行onAppWidgetOptionsChanged()。你可以在該函數中,根據 widget 的大小來顯示/隱藏某些內容。可以通過 getAppWidgetOptions() 來返回 Bundle 對象以讀取 widget 的大小信息,Bundle中包括以下信息:
OPTION_APPWIDGET_MIN_WIDTH -- 包含 widget 當前寬度的下限,以dp爲單位。
OPTION_APPWIDGET_MIN_HEIGHT -- 包含 widget 當前高度的下限,以dp爲單位。
OPTION_APPWIDGET_MAX_WIDTH -- 包含 widget 當前寬度的上限,以dp爲單位。
OPTION_APPWIDGET_MAX_HEIGHT -- 包含 widget 當前高度的上限,以dp爲單位。
onAppWidgetOptionsChanged() 是 Android 4.1 引入的。
onDeleted(Context, int[])
當 widget 被刪除時被觸發。
onEnabled(Context)
當第1個 widget 的實例被創建時觸發。也就是說,如果用戶對同一個 widget 增加了兩次(兩個實例),那麼onEnabled()只會在第一次增加widget時觸發。
onDisabled(Context)
當最後1個 widget 的實例被刪除時觸發。
onReceive(Context, Intent)
接收到任意廣播時觸發,並且會在上述的方法之前被調用。
總結,AppWidgetProvider 繼承於 BroadcastReceiver。實際上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中調用的;是onReceive()對特定事情的響應函數。參考android源碼frameworks/base/core/java/android/appwidget/AppWidgetProvider.java中onReceive()的定義:
2.2 AppWidgetProviderInfo
AppWidgetProviderInfo描述一個App Widget元數據,比如App Widget的佈局,更新頻率,以及AppWidgetProvider 類。這個應該在XML裏定義。下面以XML示例來對AppWidgetProviderInfo中常用的類型進行說明。
示例XML:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/example_keyguard">
</appwidget-provider>
示例說明:
minWidth 和minHeight
它們指定了App Widget佈局需要的最小區域。
缺省的App Widgets所在窗口的桌面位置基於有確定高度和寬度的單元網格中。如果App Widget的最小長度或寬度和這些網格單元的尺寸不匹配,那麼這個App Widget將上舍入(上舍入即取比該值大的最接近的整數——譯者注)到最接近的單元尺寸。
注意:app widget的最小尺寸,不建議比 “4x4” 個單元格要大。關於app widget的尺寸,後面在詳細說明。
minResizeWidth 和 minResizeHeight
它們屬性指定了 widget 的最小絕對尺寸。也就是說,如果 widget 小於該尺寸,便會因爲變得模糊、看不清或不可用。 使用這兩個屬性,可以允許用戶重新調整 widget 的大小,使 widget 的大小可以小於 minWidth 和 minHeight。
注意:(01) 當 minResizeWidth 的值比 minWidth 大時,minResizeWidth 無效;當 resizeMode 的取值不包括 horizontal 時,minResizeWidth 無效。
(02) 當 minResizeHeight 的值比 minHeight 大時,minResizeHeight 無效;當 resizeMode 的取值不包括 vertical 時,minResizeHeight 無效。
updatePeriodMillis
它定義了 widget 的更新頻率。實際的更新時機不一定是精確的按照這個時間發生的。建議更新儘量不要太頻繁,最好是低於1小時一次。 或者可以在配置 Activity 裏面供用戶對更新頻率進行配置。 實際上,當updatePeriodMillis的值小於30分鐘時,系統會自動將更新頻率設爲30分鐘!關於這部分,後面會詳細介紹。
注意: 當更新時機到達時,如果設備正在休眠,那麼設備將會被喚醒以執行更新。如果更新頻率不超過1小時一次,那麼對電池壽命應該不會造成多大的影響。 如果你需要比較頻繁的更新,或者你不希望在設備休眠的時候執行更新,那麼可以使用基於 alarm 的更新來替代 widget 自身的刷新機制。將 alarm 類型設置爲 ELAPSED_REALTIME 或 RTC,將不會喚醒休眠的設備,同時請將 updatePeriodMillis 設爲 0。
initialLayout
指向 widget 的佈局資源文件
configure
可選屬性,定義了 widget 的配置 Activity。如果定義了該項,那麼當 widget 創建時,會自動啓動該 Activity。
previewImage
指定預覽圖,該預覽圖在用戶選擇 widget 時出現,如果沒有提供,則會顯示應用的圖標。該字段對應在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。
autoAdvanceViewId
指定一個子view ID,表明該子 view 會自動更新。在 Android 3.0 中引入。
resizeMode
指定了 widget 的調整尺寸的規則。可取的值有: "horizontal", "vertical", "none"。"horizontal"意味着widget可以水平拉伸,“vertical”意味着widget可以豎值拉伸,“none”意味着widget不能拉伸;默認值是"none"。Android 3.1 引入。
widgetCategory
指定了 widget 能顯示的地方:能否顯示在 home Screen 或 lock screen 或 兩者都可以。它的取值包括:"home_screen" 和 "keyguard"。Android 4.2 引入。
initialKeyguardLayout
指向 widget 位於 lockscreen 中的佈局資源文件。Android 4.2 引入。
3 App Widget佈局說明
3.1 添加 widget 到lock screen中
默認情況下(即不設置android:widgetCategory屬性),Android是將widget添加到 home screen 中。
但在Android 4.2中,若用戶希望 widget 可以被添加到lock screen中,可以通過設置 widget 的 android:widgetCategory 屬性包含keyguard來完成。
當你把 widget 添加到lock screen中時,你可能對它進行定製化操作,以區別於添加到home screen中的情況。 你能夠通過 getAppWidgetOptions() 來進行判斷 widget 是被添加到lock screen中,還是home screen中。通過 getApplicationOptions() 獲取 Bundle對象,然後讀取 Bundle 的OPTION_APPWIDGET_HOST_CATEGORY值:若值爲 WIDGET_CATEGORY_HOME_SCREEN, 則表示該 widget 被添加到home screen中; 若值爲 WIDGET_CATEGORY_KEYGUARD,則表示該 widget 被添加到lock screen中。
另外,你應該爲添加到lock screen中的 widget 單獨使用一個layout,可以通過 android:initialKeyguardLayout 來指定。而 widget 添加到home screen中的layout則可以通過 android:initialLayout 來指定。
3.2 佈局
一 Widget窗口組件
如上圖所示,典型的App Widget有三個主要組件:一個邊界框(A bounding box),一個框架(a Frame),和控件的圖形控件(Widget Controls)和其他元素。App Widget並不支持全部的視圖窗口,它只是支持視圖窗口的一個子集,後面會詳細說明支持哪些視圖窗口。
要設計美觀的App Widget,建議在“邊界框和框架之間(Widget Margins)”以及“框架和控件(Widget Padding)”之間填留有空隙。在Android4.0以上的版本,系統爲自動的保留這些空隙。
二 Widget窗口大小
在AppWidgetProviderInfo中已經介紹了,minWidth 和minHeight 用來指定了App Widget佈局需要的最小區域。缺省的App Widgets所在窗口的桌面位置基於有確定高度和寬度的單元網格中。如果App Widget的最小長度或寬度和這些網格單元的尺寸不匹配,那麼這個App Widget將上舍入(上舍入即取比該值大的最接近的整數——譯者注)到最接近的單元尺寸。
例如,很多手機提供4x4網格,平板電腦能提供8x7網格。當widget被添加到時,在滿足minWidth和minHeight約束的前提下,它將被佔領的最小數目的細胞。
粗略計算minWidth和minHeight,可以參考下面表格:
單元格個數 (行 / 列) |
對應的設置大小 (dp) ( minWidth / minHeight ) |
---|---|
1 | 40dp |
2 | 110dp |
3 | 180dp |
4 | 250dp |
… | … |
n | 70 × n − 30 |
詳細計算minWidth和minHeight,要計算各個區域的大小。以下圖爲例:
計算結果:
minWidth = 144dp + (2 × 8dp) + (2 × 56dp) = 272dp
minHeight = 48dp + (2 × 4dp) = 56dp
3.3 App Widget支持的佈局和控件
Widget並不支持所有的佈局和控件,而僅僅只是支持Android佈局和控件的一個子集。
(01) App Widget支持的佈局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
(02) App Widget支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
4 App Widget應用實例
應用實例需求:建立一個Widget示例,要求Widget能被添加到主屏中,widget包含3個成分:文本、按鈕和圖片。文本要求:顯示提示信息;按鈕要求:點擊按鈕,彈出一個Toast提示框;圖片要求:每個5秒隨機更新一張圖片。
第1步 新建Android工程
新建空白的Android工程,即不需要在建立Activity子類。
第2步 編輯manifest
修改AndroidManifest.xml,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.skywang.widget" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <!-- 聲明widget對應的AppWidgetProvider --> <receiver android:name=".ExampleAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="com.skywang.widget.UPDATE_ALL"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/example_appwidget_info" /> </receiver> <service android:name=".ExampleAppWidgetService" > <intent-filter> <action android:name="android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE" /> </intent-filter> </service> </application> </manifest>
說明:
(01) ExampleAppWidgetProvider是繼承於的AppWidgetProvider類,用來響應widget的添加、刪除、更新等操作。
(02) android.appwidget.action.APPWIDGET_UPDATE,必須要顯示聲明的action!因爲所有的widget的廣播都是通過它來發送的;要接收widget的添加、刪除等廣播,就必須包含它。
(03) action android:name="com.skywang.widget.UPDATE_ALL,這是我自己添加了,是爲了接收服務所發送的更新圖片的廣播。
(04) <meta-data> 指定了 AppWidgetProviderInfo 對應的資源文件
android:name -- 指定metadata名,通過android.appwidget.provider來辨別data。
android:resource -- 指定 AppWidgetProviderInfo 對應的資源路徑。即,xml/example_appwidget_info.xml。
(05) ExampleAppWidgetService 是用於更新widget中的圖片的服務。
(06) android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE 用於啓動服務的action。
第3步 編輯AppWidgetProviderInfo對應的資源文件
在當前工程下新建xml目錄(若xml目錄不存在的話);並在xml目錄下新建example_appwidget_info.xml文件。xml文件內容如下:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="180dp" android:minHeight="180dp" android:previewImage="@drawable/preview" android:initialLayout="@layout/example_appwidget" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen|keyguard"> <!-- android:minWidth : 最小寬度 android:minHeight : 最小高度 android:updatePeriodMillis : 更新widget的時間間隔(ms),"86400000"爲1個小時 android:previewImage : 預覽圖片 android:initialLayout : 加載到桌面時對應的佈局文件 android:resizeMode : widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以豎直拉伸 android:widgetCategory : widget可以被顯示的位置。home_screen表示可以將widget添加到桌面,keyguard表示widget可以被添加到鎖屏界面。 android:initialKeyguardLayout : 加載到鎖屏界面時對應的佈局文件 --> </appwidget-provider>
說明:
(01) android:previewImage,用於指定預覽圖片。即搜索到widget時,查看到的圖片。若沒有設置的話,系統爲指定一張默認圖片。
(02) android:updatePeriodMillis 更新widget的時間間隔(ms)。在實際測試中,發現設置android:updatePeriodMillis的值爲5秒時,不管用!跟蹤android源代碼,發現:
當android:updatePeriodMillis的值小於30分鐘時,會被設置爲30分鐘。也就意味着,即使將它的值設爲5秒,實際上也是30分鐘之後才更新。因此,我們若向動態的更新widget的某組件,最好通過service、AlarmManager、Timer等方式;本文采用的是service。
android源碼中widget服務文件:frameworks/base/services/java/com/android/server/AppWidgetService.java
與 android:updatePeriodMillis相關代碼如下:
... private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes ... void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { ... // 獲取updatePeriodMillis的值 long period = p.info.updatePeriodMillis; // 當updatePeriodMillis小於30分鐘時,設爲它爲30分鐘 if (period < MIN_UPDATE_PERIOD) { period = MIN_UPDATE_PERIOD; } mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + period, period, p.broadcast); ... }
第4步 編輯example_appwidget.xml等資源文件
新建layout/example_appwidget.xml,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="HomeScreen Widget" /> <Button android:id="@+id/btn_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show" /> </LinearLayout> <ImageView android:id="@+id/iv_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/> </LinearLayout>
說明:
(01) example_appwidget佈局中,包含了“1個文本,1個按鈕和1個圖片控件”。
點擊下載:本工程中需要用到的圖片
將所有的圖片放到drawable目錄中。這裏的drawable廣義的,指工程實際用到的圖片所在目錄;例如,我自己的是放在drawabld-hdmi中。
第5步 編輯ExampleAppWidgetProvider.java
在當前工程下,新建ExampleAppWidgetProvider.java,代碼如下:
package com.skywang.widget; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.net.Uri; import android.widget.RemoteViews; import android.widget.Toast; import android.util.Log; import java.util.Set; import java.util.HashSet; import java.util.Iterator; /* * @author : skywang <[email protected]> * description : 提供App Widget */ public class ExampleAppWidgetProvider extends AppWidgetProvider { private static final String TAG = "ExampleAppWidgetProvider"; private boolean DEBUG = false; // 啓動ExampleAppWidgetService服務對應的action private final Intent EXAMPLE_SERVICE_INTENT = new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE"); // 更新 widget 的廣播對應的action private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL"; // 保存 widget 的id的HashSet,每新建一個 widget 都會爲該 widget 分配一個 id。 private static Set idsSet = new HashSet(); // 按鈕信息 private static final int BUTTON_SHOW = 1; // 圖片數組 private static final int[] ARR_IMAGES = { R.drawable.sample_0, R.drawable.sample_1, R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4, R.drawable.sample_5, R.drawable.sample_6, R.drawable.sample_7, }; // onUpdate() 在更新 widget 時,被執行, @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "onUpdate(): appWidgetIds.length="+appWidgetIds.length); // 每次 widget 被創建時,對應的將widget的id添加到set中 for (int appWidgetId : appWidgetIds) { idsSet.add(Integer.valueOf(appWidgetId)); } prtSet(); } // 當 widget 被初次添加 或者 當 widget 的大小被改變時,被調用 @Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { Log.d(TAG, "onAppWidgetOptionsChanged"); super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); } // widget被刪除時調用 @Override public void onDeleted(Context context, int[] appWidgetIds) { Log.d(TAG, "onDeleted(): appWidgetIds.length="+appWidgetIds.length); // 當 widget 被刪除時,對應的刪除set中保存的widget的id for (int appWidgetId : appWidgetIds) { idsSet.remove(Integer.valueOf(appWidgetId)); } prtSet(); super.onDeleted(context, appWidgetIds); } // 第一個widget被創建時調用 @Override public void onEnabled(Context context) { Log.d(TAG, "onEnabled"); // 在第一個 widget 被創建時,開啓服務 context.startService(EXAMPLE_SERVICE_INTENT); super.onEnabled(context); } // 最後一個widget被刪除時調用 @Override public void onDisabled(Context context) { Log.d(TAG, "onDisabled"); // 在最後一個 widget 被刪除時,終止服務 context.stopService(EXAMPLE_SERVICE_INTENT); super.onDisabled(context); } // 接收廣播的回調函數 @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); Log.d(TAG, "OnReceive:Action: " + action); if (ACTION_UPDATE_ALL.equals(action)) { // “更新”廣播 updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet); } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { // “按鈕點擊”廣播 Uri data = intent.getData(); int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); if (buttonId == BUTTON_SHOW) { Log.d(TAG, "Button wifi clicked"); Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT).show(); } } super.onReceive(context, intent); } // 更新所有的 widget private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) { Log.d(TAG, "updateAllAppWidgets(): size="+set.size()); // widget 的id int appID; // 迭代器,用於遍歷所有保存的widget的id Iterator it = set.iterator(); while (it.hasNext()) { appID = ((Integer)it.next()).intValue(); // 隨機獲取一張圖片 int index = (new java.util.Random().nextInt(ARR_IMAGES.length)); if (DEBUG) Log.d(TAG, "onUpdate(): index="+index); // 獲取 example_appwidget.xml 對應的RemoteViews RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); // 設置顯示圖片 remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]); // 設置點擊按鈕對應的PendingIntent:即點擊按鈕時,發送廣播。 remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context, BUTTON_SHOW)); // 更新 widget appWidgetManager.updateAppWidget(appID, remoteView); } } private PendingIntent getPendingIntent(Context context, int buttonId) { Intent intent = new Intent(); intent.setClass(context, ExampleAppWidgetProvider.class); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); intent.setData(Uri.parse("custom:" + buttonId)); PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0 ); return pi; } // 調試用:遍歷set private void prtSet() { if (DEBUG) { int index = 0; int size = idsSet.size(); Iterator it = idsSet.iterator(); Log.d(TAG, "total:"+size); while (it.hasNext()) { Log.d(TAG, index + " -- " + ((Integer)it.next()).intValue()); } } } }
說明:
(01) 當我們創建第一個widget到桌面時,會執行onEnabled()。在onEnabled()中通過 context.startService(EXAMPLE_SERVICE_INTENT) 啓動服務ExampleAppWidgetService。服務的作用就是每隔5秒發送一個ACTION_UPDATE_ALL廣播給我們,用於更新widget中的圖片。
僅僅當我們創建第一個widget時纔會啓動服務,因爲onEnabled()只會在第一個widget被創建時才執行。
(02) 當我們刪除最後一個widget到桌面時,會執行onDisabled()。在onDisabled()中通過 context.stopService(EXAMPLE_SERVICE_INTENT) 終止服務ExampleAppWidgetService。
僅僅當我們刪除最後一個widget時纔會終止服務,因爲onDisabled()只會在最後一個widget被刪除時才執行。
(03) 本工程中,每添加一個widget都會執行onUpdate()。例外情況:在定義android:configure的前提下,第一次添加widget時不會執行onUpdate(),而是執行android:configure中定義的activity。
(04) onReceive()中,處理兩個廣播:更新桌面的widget 以及 響應按鈕點擊廣播。
當收到ACTION_UPDATE_ALL廣播時,調用updateAllAppWidgets()來更新所有的widget。
當收到的廣播的categery爲Intent.CATEGORY_ALTERNATIVE,並且scheme爲BUTTON_SHOW時,對應是按鈕點擊事件。按鈕的監聽是在updateAllAppWidgets()中註冊的。
第6步 編輯ExampleAppWidgetService.java
在當前工程下,新建ExampleAppWidgetService.java,代碼如下:
package com.skywang.widget; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log; /* * @author : skywang <[email protected]> * description : 週期性更新AppWidget的服務 */ public class ExampleAppWidgetService extends Service { private static final String TAG="ExampleAppWidgetService"; // 更新 widget 的廣播對應的action private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL"; // 週期性更新 widget 的週期 private static final int UPDATE_TIME = 5000; // 週期性更新 widget 的線程 private UpdateThread mUpdateThread; private Context mContext; // 更新週期的計數 private int count=0; @Override public void onCreate() { // 創建並開啓線程UpdateThread mUpdateThread = new UpdateThread(); mUpdateThread.start(); mContext = this.getApplicationContext(); super.onCreate(); } @Override public void onDestroy(){ // 中斷線程,即結束線程。 if (mUpdateThread != null) { mUpdateThread.interrupt(); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } /* * 服務開始時,即調用startService()時,onStartCommand()被執行。 */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand"); super.onStartCommand(intent, flags, startId); return START_STICKY; } private class UpdateThread extends Thread { @Override public void run() { super.run(); try { count = 0; while (true) { Log.d(TAG, "run ... count:"+count); count++; Intent updateIntent=new Intent(ACTION_UPDATE_ALL); mContext.sendBroadcast(updateIntent); Thread.sleep(UPDATE_TIME); } } catch (InterruptedException e) { // 將 InterruptedException 定義在while循環之外,意味着拋出 InterruptedException 異常時,終止線程。 e.printStackTrace(); } } } }
說明:
(01) onCreate() 在創建服務時被執行。它的作用是創建並啓動線程UpdateThread()。
(02) onDestroy() 在銷燬服務時被執行。它的作用是註銷線程UpdateThread()。
(03) 服務UpdateThread 每隔5秒,發送1個廣播ACTION_UPDATE_ALL。廣播ACTION_UPDATE_ALL在ExampleAppWidgetProvider被處理:用來更新widget中的圖片。
點擊下載:源代碼
點擊查看:skywang博客索引
widget在添加到桌面前的效果圖:
widget在添加到桌面後的效果圖:
轉自:http://www.cnblogs.com/skywang12345/p/3158310.html