前段时间解决一个widget的bug,具体分析是“appWidgetManager.updateAppWidget(THIS_APPWIDGET, views);”这个方法updateAppWidget()在恢复出厂设置后不更新了,我就查原因,结果通过log的验证发现时sdk的bug:

step 1:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px">public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
  2. try {
  3. sService.updateAppWidgetIds(appWidgetIds, views);
  4. }
  5. catch (RemoteException e) {
  6. throw new RuntimeException("system server dead?", e);
  7. }
  8. }</span>

step 2:这个sService.updateAppWidgetIds(appWIdgetIds, views);sService是AppWidgetService的对象,在这个类中:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px">public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
  2. if (appWidgetIds == null) {
  3. return;
  4. }
  5. if (appWidgetIds.length == 0) {
  6. return;
  7. }
  8. final int N = appWidgetIds.length;
  9. synchronized (mAppWidgetIds) {
  10. for (int i=0; i<N; i++) {
  11. AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
  12. updateAppWidgetInstanceLocked(id, views);
  13. }
  14. }
  15. }</span>

step 3:经过分析代码:updateAppWidgetInstanceLocked()这个方法出的问题;看代码:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px">void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
  2. // allow for stale appWidgetIds and other badness
  3. // lookup also checks that the calling process can access the appWidgetId
  4. // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
  5. if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
  6. id.views = views;
  7. // is anyone listening?
  8. if (id.host.callbacks != null) {
  9. try {
  10. // the lock is held, but this is a oneway call
  11. id.host.callbacks.updateAppWidget(id.appWidgetId, views);
  12. } catch (RemoteException e) {
  13. // It failed; remove the callback. No need to prune because
  14. // we know that this host is still referenced by this instance.
  15. id.host.callbacks = null;
  16. }
  17. }
  18. }
  19. }
  20. </span>

Step 4:经过打log分析,原来这个值id.host.callbacks == null造成的没有走到这个方法updateAppWidget(id.appWidgetId, views);这个callbacks是在这个类的startListening的时候赋值的,下面看一下这个方法:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px">public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
  2. List<RemoteViews> updatedViews) {
  3. int callingUid = enforceCallingUid(packageName);
  4. synchronized (mAppWidgetIds) {
  5. Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
  6. <strong>  </strong><span style="color:#990000;"><strong>host.callbacks = callbacks;</strong>
  7. </span>            updatedViews.clear();
  8. ArrayList<AppWidgetId> instances = host.instances;
  9. int N = instances.size();
  10. int[] updatedIds = new int[N];
  11. for (int i=0; i<N; i++) {
  12. AppWidgetId id = instances.get(i);
  13. updatedIds[i] = id.appWidgetId;
  14. updatedViews.add(id.views);
  15. }
  16. return updatedIds;
  17. }
  18. }
  19. </span>

造成这个callbacks为空有两种原因,一个是host.callbacks = callbacks;赋值后,这个host.callbacks被别的条件置为空,另一个原因是这个参数传递的时候callbacks就传递了空值。

Step5:找到IAppWidgetHost callback赋值的地方。在AppWidgetHost中也有个startListenering方法()代码如下:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px"> /**
  2. * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
  3. * becomes visible, i.e. from onStart() in your Activity.
  4. */
  5. public void startListening() {
  6. int[] updatedIds;
  7. ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
  8. try {
  9. if (mPackageName == null) {
  10. mPackageName = mContext.getPackageName();
  11. }
  12. <span style="color:#660000;"><strong>updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
  13. </strong></span>        }
  14. catch (RemoteException e) {
  15. throw new RuntimeException("system server dead?", e);
  16. }
  17. final int N = updatedIds.length;
  18. for (int i=0; i<N; i++) {
  19. updateAppWidgetView(updatedIds[i], updatedViews.get(i));
  20. }
  21. }</span>

这个mCallbacks就是Step4中的callbacks传递过去的值,现在查找这个mCallbacks怎么赋值的??

搜索发现Callbacks mCallbacks = new Callbacks();这个mCallbacks是new的。这下明了了吧,在

sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);判断一下,如果mCallbacks为空,再new一下。传递过去后,把这个callbacks的值赋给一个全局变量,在Step3的时候加一个判断,为空的时候,赋值给id.host.callbacks。这样就解决了为空的情况!真正的原因没有查为什么为空?只是找到了解决方案!

通过以上的查找,我对AppWidgetProvider有了一定的了解。借此基础我写了一个时钟的widget,和模拟时钟的效果一样的,点击时钟就能进入到闹钟的界面:截图如下:

 红色部分是时钟:                         点击桌面添加时钟widget:     点击每一个进入到闹钟界面:

                           

因为AppWidgetProvider是继承extends BroadcastReceiver, 为一个简便类来处理App Widget广播。AppWidgetProvider只接收和这个App Widget相关的事件广播,比如这个App Widget被更新,删除,启用,以及禁用。当这些广播事件发生时,AppWidgetProvider 将接收到下面的方法调用:

  一、onEnabled(Context  context):

当第一次实例化一个appwidget的时候,接受action_appwidget_enabled广播,重写此方法以实现自己的appwidget功能。

 二、onUpdate(Context context,  AppWidgetManager appWidgetManager,  int[] appWidgetIds)

这个方法调用来间隔性的更新App Widget,间隔时间用AppWidgetProviderInfo 里的updatePeriodMillis属性定义(参见添加AppWidgetProviderInfo元数据)。这个方法也会在用户添加App Widget时被调用,因此它应该执行基础的设置,比如为视图定义事件处理器并启动一个临时的服务Service,如果需要的话。但是,如果你已经声明了一个配置活动,这个方法在用户添加App Widget时将不会被调用,而只在后续更新时被调用。配置活动应该在配置完成时负责执行第一次更新。

三、onDisabled(Context) 
       当你的App Widget的最后一个实例被从宿主中删除时被调用。你应该在onEnabled(Context)中做一些清理工作,比如删除一个shareparence或者是一个数据库。

四、onReceive(Context, Intent) 
      这个接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的AppWidgetProvider 实现过滤所有App Widget 广播并恰当的调用上述方法。
注意: 在Android 1.5中, 有一个已知问题,onDeleted()方法在该调用时不被调用。为了规避这个问题,你可以像

Group post中描述的那样实现onReceive() 来接收这个onDeleted()回调。

更多详细知识请看sdk帮助文档;

下面把截图的代码简单梳理一下:

一、在manifest中注册receiver事件

[html] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px"><receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider" android:label="@string/dmling_widget"
  2. android:icon="@drawable/ic_appwidget_clock148">
  3. <intent-filter>
  4. <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  5. </intent-filter>
  6. <meta-data android:name="android.appwidget.oldName" android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider" />
  7. <meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget" />
  8. </receiver>
  9. <receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider2" android:label="@string/dmling_widget2"
  10. android:icon="@drawable/ic_appwidget_clock248">
  11. <intent-filter>
  12. <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  13. </intent-filter>
  14. <meta-data android:name="android.appwidget.oldName2" android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider2" />
  15. <meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget2" />
  16. </receiver>
  17. <receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider3" android:label="@string/dmling_widget3"
  18. android:icon="@drawable/ic_appwidget_clock348">
  19. <intent-filter>
  20. <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  21. </intent-filter>
  22. <meta-data android:name="android.appwidget.oldName3" android:value="com.cn.daming.provider.DMAlarmAppWidgetProvider3" />
  23. <meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget3" />
  24. </receiver>
  25. </application></span>

其中<meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget" />这个格式是固定不变的,给appwidget定义一个dmling_appwidget.xml的文件.

二、在res目录下建立xml文件夹,在xml文件夹中建立dmling_appwidget.xml文件

[html] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px"><?xml version="1.0" encoding="utf-8"?>
  2. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:minWidth="146dip"
  4. android:minHeight="146dip"
  5. android:updatePeriodMillis="0"
  6. android:initialLayout="@layout/damling_appwidget"
  7. >
  8. </appwidget-provider>
  9. </span>

这个appwidget-provider格式是固定的。

三、给“一”中的receiver建立一个接受类,正如“一”中的xml的语句中:

receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider"建立com.cn.daming.provider包,在

com.cn.daming.provider包中建立DMAlarmAppWidgetProvider.java类,如下:

[java] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px">package com.cn.daming.provider;
  2. import android.app.PendingIntent;
  3. import android.appwidget.AppWidgetManager;
  4. import android.content.BroadcastReceiver;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.util.Log;
  8. import android.widget.RemoteViews;
  9. import com.cn.daming.deskclock.DeskClockMainActivity;
  10. import com.cn.daming.deskclock.R;
  11. public class DMAlarmAppWidgetProvider extends BroadcastReceiver {
  12. public void onReceive(Context context, Intent intent) {
  13. String action = intent.getAction();
  14. if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
  15. RemoteViews views = new RemoteViews(context.getPackageName(),
  16. R.layout.damling_appwidget);
  17. views.setOnClickPendingIntent(R.id.damling_appwidget,
  18. PendingIntent.getActivity(context, 0,
  19. new Intent(context, DeskClockMainActivity.class),
  20. PendingIntent.FLAG_UPDATE_CURRENT));//FLAG_NO_CREATE
  21. int[] appWidgetIds = intent.getIntArrayExtra(
  22. AppWidgetManager.EXTRA_APPWIDGET_IDS);
  23. AppWidgetManager gm = AppWidgetManager.getInstance(context);
  24. Log.v("wdaming", "DMAlarmAppWidgetProvider ---> appWidgetIds == "+appWidgetIds+"  gm == "+gm+
  25. "  views == "+views);
  26. gm.updateAppWidget(appWidgetIds, views);
  27. }
  28. }
  29. }
  30. </span>

四、如“二”中的代码建立layout文件damling_appwidget.xml:

[html] view plaincopyprint?
  1. <span style="FONT-SIZE: 16px"><?xml version="1.0" encoding="utf-8"?>
  2. <!-- 模拟时钟 -->
  3. <AnalogClock xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:id="@+id/damling_appwidget"
  5. android:dial="@drawable/ic_appwidget_clock1"
  6. android:hand_hour="@drawable/appwidget_clock_hour"
  7. android:hand_minute="@drawable/appwidget_clock_minute"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent" />
  10. </span>

这样基本就把时钟的widget建立好了!当然这个没有按照AppWidgetProvider来写。我参考的是闹钟的时钟widget,我看这样也挺简单的,extends BroadcastReceiver,在onReceiver中写一个监听时钟的点击事件的方法,因为这个模拟时钟是

AnalogClock,在frameworks已经对它做了处理,所以我们只需要给它dial【表盘】,hand_hour:【时针】,hand_minute:

【分针】,就可以实现时钟了!

 说明:有问题的,好意见的或者想要源码的可以留言!欢迎各界人士拍砖!

Android时钟的widget相关推荐

  1. Binary XML file line #8: Error inflating class android.support.v7.widget.RecyclerView

    今天创建了一个 demo ,然后就是复制RecyclerView 过去, 到最后完成运行的时候发现 系统奔溃了, 然后 提示 Binary XML file line #8: Error inflat ...

  2. 解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题

    如果在刚够构建Android Studio项目的时候,运行发现,出现没找到资源的错误!找不到com.android.support/appcompat-v7/23.0.1/res/values-v23 ...

  3. Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题

    Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题 这是API ...

  4. 解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题...

    如果在刚够构建Android Studio项目的时候,运行发现,出现没找到资源的错误!找不到com.android.support/appcompat-v7/23.0.1/res/values-v23 ...

  5. AndroidStudio中提示:Didn‘t find class “android.support.v7.widget.RecyclerView“

    场景 在Android Studio中使用Recycle View时提示: Didn't find class "android.support.v7.widget.RecyclerView ...

  6. int android.support.v7.widget.RecyclerView$ViewHolder.mItemViewType' on a null.....

    今天在做一个即时通讯回话列表时,遇到int android.support.v7.widget.RecyclerView$ViewHolder.mItemViewType' on a null..空指 ...

  7. android.support.v7.widget.,关于android.support.v7.widget.RecyclerView的使用,总是找不到类...

    各位高手求指导决,卡在这了,整了一个上午没搞定. log输出: 06-13 00:42:03.395: E/AndroidRuntime(1884): FATAL EXCEPTION: main 06 ...

  8. android layout 渲染,java – 渲染android.support.design.widget.CoordinatorLayout的问题

    我将 android studio升级到2.2.3后面临问题 问题是使用android.support.design.widget.CoordinatorLayout 它显示了这个错误 The fol ...

  9. Didn't find class android.support.v7.widget.RecyclerView 解决办法 ———————————————— 版权声明:本文为CSDN博主「eag

    依赖记得加:implementation 'com.android.support:recyclerview-v7:28.0.0' 你的可能是这样的 <android.support.v7.wi ...

最新文章

  1. suse11sp4配置vnc显示gnome
  2. C 判断 —— if...else 语句(bool变量、float变量、指针变量与“零值”进行比较)(else 到底与哪个 if 配对呢? if 语句后面的分号?)
  3. vue中使用axios发送ajax请求
  4. 54.什么是页面?什么是块或物理块?
  5. Angular2入门学习
  6. 如何用代码的方式取出SAP C4C销售订单创建后所有业务伙伴的数据
  7. lesson2-python3数据类型
  8. echarts tab切换_Python 数据可视化实战:使用 PyQt5 和 Echarts 打造股票数据看板
  9. 什么网了解c语言,什么是c语言?
  10. AsyncTask 很好
  11. sql基础语法(增、删、改、查)
  12. CF卡技术详解——笔记
  13. 5G UE — USIM Card
  14. 7.Android常用第三方支付
  15. 蒙特卡洛模拟与matlab,用MATLAB进行蒙特卡洛模拟?
  16. xctf攻防世界 MISC高手进阶区 3-11
  17. 判断当前是否是移动端H5打开
  18. discuz 版块导航function_forumlist.php,Discuz! X2扩建左侧版块导航 社区层次一目了然...
  19. libpng error: iTXt: chunk data is too large error: PNG unsigned integer out of range
  20. linux怎么运行dat文件,dat文件扩展名,dat文件怎么打开?

热门文章

  1. 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 获取 远程 目标进程 中的 /system/lib/libc.so 动态库中的 mmap 函数地址 )
  2. 【AOP 面向切面编程】AOP 简介 ( AspectJ 简介 | AspectJ 下载 )
  3. 【Android 高性能音频】AAudio 音频流 PCM 采样 的 采样 缓冲 播放 的 连续机制 ( 数据回调机制 | 数据回调函数指针 | 实现数据回调函数 | 设置数据回调函数 )
  4. 【嵌入式开发】嵌入式 开发环境 (远程登录 | 文件共享 | NFS TFTP 服务器 | 串口连接 | Win8.1 + RedHat Enterprise 6.3 + Vmware11)
  5. Redis源码剖析(十二)--客户端和服务器
  6. iOS开发UI篇—使用UItableview完成一个简单的QQ好友列表(一)
  7. 多区域显示(8)-透明花边
  8. asp.net添加电子地图
  9. [zz]一份非常内行的Linux LVM HOWTO
  10. Flash 与物理笔记:简单的小球重力模拟