前段時(shí)間解決一個(gè)widget的bug,具體分析是“appWidgetManager.updateAppWidget(THIS_APPWIDGET, views);”這個(gè)方法updateAppWidget()在恢復(fù)出廠設(shè)置后不更新了,我就查原因,結(jié)果通過log的驗(yàn)證發(fā)現(xiàn)時(shí)sdk的bug:
step 1:
[java]?view plaincopyprint?
<span?style="FONT-SIZE:?16px">public?void?updateAppWidget(int[]?appWidgetIds,?RemoteViews?views)?{?? ????????try?{?? ????????????sService.updateAppWidgetIds(appWidgetIds,?views);?? ????????}?? ????????catch?(RemoteException?e)?{?? ????????????throw?new?RuntimeException("system?server?dead?",?e);?? ????????}?? ????}</span>??
?
step 2:這個(gè)sService.updateAppWidgetIds(appWIdgetIds, views);sService是AppWidgetService的對象,在這個(gè)類中:
[java]?view plaincopyprint?
<span?style="FONT-SIZE:?16px">public?void?updateAppWidgetIds(int[]?appWidgetIds,?RemoteViews?views)?{?? ????????if?(appWidgetIds?==?null)?{?? ????????????return;?? ????????}?? ????????if?(appWidgetIds.length?==?0)?{?? ????????????return;?? ????????}?? ????????final?int?N?=?appWidgetIds.length;?? ?? ????????synchronized?(mAppWidgetIds)?{?? ????????????for?(int?i=0;?i<N;?i++)?{?? ????????????????AppWidgetId?id?=?lookupAppWidgetIdLocked(appWidgetIds[i]);?? ????????????????updateAppWidgetInstanceLocked(id,?views);?? ????????????}?? ????????}?? ????}</span>??
?
step 3:經(jīng)過分析代碼:updateAppWidgetInstanceLocked()這個(gè)方法出的問題;看代碼:
[java]?view plaincopyprint?
<span?style="FONT-SIZE:?16px">void?updateAppWidgetInstanceLocked(AppWidgetId?id,?RemoteViews?views)?{?? ?????????? ?????????? ?????????? ????????if?(id?!=?null?&&?id.provider?!=?null?&&?!id.provider.zombie?&&?!id.host.zombie)?{?? ????????????id.views?=?views;?? ?? ?????????????? ????????????if?(id.host.callbacks?!=?null)?{?? ????????????????try?{?? ?????????????????????? ????????????????????id.host.callbacks.updateAppWidget(id.appWidgetId,?views);?? ????????????????}?catch?(RemoteException?e)?{?? ?????????????????????? ?????????????????????? ????????????????????id.host.callbacks?=?null;?? ????????????????}?? ????????????}?? ????????}?? ????}?? </span>??
?
Step 4:經(jīng)過打log分析,原來這個(gè)值id.host.callbacks == null造成的沒有走到這個(gè)方法updateAppWidget(id.appWidgetId, views);這個(gè)callbacks是在這個(gè)類的startListening的時(shí)候賦值的,下面看一下這個(gè)方法:
[java]?view plaincopyprint?
?<span?style="FONT-SIZE:?16px">public?int[]?startListening(IAppWidgetHost?callbacks,?String?packageName,?int?hostId,?? ????????????List<RemoteViews>?updatedViews)?{?? ????????int?callingUid?=?enforceCallingUid(packageName);?? ????????synchronized?(mAppWidgetIds)?{?? ????????????Host?host?=?lookupOrAddHostLocked(callingUid,?packageName,?hostId);?? ??????????<strong>??</strong><span?style="color:#990000;"><strong>host.callbacks?=?callbacks;</strong>?? ?? </span>????????????updatedViews.clear();?? ?? ????????????ArrayList<AppWidgetId>?instances?=?host.instances;?? ????????????int?N?=?instances.size();?? ????????????int[]?updatedIds?=?new?int[N];?? ????????????for?(int?i=0;?i<N;?i++)?{?? ????????????????AppWidgetId?id?=?instances.get(i);?? ????????????????updatedIds[i]?=?id.appWidgetId;?? ????????????????updatedViews.add(id.views);?? ????????????}?? ????????????return?updatedIds;?? ????????}?? ????}?? </span>??
造成這個(gè)callbacks為空有兩種原因,一個(gè)是host.callbacks = callbacks;賦值后,這個(gè)host.callbacks被別的條件置為空,另一個(gè)原因是這個(gè)參數(shù)傳遞的時(shí)候callbacks就傳遞了空值。
?
Step5:找到IAppWidgetHost callback賦值的地方。在AppWidgetHost中也有個(gè)startListenering方法()代碼如下:
[java]?view plaincopyprint?
<span?style="FONT-SIZE:?16px">?? ? ? ?? ????public?void?startListening()?{?? ????????int[]?updatedIds;?? ????????ArrayList<RemoteViews>?updatedViews?=?new?ArrayList<RemoteViews>();?? ?????????? ????????try?{?? ????????????if?(mPackageName?==?null)?{?? ????????????????mPackageName?=?mContext.getPackageName();?? ????????????}?? ????????????<span?style="color:#660000;"><strong>updatedIds?=?sService.startListening(mCallbacks,?mPackageName,?mHostId,?updatedViews);?? </strong></span>????????}?? ????????catch?(RemoteException?e)?{?? ????????????throw?new?RuntimeException("system?server?dead?",?e);?? ????????}?? ?? ????????final?int?N?=?updatedIds.length;?? ????????for?(int?i=0;?i<N;?i++)?{?? ????????????updateAppWidgetView(updatedIds[i],?updatedViews.get(i));?? ????????}?? ????}</span>??
這個(gè)mCallbacks就是Step4中的callbacks傳遞過去的值,現(xiàn)在查找這個(gè)mCallbacks怎么賦值的??
搜索發(fā)現(xiàn)Callbacks mCallbacks = new Callbacks();這個(gè)mCallbacks是new的。這下明了了吧,在
sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);判斷一下,如果mCallbacks為空,再new一下。傳遞過去后,把這個(gè)callbacks的值賦給一個(gè)全局變量,在Step3的時(shí)候加一個(gè)判斷,為空的時(shí)候,賦值給id.host.callbacks。這樣就解決了為空的情況!真正的原因沒有查為什么為空?只是找到了解決方案!
?
通過以上的查找,我對AppWidgetProvider有了一定的了解。借此基礎(chǔ)我寫了一個(gè)時(shí)鐘的widget,和模擬時(shí)鐘的效果一樣的,點(diǎn)擊時(shí)鐘就能進(jìn)入到鬧鐘的界面:截圖如下:
????????????紅色部分是時(shí)鐘:?????????????????????????點(diǎn)擊桌面添加時(shí)鐘widget:?????點(diǎn)擊每一個(gè)進(jìn)入到鬧鐘界面:
??????????????????????????????????????
?
??? 因?yàn)锳ppWidgetProvider是繼承extends BroadcastReceiver, 為一個(gè)簡便類來處理App Widget廣播。AppWidgetProvider只接收和這個(gè)App Widget相關(guān)的事件廣播,比如這個(gè)App Widget被更新,刪除,啟用,以及禁用。當(dāng)這些廣播事件發(fā)生時(shí),AppWidgetProvider 將接收到下面的方法調(diào)用:
?? 一、onEnabled(Context ?context):
?????? ?當(dāng)?shù)谝淮螌?shí)例化一個(gè)appwidget的時(shí)候,接受action_appwidget_enabled廣播,重寫此方法以實(shí)現(xiàn)自己的appwidget功能。
?
??二、onUpdate(Context context, ?AppWidgetManager appWidgetManager,? int[] appWidgetIds)
??????? 這個(gè)方法調(diào)用來間隔性的更新App Widget,間隔時(shí)間用AppWidgetProviderInfo 里的updatePeriodMillis屬性定義(參見添加AppWidgetProviderInfo元數(shù)據(jù))。這個(gè)方法也會(huì)在用戶添加App Widget時(shí)被調(diào)用,因此它應(yīng)該執(zhí)行基礎(chǔ)的設(shè)置,比如為視圖定義事件處理器并啟動(dòng)一個(gè)臨時(shí)的服務(wù)Service,如果需要的話。但是,如果你已經(jīng)聲明了一個(gè)配置活動(dòng),這個(gè)方法在用戶添加App Widget時(shí)將不會(huì)被調(diào)用,而只在后續(xù)更新時(shí)被調(diào)用。配置活動(dòng)應(yīng)該在配置完成時(shí)負(fù)責(zé)執(zhí)行第一次更新。
?
三、onDisabled(Context)?
?????? 當(dāng)你的App Widget的最后一個(gè)實(shí)例被從宿主中刪除時(shí)被調(diào)用。你應(yīng)該在onEnabled(Context)中做一些清理工作,比如刪除一個(gè)shareparence或者是一個(gè)數(shù)據(jù)庫。
?
四、onReceive(Context, Intent)?
????? 這個(gè)接收到每個(gè)廣播時(shí)都會(huì)被調(diào)用,而且在上面的回調(diào)函數(shù)之前。你通常不需要實(shí)現(xiàn)這個(gè)方法,因?yàn)槿笔〉腁ppWidgetProvider 實(shí)現(xiàn)過濾所有App Widget 廣播并恰當(dāng)?shù)恼{(diào)用上述方法。
注意: 在Android?1.5中, 有一個(gè)已知問題,onDeleted()方法在該調(diào)用時(shí)不被調(diào)用。為了規(guī)避這個(gè)問題,你可以像
? Group post中描述的那樣實(shí)現(xiàn)onReceive() 來接收這個(gè)onDeleted()回調(diào)。
? 更多詳細(xì)知識(shí)請看sdk幫助文檔;
?
下面把截圖的代碼簡單梳理一下:
一、在manifest中注冊receiver事件:
[html]?view plaincopyprint?
<span?style="FONT-SIZE:?16px"><receiver?android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider"?android:label="@string/dmling_widget"?? ????????????android:icon="@drawable/ic_appwidget_clock148">?? ????????????<intent-filter>?? ????????????????<action?android:name="android.appwidget.action.APPWIDGET_UPDATE"?/>?? ????????????</intent-filter>?? ????????????<meta-data?android:name="android.appwidget.oldName"?android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider"?/>?? ????????????<meta-data?android:name="android.appwidget.provider"?android:resource="@xml/dmling_appwidget"?/>?? ????????</receiver>?? ?????????? ?????????? ????????<receiver?android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider2"?android:label="@string/dmling_widget2"?? ????????????android:icon="@drawable/ic_appwidget_clock248">?? ????????????<intent-filter>?? ????????????????<action?android:name="android.appwidget.action.APPWIDGET_UPDATE"?/>?? ????????????</intent-filter>?? ????????????<meta-data?android:name="android.appwidget.oldName2"?android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider2"?/>?? ????????????<meta-data?android:name="android.appwidget.provider"?android:resource="@xml/dmling_appwidget2"?/>?? ????????</receiver>?? ?????????? ????????<receiver?android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider3"?android:label="@string/dmling_widget3"?? ????????????android:icon="@drawable/ic_appwidget_clock348">?? ????????????<intent-filter>?? ????????????????<action?android:name="android.appwidget.action.APPWIDGET_UPDATE"?/>?? ????????????</intent-filter>?? ????????????<meta-data?android:name="android.appwidget.oldName3"?android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider3"?/>?? ????????????<meta-data?android:name="android.appwidget.provider"?android:resource="@xml/dmling_appwidget3"?/>?? ????????</receiver>?? ????</application></span>??
其中<meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget" />這個(gè)格式是固定不變的,給appwidget定義一個(gè)dmling_appwidget.xml的文件.
?
二、在res目錄下建立xml文件夾,在xml文件夾中建立dmling_appwidget.xml文件:
[html]?view plaincopyprint?
<span?style="FONT-SIZE:?16px"><?xml?version="1.0"?encoding="utf-8"?>?? <appwidget-provider?xmlns:android="http://schemas.android.com/apk/res/android"?? ????android:minWidth="146dip"?? ????android:minHeight="146dip"?? ????android:updatePeriodMillis="0"?? ????android:initialLayout="@layout/damling_appwidget"?? ????>?? </appwidget-provider>?? ?? </span>??
這個(gè)appwidget-provider格式是固定的。
?
三、給“一”中的receiver建立一個(gè)接受類,正如“一”中的xml的語句中:
????? receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider"建立com.cn.daming.provider包,在
com.cn.daming.provider包中建立DMAlarmAppWidgetProvider.java類,如下:
[java]?view plaincopyprint?
<span?style="FONT-SIZE:?16px">package?com.cn.daming.provider;?? ?? import?android.app.PendingIntent;?? import?android.appwidget.AppWidgetManager;?? import?android.content.BroadcastReceiver;?? import?android.content.Context;?? import?android.content.Intent;?? import?android.util.Log;?? import?android.widget.RemoteViews;?? ?? import?com.cn.daming.deskclock.DeskClockMainActivity;?? import?com.cn.daming.deskclock.R;?? ?? public?class?DMAlarmAppWidgetProvider?extends?BroadcastReceiver?{?? ?? ????public?void?onReceive(Context?context,?Intent?intent)?{?? ????????String?action?=?intent.getAction();?? ?? ????????if?(AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action))?{?? ????????????RemoteViews?views?=?new?RemoteViews(context.getPackageName(),?? ????????????????????R.layout.damling_appwidget);?? ?? ????????????views.setOnClickPendingIntent(R.id.damling_appwidget,?? ????????????????????PendingIntent.getActivity(context,?0,?? ????????????????????????new?Intent(context,?DeskClockMainActivity.class),?? ????????????????????????PendingIntent.FLAG_UPDATE_CURRENT));?? ?? ????????????int[]?appWidgetIds?=?intent.getIntArrayExtra(?? ????????????????????AppWidgetManager.EXTRA_APPWIDGET_IDS);?? ?? ????????????AppWidgetManager?gm?=?AppWidgetManager.getInstance(context);?? ????????????Log.v("wdaming",?"DMAlarmAppWidgetProvider?--->?appWidgetIds?==?"+appWidgetIds+"??gm?==?"+gm+?? ????????????????????"??views?==?"+views);?? ????????????gm.updateAppWidget(appWidgetIds,?views);?? ????????}?? ????}?? }?? </span>??
?
四、如“二”中的代碼建立layout文件damling_appwidget.xml:
[html]?view plaincopyprint?
<span?style="FONT-SIZE:?16px"><?xml?version="1.0"?encoding="utf-8"?>?? ?? <AnalogClock?xmlns:android="http://schemas.android.com/apk/res/android"?? ????android:id="@+id/damling_appwidget"?? ????android:dial="@drawable/ic_appwidget_clock1"?? ????android:hand_hour="@drawable/appwidget_clock_hour"?? ????android:hand_minute="@drawable/appwidget_clock_minute"?? ????android:layout_width="match_parent"?? ????android:layout_height="match_parent"?/>?? </span>??
?
這樣基本就把時(shí)鐘的widget建立好了!當(dāng)然這個(gè)沒有按照AppWidgetProvider來寫。我參考的是鬧鐘的時(shí)鐘widget,我看這樣也挺簡單的,extends BroadcastReceiver,在onReceiver中寫一個(gè)監(jiān)聽時(shí)鐘的點(diǎn)擊事件的方法,因?yàn)檫@個(gè)模擬時(shí)鐘是
AnalogClock,在frameworks已經(jīng)對它做了處理,所以我們只需要給它dial【表盤】,hand_hour:【時(shí)針】,hand_minute:
【分針】,就可以實(shí)現(xiàn)時(shí)鐘了!
????說明:有問題的,好意見的或者想要源碼的可以留言!歡迎各界人士拍磚!
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的Android时钟的widget的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。