AppWidget基礎小結


 由於上傳貼圖比較麻煩,為此特將文檔的pdf版本放在此處供下載 http://files.cnblogs.com/franksunny/AppWidget%E5%9F%BA%E7%A1%80%E5%B0%8F%E7%BB%93.pdf

AppWidget基礎小結

 

小小嘗試了下AppWidget,參考網上資料和demo小測,得出如下基礎小結。

AppWidget是基於BroadcastReceiver組件機制再開發而來的,為此他首先需要遵循BroadcastReceiver的開發流程進行開發,其次是根據他自身提供的AppWidgetProvider、AppWidgetProvderInfo、AppWidgetManger來進行開發。為此要開發一個AppWidget大致流程如下:

一、注冊部件

在工程manifest中注冊用於開發AppWidget的receiver

<receiver android:name="AppWidget">

       <intent-filter>

              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action>

       </intent-filter>

       <meta-data android:name="android.appwidget.provider"

              android:resource="@xml/appwidget01" />

<intent-filter>

              <action android:name="com.qlf.appWidgetUpdate"></action>

       </intent-filter>

</receiver>

其中紅色部分的代碼是固定要寫的,綠色部分代碼是根據用戶自己開發情況來設置的。

通常一個應用程序往往只需要添加一個桌面部件就夠了,為此如上述receiver這一項沒有給出桌面部件的名稱和圖標,系統會默認用application標簽中的名稱和標簽為widgets列表中的名稱和圖標。假如我們需要更加個性化,或者添加多種類型的桌面部件,那么可以根據需求完善桌面部件在桌面部件列表中的圖標和名字,參考如下

<receiver android:name="AppWidget"

    android:label="test App widget"

    android:icon="@drawable/about_title">

一個桌面部件,需要一個receiver注冊,多個就注冊多個receiver。具體可以參考網上小結http://txlong-onz.iteye.com/blog/1143532

AppWidget是派生自AppWidgetProvider的自定義類,主要負責接收通知及其處理。該類派生自BroadcastReceiver,通過源碼可以得知,其主要對BroadcastReceiver的onReceive函數做了細分,衍生出onEnabled(創建該類第一個桌面部件時調用)、onUpdate(單個該類桌面部件添加時或所設時間間隔過期時調用)、onDeleted(單個該類桌面部件刪除時調用)和onDisabled(手機該類最后一個部件被刪除后調用),后面再詳述。

meta-data標簽中的android:resource元素指定的xml資源,對應就是描述桌面部件的大小、更新頻率和配置器(activity)等的AppWidgetProvderInfo對象,其資源文件被要求放置於res/xml文件夾中,我們拿一個實例來說明下

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="150dp"

    android:minHeight="120dp"

    android:updatePeriodMillis="0"

    android:initialLayout="@layout/ appwidgetlayout"

    android:configure="com.androidbook.BDayWidget.ConfigureBDayWidgetActivity"

    >

</appwidget-provider>

以上包含了SDK 3.0之前的appwidget-provider標簽所有的元素。其中的minWidth和minHeight為桌面部件需要的寬度和高度,據書上所說,桌面被分割高寬都為為74dp的單元格,所以部件的高寬最好設置為(number of cells * 74) - 2,因為最后部件放置時,桌面會將部件大小調整為適合整數個單元格大小。

updatePeriodMillis為桌面部件更新的時間間隔,單位是毫秒,表示多久時間調用一次onUpdate函數。google建議這個值設置為最小1小時,否則設備會被頻繁喚醒,而且還建議如果要設置短時的更新時間,推薦使用AlarmManager類中的工具來調用onUpdate。這里該值設置為0,則表示不進行對onUpdate的更新調用,只在添加到桌面時調用。

initialLayout為桌面部件加載到桌面后的布局,與其它布局類似,只是因為桌面部件布局需要使用跨進程的RemoteViews才能訪問,而RemoteViews所支持的布局和控件是有限的,具體可以查看android在線幫助Dev-Guide下的App Widgets條目。在這里,initialLayout指定為appwidgetlayout.xml。其具體內容如下所示

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

       android:layout_width="fill_parent"

       android:layout_height="fill_parent">

       <TextView android:id="@+id/txtapp"

           android:text="test"

           android:layout_width="wrap_content"

              android:layout_height="wrap_content"

              android:background="#ffffff"></TextView>

       <Button

           android:id="@+id/btnSend"

           android:layout_width="wrap_content"

           android:layout_height="wrap_content"

           android:text="Send"></Button>

</LinearLayout>

即一個文本框和一個按鈕組成。

Configure為桌面部件的配置器,主要負責配置部件實例,是一個Activity。

在3.0以后還提供諸如previewImage、autoAdvanceViewId、resizeMode,在這里就不做展開了。

二、編寫派生自AppWidgetProvider的自定義類

上面注冊了桌面部件,命名了一個名為“AppWidget”的AppWidgetProvider派生類,這個類的功能就是針對每一個桌面部件實例的消息處理類。正如上面所述,其繼承自BroadcastReceiver,除了onReceive函數是通用性處理外,其它幾個函數都是對應有固定的ACTION通知的。這部分還是直接通過代碼說明的方便。

public class AppWidget extends AppWidgetProvider

{

 

       private final String broadCastString = "com.qlf.appWidgetUpdate";

       private final String extr = "com_extr";

      

       /**

        * 刪除一個AppWidget時調用

        * */

       @Override

       public void onDeleted(Context context, int[] appWidgetIds){

              super.onDeleted(context, appWidgetIds);

       }

 

       /**

        * 最后一個appWidget被刪除時調用

        * */

       @Override

       public void onDisabled(Context context){

              super.onDisabled(context);

       }

 

       /**

        * AppWidget的第一個實例被創建時調用

        * */

       @Override

       public void onEnabled(Context context){

              super.onEnabled(context);

       }

 

       /**

        * 到達指定的更新時間或者當用戶向桌面添加AppWidget時被調用

        * */

       @Override

       public void onUpdate(Context context, AppWidgetManager appWidgetManager,

                     int[] appWidgetIds){

                     //創建一個Intent對象

                     Intent intent = new Intent();

                     intent.setAction(broadCastString);

                     intent.putExtra(extr, 0);

                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

                    

                     //這里是getBroadcast,當然也可以是Activity、Receiver等

                     PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetIds[0], intent, PendingIntent.FLAG_UPDATE_CURRENT);

                    

                     //通過RemoteViews添加單擊事件

                     RemoteViews remoteViews  = new RemoteViews(context.getPackageName(),R.layout.appwidgetlayout);                  

                     remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

                    

                     //更新Appwidget

                     appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);        

       }

      

       /**

        * 接受廣播事件

        * */

       @Override

       public void onReceive(Context context, Intent intent){

              if (intent.getAction().equals(broadCastString)){               

                     RemoteViews remoteViews  = new RemoteViews(context.getPackageName(),R.layout.appwidgetlayout);

                     //獲得appwidget管理實例,用於管理appwidget以便進行更新操作

                     AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

                     int mTemp = intent.getIntExtra(extr, 0);

                    

                     if(mTemp == 0){

                            int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

                            if (appWidgetIds != null && appWidgetIds.length > 0) {  

                                  

                                   //創建一個Intent對象

                                   Intent vintent = new Intent();

                                   vintent.setAction(broadCastString);

                                   vintent.putExtra(extr, 1);

                                   vintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

                                  

                                   PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetIds[0], vintent, PendingIntent.FLAG_UPDATE_CURRENT);

                                   remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

                                   remoteViews.setTextViewText(R.id.btnSend, "hihi"); 

 

                                   //更新appwidget

                                   appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

                            }

                     }else{                         

                   int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

                   if (appWidgetIds != null && appWidgetIds.length > 0) {

                          //創建一個Intent對象

                          Intent vintent = new Intent();

                          vintent.setAction(broadCastString);

                          vintent.putExtra(extr, 0);

                          vintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

                         

                          PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetIds[0], vintent, PendingIntent.FLAG_UPDATE_CURRENT);

                          remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

                          remoteViews.setTextViewText(R.id.btnSend, "send");

                         

                          //更新appwidget

                          appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

                   }

                     }

              }

              super.onReceive(context, intent);

       }

}

上述代碼是對網上的一個整理的不錯的例子進行每個widget桌面實例單獨控制的修改而來,以避免在桌面上添加了多個同類桌面部件后,點擊其中一個全部都被修改的問題。該例子位於http://www.cnblogs.com/qianlifeng/archive/2011/03/26/1996407.html

關於AppWidgetManager和RemoteViews

上述代碼簡單演示了AppWidgetProvider的消息處理流程,我們可以看到其內部的調用都是通過AppWidgetManager和RemoteViews來實現的。不像一般程序開發,一個實例是有一個類的對象化來實現的,在appWidget中,每一個具體放置於桌面上的部件,並不是某一個類的對象化,這些桌面部件都是通過AppWidgetManager這個客戶端的中介與真實的AppWidgetService交互(其間采用了基於AIDL技術的C/S機制),從應用程序開發的角度來上說,我們可以理解為使用AppWidgetManager通過RemoteViews為信息載體來實現對每個桌面部件的控制,最主要的三個函數為

public void updateAppWidget(int appWidgetId, RemoteViews views)

public void updateAppWidget(int[] appWidgetIds, RemoteViews views)

public void updateAppWidget(ComponentName provider, RemoteViews views)

前面兩個根據源碼可以歸為一類,即將RemoteViews的更改派發給指定的一個或多個appWidget實例,而第三個函數用於更新一類appWidget的所有實例。

PendingIntent注意

在這里需要補充說明的是,PendingIntent作為對Intent的封裝,被稱為掛起的Intent,有一個很特殊的現象,當intent的內容除了Extra內容不同,其它都一致時,會被認為是同一個Intent,為此我們要通過Intent傳遞信息,必須從獲得PendingIntent三個函數入手:

public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags)

public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)

public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags)

requestCode是一個目前沒有使用的特殊值,網上通常采用默認的0值,這里可以通過給int變量賦不同值來達到區分相同內容Intent的目的。在appWidget中,這個值用每個桌面實例的appWidgetId最好了。不過我有疑問的是,這個requestCode不知道在哪里可以通過什么方式獲取不?我是沒有找到這個值的獲取方式,為此雖然這個值設置為appWidgetId,但是還是需要通過Intent的Extra信息來傳遞跟桌面每個部件對應id值。

同時上述三函數的調用中會存在Extra數據不被傳遞的問題,這個問題,可以通過修改第四個參數flags來實現,這個值可以有如下四種取值,簡單描述如下:

int FLAG_ONE_SHOT(0x40000000):該PendingIntent只能用一次,在send()方法執行后,自動取消;

int FLAG_NO_CREATE(0x20000000):如果該PendingIntent不存在,直接返回null而不是創建一個PendingIntent;

int FLAG_CANCEL_CURRENT(0x10000000):如果該PendingIntent已經存在,則在生成新的之前取消當前的;

int FLAG_UPDATE_CURRENT(0x8000000):如果該PendingIntent已經存在,則用新傳入的Intent更新當前的數據。

為了用新的Intent替換PendingIntent中舊的Intent,保持最新意圖得到執行,為此我們在這里建議使用FLAG_UPDATE_CURRENT。

三、編寫部件配置器

簡單的桌面部件不需要編寫部件配置,不過一旦在AppWidgetProvderInfo信息中添加了配置器,那么就需要編寫該Activity,這個類的寫法在sdk中分步驟描述得很詳細,我就不做翻譯了,直接貼上示例代碼如下

First, get the App Widget ID from the Intent that launched the Activity:

Intent intent = getIntent();

Bundle extras = intent.getExtras();

if (extras != null) {

    mAppWidgetId = extras.getInt(

            AppWidgetManager.EXTRA_APPWIDGET_ID,

            AppWidgetManager.INVALID_APPWIDGET_ID);

}

2.Perform your App Widget configuration.

3.When the configuration is complete, get an instance of the AppWidgetManager by calling getInstance(Context):

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

4.Update the App Widget with a RemoteViews layout by calling updateAppWidget(int, RemoteViews):

RemoteViews views = new RemoteViews(context.getPackageName(),

R.layout.example_appwidget);

appWidgetManager.updateAppWidget(mAppWidgetId, views);

Finally, create the return Intent, set it with the Activity result, and finish the Activity:

Intent resultValue = new Intent();

resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);

setResult(RESULT_OK, resultValue);

finish();

四、幾個小問題

上述是涉及開發方面的問題,個人在親測時還發現如下幾個問題,有心人看看是否為共性問題,網上暫時也沒有搜到相關資料

程序安裝到SD卡后選擇窗口小部件列表中沒有對應的項

由於考慮到我的手機內存不多,所以將程序安裝到SD卡,結果在桌面長按,彈出類似如下的“添加到桌面”對話框,選中widget對應的“小工具”,結果在“選擇窗口小部件”列表對話框沒有將安裝到SD卡的程序帶有的widget顯示出來。

  

而一旦在應用程序管理中將程序移動到手機內存,則在上述“選擇窗口小部件”列表對話框中就會顯示該程序帶有的appWidget部件了。

Android中除了appWidget外,還有一些應用是不能安裝到SD卡的,比如Live Folders(活動文件夾)、Input Method Engines(輸入法引擎)、Alarm Services(鬧鈴提醒服務)。在SDK中沒有找到相關信息,不過網上有篇博文提到類似信息,感興趣可以打開如下鏈接:http://blog.csdn.net/raindrophust/article/details/6369810

程序覆蓋安裝后,會對所有以加載的部件調用一次onUpdate操作

因為用戶可以在桌面上添加多個同類窗口部件,為此我們最好做到每個窗口部件獨立控制,而不是在一個窗口部件上點擊,導致其他所有同類窗口部件跟着變化,假如代碼起初按照二中的代碼所寫,那么不遭遇程序覆蓋安裝,是不會有問題的,但是程序一旦重新覆蓋安裝,那么安裝成功后,桌面會調一次onUpdate函數,其中的第三個參數appWidgetIds是所有該類部件的實例ID數組,而不是像添加部件時一樣是單個實例ID,所以會發生重新覆蓋安裝后,部件又跟着聯動的現象。

為了避免上述問題,最簡單的是修改onUpdate函數和onReceive函數,具體如下

       /**

        * 到達指定的更新時間或者當用戶向桌面添加AppWidget時被調用

        * */

       @Override

       public void onUpdate(Context context, AppWidgetManager appWidgetManager,

                     int[] appWidgetIds){

              if (appWidgetIds != null && appWidgetIds.length > 0) {  

                     Mylog("onUpdate appWidgetIds length" + appWidgetIds.length);

                     for(int appWidgetId:appWidgetIds){

                            //創建一個Intent對象

                            Intent intent = new Intent();

                            intent.setAction(broadCastString);

                            intent.putExtra(extr, 0);

                            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

 

                            //這里是getBroadcast,當然也可以是Activity、Receiver等

                            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);

 

                            //通過RemoteViews添加單擊事件

                            RemoteViews remoteViews  = new RemoteViews(context.getPackageName(),R.layout.appwidgetlayout);                    

                            remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

 

                            //更新Appwidget

                            appWidgetManager.updateAppWidget(appWidgetId, remoteViews);  

                     }

              }

       }

      

       /**

        * 接受廣播事件

        * */

       @Override

       public void onReceive(Context context, Intent intent){

              if (intent.getAction().equals(broadCastString)){               

                     RemoteViews remoteViews  = new RemoteViews(context.getPackageName(),R.layout.appwidgetlayout);

                     //獲得appwidget管理實例,用於管理appwidget以便進行更新操作

                     AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

                    

                     int mTemp = intent.getIntExtra(extr, 0);

                     int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

                     if(mTemp == 0){                       

                            if (appWidgetId != -1) {                                  

                                   //創建一個Intent對象

                                   Intent vintent = new Intent();

                                   vintent.setAction(broadCastString);

                                   vintent.putExtra(extr, 1);

                                   vintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

 

                                   PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, vintent, PendingIntent.FLAG_UPDATE_CURRENT);

                                   remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

                                   remoteViews.setTextViewText(R.id.btnSend, "hihi"); 

 

                                   //更新appwidget

                                   appWidgetManager.updateAppWidget(appWidgetId, remoteViews);

                            }

                     }else{                         

                            if (appWidgetId != -1) {

                                   //創建一個Intent對象

                                   Intent vintent = new Intent();

                                   vintent.setAction(broadCastString);

                                   vintent.putExtra(extr, 0);

                                   vintent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

 

                                   PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, vintent, PendingIntent.FLAG_UPDATE_CURRENT);

                                   remoteViews.setOnClickPendingIntent(R.id.btnSend, pendingIntent);

                                   remoteViews.setTextViewText(R.id.btnSend, "send");

 

                                   //更新appwidget

                                   appWidgetManager.updateAppWidget(appWidgetId, remoteViews);

                            }

                     }

 

              }

              super.onReceive(context, intent);

       }

經過上述修改后,就不會有覆蓋安裝后,在一個桌面部件上點擊,其它同類部件跟着聯動的現象了。

程序卸載后,已創建的桌面部件會留下痕跡

我在程序使用過程中,在桌面上添加了如下左圖的所示的多個桌面部件,待程序卸載后,桌面上會出現類似如下右圖所示的多個坑或者沒留坑但是在部件上進行點擊等操作就沒有反應了。這些坑可能會在桌面重啟時消失,也有可能重啟后也繼續在那里留坑。

 

還有一種情況是我用ADW桌面和點心桌面做了個測試,即添加桌面部件到兩個桌面,不刪除程序的情況下,讓手機重啟,結果重啟后如果ADW桌面裝在手機內存中,那么就一切正常;如果裝在SD卡上則之前添加部件的桌面位置會留了幾個類似上面所示的坑;而裝在SD卡上的點心桌面連坑都沒有留下,被清除的一干二凈;當然裝在手機內存中的話,

這些問題,其實是跟加載AppWidget的具體的桌面應用程序相關了,不屬於AppWidget開發的范疇,所以對於這個問題也沒有什么好的解決方法。

關於AppWidget暫時小結到這里,對於高階地集合使用等未作嘗試和整理。

 

另外再添加幾篇關於開發appWidget的博文,僅做擴展了解

http://blog.csdn.net/lyfadd85/article/details/6618004

http://blog.csdn.net/lyfadd85/article/details/6686831

http://blog.csdn.net/lyfadd85/article/details/6747546

http://blog.csdn.net/lyfadd85/article/details/6772772

http://www.cnblogs.com/TerryBlog/archive/2010/07/29/1788319.html

http://www.cnblogs.com/TerryBlog/archive/2010/08/10/1796896.html

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM