`
jandroid
  • 浏览: 1939972 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Android时钟的widget【安卓进化三十七】

 
阅读更多

前段时间解决一个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:

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

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



分享到:
评论
3 楼 yuer1218 2013-07-16  
正在学习这块,能把源码发我一份吗,12045464@qq.com,谢谢
2 楼 难得糊涂CN2010 2013-04-25  
最近一个项目要用到widget,可以送下源码不?dz.bitao@HuaWei.com
1 楼 变幻使者 2013-03-16  
楼主你真的好牛我是一个初学者你能把源码发到我邮箱啊我的邮箱2394149756@qq.com

相关推荐

    android时钟widget小部件源代码

    本主题聚焦于一个特定类型的Widget——时钟Widget,通过分析“android时钟widget小部件源代码”,我们可以深入了解Android Widget的开发及其与时间显示的集成。 Android时钟Widget通常包括以下几个核心组件和功能:...

    Android桌面插件-时钟widget

    本主题聚焦于"Android桌面插件-时钟widget",这是一个非常实用且常见的组件,它能为用户提供即时的时间显示,甚至支持多时区时间查看。 时钟Widget是Android系统中的一个核心组件,开发者可以通过自定义实现来提供...

    Android 时钟 Widget Demo

    本示例“Android时钟Widget Demo”着重于创建一个自定义的时钟组件,让用户可以在主屏幕上看到实时的时间显示,同时可能包含一些独特的动画效果。 首先,我们来探讨Android Widget的基本构建过程: 1. **创建布局...

    android时钟widget例子

    Android时钟Widget是一种桌面小部件,它允许用户在手机或平板电脑的主屏幕上显示一个实时更新的时间显示。在Android平台上,Widgets是应用程序的一种扩展,它们提供了无需打开应用就能与之交互的功能。在这个...

    android模拟时钟widget

    在Android平台上,开发一款模拟时钟Widget是一项挑战性的工作,因为它涉及到系统级别的交互以及UI设计。"android模拟时钟widget"是一个很好的示例项目,它展示了如何在Android应用中创建一个实时更新的时间显示组件...

    android 数字时钟widget 源码

    在Android平台上,开发一款数字时钟Widget是一项常见的任务,它能为用户提供桌面小部件,实时显示当前时间。这里我们深入探讨一下"android 数字时钟widget 源码"的相关知识点,以及如何实现这一功能。 首先,让我们...

    安卓桌面时钟widget代码

    本项目"安卓桌面时钟widget代码"是一个实现桌面时钟功能的小部件,它包含了大字体的时钟显示以及年月日星期的信息。以下是关于这个项目的一些关键知识点和相关技术的详细解释: 1. **Android Widget**: Android小...

    天气时钟widget

    《Android天气时钟Widget开发详解》 在移动设备领域,Android操作系统以其开源、灵活的特点深受开发者和用户喜爱。其中,Widget作为Android系统的一大特色,它允许开发者创建可以在用户主屏幕上直接展示信息的小...

    android 4.0 桌面时钟源码DeskClock

    《Android 4.0 桌面时钟源码DeskClock深度解析》 在Android操作系统中,DeskClock是一款内置的桌面时钟应用,它集成了多种功能,包括时钟、闹钟、计时器和秒表。对于开发者来说,深入理解DeskClock的源码能够帮助...

    android 源码 android Widget开发案例 eclipse项目 直接导入

    在Android平台上,Widget是应用程序的一种特殊形式,它们可以在用户的主屏幕上显示实时信息,提供快捷操作,无需打开完整的应用。这个“android 源码 android Widget开发案例 eclipse项目 直接导入”是一个很好的...

    Eclipse编写的Android时钟应用实例

    在本实例中,我们探讨的是如何使用Eclipse V4.2.0开发一个Android时钟应用,这将涉及Android应用程序的基础架构、UI设计、时间显示以及线程管理等多个关键知识点。Eclipse是一款广泛用于Java开发的集成开发环境,...

    安卓Widget小组件相关-androidWidget小组件开发.zip

    本资料包"androidWidget小组件开发.zip"包含了一个名为"MyWidget"的示例项目,可能包含了关于如何创建和实现Android Widget小组件的代码和资源。 1. **Widget组件概述** - Android Widget是Android系统中的一个...

    android翻页时钟

    在Android平台上,翻页时钟是一种独特且富有视觉吸引力的时钟设计,它模拟了实体翻页时钟的动画效果,每过一分钟或一小时,数字会像书页一样翻转,给人一种优雅而动态的时间展示。这个项目可能是为了创建一个自定义...

    Android自定义时钟

    Android时钟通常依赖于系统提供的`android.appwidget.AppWidgetProvider`类,它是一个广播接收器,用于监听与桌面小部件相关的事件。在自定义时钟时,我们需要继承这个类,并覆盖其`onUpdate()`方法,以便在每次时钟...

    Android简易时钟-数字时钟-APP项目源码

    主要用于实现搭载Android系统的手机,平板,电视机顶盒设备上运行时钟显示的功能, 让简直设备活起来,实现它存在的价值。 如果你觉得可以,请在此项目基础上添加更多功能,例如定时闹钟,天气预报等练习开发, 本...

    android时钟源代码

    Android桌面时钟是一款为用户提供实时时间显示的应用,它不仅可以作为手机主屏幕上的小部件(Widget),还能提供个性化的时间展示和功能扩展。本篇文章将深入探讨Android桌面时钟的源代码,揭示其背后的开发原理和...

    android时钟插件源码

    在Android开发中,创建自定义时钟插件是一项常见的任务,可以为用户提供独特的视觉体验和交互方式。本源码提供了一个宝贵的资源库,包含了三种不同的时钟实现方式,分别是通过`Runnable`、`TimerTask`以及`...

    android launcher桌面数字时钟控件

    1. **Android Widget API**:Android提供了一套Widget API,允许开发者创建可添加到用户主屏幕的小部件,包括数字时钟。开发者需要创建一个继承自`AppWidgetProvider`的类,并在XML布局文件中定义控件的外观。 2. *...

    android实现widget时钟示例分享

    在Android平台上,开发一个Widget时钟是一个有趣且实用的任务,它可以将实时时间显示在用户的主屏幕上,无需打开应用即可查看。下面将详细讲解如何在Android中实现一个Widget时钟。 首先,我们需要在`...

    Android简易时钟-数字时钟-星期显示-APP项目源码

    主要用于实现搭载Android系统的手机,平板,电视机顶盒设备上运行时钟显示的功能, 让闲置安卓设备发挥余热,实现它存在的价值。 如果你觉得可以,请在此项目基础上添加更多功能,例如定时闹钟,天气预报等练习开发...

Global site tag (gtag.js) - Google Analytics