Appwidget就是手機應用中常常放在桌面(即home)上的一些應用程序,比如說鬧鍾等。這種應用程序的特點是它上面顯示的內容能夠根據系統內部的數據進行更新,不需要我們進入到程序的內部去,比如說鬧鍾指針的擺動等。本節內容就簡單的介紹下實現這種功能所用到的appwidget技術,通過3個例子由淺入深來學會使用它。參考資料是mars的教程。
實驗基礎:
自己實現一個AppWidget的步驟如下:
1. 在src目錄下新建一個名為xml的文件夾,在該文件夾下新建一個xml文件,該xml文件的根標簽為appwidget-provider. 該xml文件主要是對所建立的appwidget的一個屬性設置,其中比較常見的屬性有appwidget更新的時間,其初始的布局文件等等。
2. 在src下的layout文件夾下新建一個xml文件夾,然后在xml文件夾新建一個布局文件,該布局文件就是第一步中需要加載的appwidget初始化時所需的布局文件,因此該xml文件的根標簽為與layout有關,比如說LinearLayout類型等。
3. 在src的包目錄下新建一個java文件,該文件為實現所需建立的appwidget全部功能,其中比較重要的功能是接收廣播消息來更新appwidget的內容。該java文件時一個類,繼承AppWidgetProvider這個類,復寫其中的onDeleted,onDisabled,onEnabled,onReceive,onUpdate等方法。其中幾個方法都是與AppWidgetProvider的生命周期有關的。其中onDeleted()方法是當appwidget刪除時被執行,onDisabled()是當最后一個appwidget被刪除時執行,onEnabled()為當第一個appwidget被建立時執行,onReceive()為當接收到了相應的廣播信息后被執行(在每次添加或者刪除appwidget時都會執行,且在其它方法執行的前面該方法也會被執行,其實本質上該方法不是AppWidgetProvider這個類的生命周期函數);onUpdate()為到達了appwidget的更新時間或者一個appwidget被建立時執行。
在android4.1模擬器中,在桌面上添加一個appwidget的方法是在WIDGETS欄目(和APPS欄目並列)中選中所需要添加的appwidget,並按住鼠標不動,一會兒會出現手機桌面空白處,放在自己想放的位置即可。在該模擬器中刪除一個appwidge的方法是選中該appwidget一會兒,然后向屏幕上方拖動,屏幕上方會出現XRemove字樣,放進去即可。
appwidget中本身里面就有一個程序(有個activity),但是我們在桌面上添加一個appwidget后也相當於一個程序,這2個程序本身不是在同一個進程當中,而是在各自單獨的進程中。
例一:
實驗說明:
這個例子實現一個最簡單的appwidget,即我們的appwidget只有一個按鈕,按鈕上面寫着“我的常用密碼字樣”,沒有其它功能,呵呵。然后我們在appwidget的java類的程序中,每個生命周期函數都在后台打印出一句話,內容是與該生命周期函數名相對應的。
實驗結果:
往桌面添加自己創建的一個appwidget效果如下所示:
該實驗室先后在桌面上放2個appwidget,然后依次刪除2個appwidget,則程序后台的輸出結果如下所示:
實驗主要代碼及注釋(附錄有實驗工程code下載鏈接):
MainActivity.java不用更改任何代碼,采用默認的就行了,這里就不貼出來了。
my_passward_provider.java(Appwidget功能實現):
package com.example.appwidget1; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; public class my_password_provider extends AppWidgetProvider { @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO Auto-generated method stub System.out.println("appwidget--->onDeleted()"); super.onDeleted(context, appWidgetIds); } @Override public void onDisabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onDisabled()"); super.onDisabled(context); } @Override public void onEnabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onEnabled()"); super.onEnabled(context); } @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub System.out.println("appwidget--->onReceive()"); super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO Auto-generated method stub System.out.println("appwidget--->onUpdate()"); super.onUpdate(context, appWidgetManager, appWidgetIds); } }
res/xml/my_password.xml:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="200dp" android:minHeight="100dp" android:updatePeriodMillis="600000" android:initialLayout="@layout/my_password_initillayout" > </appwidget-provider>
res/layout/my_password_initilayout.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" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_appwidget" /> </LinearLayout>
例二:
該實驗的目的主要是學會在appwidget中使用PendingIntent和RemoteViews這2個類,並最終對它們有一定的了解。
實驗說明:
這個例子在上面的例子中多增加了一個功能,即當我們把appwidget添加到桌面上的時候(上面那個例子是個按鈕),單擊這個按鈕,這個時候程序會從home界面跳轉到其它activity界面。那么怎么實現監聽appwidget上的按鈕控件呢?這里實現該過程與在activity中的方法不同。在此之前,我們需要了解2個概念。
PendingIntent:
PendingIntent與以前我們的Intent不同,以前我們新建一個intent時,立刻就用它啟動一個activity,或者啟動一個service,亦或是發送一個broadcast。這里我們新建一個PendingIntent后,按照字面意思並不馬上使用它,而是當我們需要使用它的時候再啟動,比如說當某一事件需要響應時,我們這時候可以使用建立好了的PendingIntent了。一個PendingIntent中包含了一個intent。Mars老師把PendingIntent比作成三國中的“精囊妙計”,從下面mars老師的2張示意圖中可以更深一步了解PendingIntent。
建立PendingIntent示意圖:
響應PendingIntent示意圖:
RemoteViews:
RemoteView代表了與調用它的那個activity不在同一個進程的view,因此叫做”遠程view”。在appWidget中使用這個類就可以實現當對appwidget的那個進程進行操作時響應其它進程中的activity。而RemoteViews則表示了一系列的RemoteView。
實驗結果與例一一樣,只不過是在單擊appwidget上的按鈕時,會自動跳轉到相應的activity上。這里就不截圖看效果了。
實驗主要部分及代碼(附錄有實驗工程code下載鏈接):
其它部分與例一都差不多,只不過是在appwidget生命周期的onUpdate()函數不同,下面是對應其文件的java代碼:
package com.example.appwidget1; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; public class my_password_provider extends AppWidgetProvider { @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO Auto-generated method stub System.out.println("appwidget--->onDeleted()"); super.onDeleted(context, appWidgetIds); } @Override public void onDisabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onDisabled()"); super.onDisabled(context); } @Override public void onEnabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onEnabled()"); super.onEnabled(context); } @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub System.out.println("appwidget--->onReceive()"); super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO Auto-generated method stub // System.out.println("appwidget--->onUpdate()"); for(int i = 0; i < appWidgetIds.length; i++) { System.out.println(appWidgetIds[i]); //該構造函數之間把跳轉的2個activity給聯系起來了 Intent intent = new Intent(context, MyPasswordActivity.class); //創建一個PendingIntent PendingIntent pendint_intent = PendingIntent.getActivity(context, 0, intent, 0); //創建一個remoteview對象,第2個參數為appwidget的初始布局文件 RemoteViews remote_views = new RemoteViews(context.getPackageName(), R.layout.my_password_initillayout); //為RemoteViews中的button按鈕添加監聽器,第二個參數為PendingIntent類型,當事件觸發時才執行 remote_views.setOnClickPendingIntent(R.id.my_password, pendint_intent); //更新appwidget appWidgetManager.updateAppWidget(appWidgetIds[i], remote_views); } super.onUpdate(context, appWidgetManager, appWidgetIds); } }
例三:
實驗說明:
這個例子在例二的基礎上增加一些功能,即利用appwidget的onUpdate()方法中發送廣播信息,然后在onReceive()方法中來接收廣播消息,從而來更改appwidget的外觀,這里是更改它的圖片和文本顯示。
這個例子不像上面那樣采用getActivity來創建PendingI,而是采用的getBroadcast,因為這里需要的是發送廣播信息,而不是跳轉到另一個activity。
同樣的,需要啊manifest.xml文件中隊appwidget這個類來注冊它的接收器,action過濾時采用的是自己定義的action,名字可以自己隨便取,保證不和系統提供的action名字相同即可,該程序中采用的是"my.action.APPWIDGET_UPDATE"這個名字。
Appwidget中有1個按鈕,一個ImageView,一個TextView。程序實現的是這么一個功能:appwidget在桌面被創建時,imageview和textview都有各自的內容,當按鈕按下時,這2個控件的內容都會發生變化,這種變化都是通過RemotViews的方法實現的。其中imageview是用的setImageViewResource()函數,textview是用的setTextViewText()函數。
從上面的解釋可以看到,為什么在同一個類(這里指繼承AppWidgetProvide的那個類)中,從onUpdate()函數發送出去的廣播能夠在onReceiver()函數里接收呢?這是因為AppWidgetProvider的其它4個生命周期函數的執行都是由onReceiver分發下去的。由mars老師提供的這張示意圖可以看出它們之間的關系:
實驗結果:
桌面上創建appwidget時顯示如下:
單擊按鈕后,顯示如下:
實驗主要部分代碼即注釋(附錄有實驗工程code下載鏈接):
AndriodManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.appwidget1" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".my_password_provider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <intent-filter> <action android:name="my.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_password" /> </receiver> <activity android:name=".MyPasswordActivity" android:label="@string/title_activity_my_password" > </activity> </application> </manifest>
my_password_provider.java(里面有appwidget的生命周期函數):
package com.example.appwidget1; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.widget.RemoteViews; public class my_password_provider extends AppWidgetProvider { private static final String MY_ACTION = "my.action.APPWIDGET_UPDATE"; @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO Auto-generated method stub System.out.println("appwidget--->onDeleted()"); super.onDeleted(context, appWidgetIds); } @Override public void onDisabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onDisabled()"); super.onDisabled(context); } @Override public void onEnabled(Context context) { // TODO Auto-generated method stub System.out.println("appwidget--->onEnabled()"); super.onEnabled(context); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(MY_ACTION.equals(action)) { RemoteViews remote_views = new RemoteViews(context.getPackageName(), R.layout.my_password_initillayout); //更改appwidget界面中的圖片 remote_views.setImageViewResource(R.id.my_image, R.drawable.no); //更改appwidget界面中textview中的文字內容 remote_views.setTextViewText(R.id.my_text, "no"); remote_views.setTextColor(R.id.my_text, Color.RED); //獲得本context的AppWidgetManager AppWidgetManager appwidget_manager = AppWidgetManager.getInstance(context); //新建一個ComponentName,該ComponentName指的是針對appwidget整體而言的;而RemoteViews是針對appwidget //中各個部件之和而言的,這兩者有些區別 ComponentName component_name = new ComponentName(context, my_password_provider.class); //上面2句代碼是為下面更新appwidget做准備的 appwidget_manager.updateAppWidget(component_name, remote_views); } else super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO Auto-generated method stub Intent intent = new Intent(); intent.setAction(MY_ACTION); //以發送廣播消息的方式創建PendingIntent. PendingIntent pending_intent = PendingIntent.getBroadcast(context, 0, intent, 0); //創建一個remoteviews,其布局文件為appwidget的初始布局文件 RemoteViews remote_views = new RemoteViews(context.getPackageName(), R.layout.my_password_initillayout); //為按鈕添加監聽器 remote_views.setOnClickPendingIntent(R.id.my_password, pending_intent); //更新appwidget appWidgetManager.updateAppWidget(appWidgetIds, remote_views); super.onUpdate(context, appWidgetManager, appWidgetIds); } }
res/layout/my_password_initillayout.xml(appwidget的布局文件):
<?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" > <Button android:id="@+id/my_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_appwidget" /> <ImageView android:id="@+id/my_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dip" android:src="@drawable/yes" /> <TextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/yes" android:textColor="#0000ff" /> </LinearLayout>
Res/xml/my_password.xml(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" > <Button android:id="@+id/my_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_appwidget" /> <ImageView android:id="@+id/my_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dip" android:src="@drawable/yes" /> <TextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/yes" android:textColor="#0000ff" /> </LinearLayout>
總結:通過這幾個例子,可以初步了解創建一個appwidget的整個流程,並且學會了簡單的是appwidget和其它的進程間進行通信。
參考資料:
http://www.mars-droid.com/bbs/forum.php
附錄: