前段时间解决一个widget的bug,具体分析是“appWidgetManager.updateAppWidget(THIS_APPWIDGET, views);”这个方法updateAppWidget()在恢复出厂设置后不更新了,我就查原因,结果通过log的验证发现时sdk的bug:
step 1:
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
sService.updateAppWidgetIds(appWidgetIds, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
step 2:这个sService.updateAppWidgetIds(appWIdgetIds, views);sService是AppWidgetService的对象,在这个类中:
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);
}
}
}
step 3:经过分析代码:updateAppWidgetInstanceLocked()这个方法出的问题;看代码:
void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
// allow for stale appWidgetIds and other badness
// lookup also checks that the calling process can access the appWidgetId
// drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
id.views = views;
// is anyone listening?
if (id.host.callbacks != null) {
try {
// the lock is held, but this is a oneway call
id.host.callbacks.updateAppWidget(id.appWidgetId, views);
} catch (RemoteException e) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
id.host.callbacks = null;
}
}
}
}
Step 4:经过打log分析,原来这个值id.host.callbacks == null造成的没有走到这个方法updateAppWidget(id.appWidgetId, views);这个callbacks是在这个类的startListening的时候赋值的,下面看一下这个方法:
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
List<RemoteViews> updatedViews) {
int callingUid = enforceCallingUid(packageName);
synchronized (mAppWidgetIds) {
Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
host.callbacks = callbacks;
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;
}
}
造成这个callbacks为空有两种原因,一个是host.callbacks = callbacks;赋值后,这个host.callbacks被别的条件置为空,另一个原因是这个参数传递的时候callbacks就传递了空值。
Step5:找到IAppWidgetHost callback赋值的地方。在AppWidgetHost中也有个startListenering方法()代码如下:
/**
* Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
}
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));
}
}
这个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事件:
<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>
其中<meta-data android:name="android.appwidget.provider" android:resource="@xml/dmling_appwidget" />这个格式是固定不变的,给appwidget定义一个dmling_appwidget.xml的文件.
二、在res目录下建立xml文件夹,在xml文件夹中建立dmling_appwidget.xml文件:
<?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>
这个appwidget-provider格式是固定的。
三、给“一”中的receiver建立一个接受类,正如“一”中的xml的语句中:
receiver android:name="com.cn.daming.provider.DMAlarmAppWidgetProvider"建立com.cn.daming.provider包,在
com.cn.daming.provider包中建立DMAlarmAppWidgetProvider.java类,如下:
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));//FLAG_NO_CREATE
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);
}
}
}
四、如“二”中的代码建立layout文件damling_appwidget.xml:
<?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" />
这样基本就把时钟的widget建立好了!当然这个没有按照AppWidgetProvider来写。我参考的是闹钟的时钟widget,我看这样也挺简单的,extends BroadcastReceiver,在onReceiver中写一个监听时钟的点击事件的方法,因为这个模拟时钟是
AnalogClock,在frameworks已经对它做了处理,所以我们只需要给它dial【表盘】,hand_hour:【时针】,hand_minute:
【分针】,就可以实现时钟了!
说明:有问题的,好意见的或者想要源码的可以留言!欢迎各界人士拍砖!
分享到:
相关推荐
android时钟widget小部件源代码
Android 时钟 Widget Demo
Android桌面插件-时钟widget 插入桌面widget时钟,可以选择多个时区,也可以自己添加代码进行改编和学习。
最近在学习widget,很久以前我的g1里面有个透明时钟,很是喜欢,后来换手机了,就找不到这个widget了,就自己写了个。喜欢的朋友和想学桌面widget的同学拿走吧
android模拟时钟widget 一个很好的demo 美中不足的是没有秒针
一个android桌面小控件案例源码,实现数字时钟功能
安卓桌面时钟widget工程代码包,widget有大字时钟和年月日星期显示,点击widget打开nubia时间管理应用,可以替换为其他时钟应用
android天气时钟,待机界面的的widget显示时间和天气
android翻页时钟,类似于htc时钟插件那种效果
主要介绍了android实现widget时钟示例,需要的朋友可以参考下
安卓手机时钟 本项目通过使用Google提供的Android SDK搭建应用开发平台,研究并总结了Android应用开发中涉及的一些关键技术,包括Android应用开发的环境配置、Android开发图形界面技术以及Android开发多线程技术。...
ClockView —— 简单的Android时钟控件
Android时钟适应所有分辨率 public float[] getDrawXY(double x,double y,double d,double angle){ float dx = (float) (x+d*Math.cos(angle * Math.PI / 180)); float dy = (float) (y+d*Math.sin(angle * Math...
时钟事件处理,显示签到时间,初学者实用。 android:id="@+id/analogClock01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"/> ...
主要用于实现搭载Android系统的手机,平板,电视机顶盒设备上运行时钟显示的功能, 让简直设备活起来,实现它存在的价值。 如果你觉得可以,请在此项目基础上添加更多功能,例如定时闹钟,天气预报等练习开发, 本...
本源码中包含三个时钟代码:更改系统时钟__Runable实现;更改系统时钟__TimerTask实现;自定义动画时钟__RotateAnimation实现
具体内容详情可看我的博文:https://mp.csdn.net/mp_blog/creation/editor/124227341 《Android Studio利用时钟控件AnalogClock显示模拟时钟以及TextClock显示数字时钟》
Eclipse V4.2.0编写的Android时钟应用实例(有用到线程)。
AppWidget 即桌面小部件,也叫桌面控件,就是能直接显示在Android系统桌面上的小程序,先看图: 图中我用黄色箭头指示的即为AppWidget,一些用户使用比较频繁的程序,可以做成AppWidget,这样能方便地使用。典型...
Android时钟控件