zoukankan      html  css  js  c++  java
  • Android 之窗口小部件详解--App Widget

    Android 之窗口小部件详解--App Widget

     版本号 说明 作者 日期 
    1.0  添加App Widge介绍和示例  Sky Wang 2013/06/27 
           

     


    1 App Widget简介

    App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。

    本文参考Android官方文本,先介绍App Widget的主要组件,然后再以示例来详细说明。


    2 App Widget主要的相关类介绍

    2.1 AppWidgetProvider

    AppWidgetProvider 继承自 BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。

    AppWidgetProvider中的广播处理函数如下:

    onUpdate()
      当 widget 更新时被执行。
      同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了 widget 的 configure属性(即android:config,后面会介绍),那么当用户首次添加 widget 时,onUpdate()不会被调用;之后更新 widget 时,onUpdate才会被调用。

    onAppWidgetOptionsChanged()
      当 widget 被初次添加 或者 当 widget 的大小被改变时,执行onAppWidgetOptionsChanged()。你可以在该函数中,根据 widget 的大小来显示/隐藏某些内容。可以通过 getAppWidgetOptions() 来返回 Bundle 对象以读取 widget 的大小信息,Bundle中包括以下信息:
      OPTION_APPWIDGET_MIN_WIDTH -- 包含 widget 当前宽度的下限,以dp为单位。
      OPTION_APPWIDGET_MIN_HEIGHT -- 包含 widget 当前高度的下限,以dp为单位。
      OPTION_APPWIDGET_MAX_WIDTH -- 包含 widget 当前宽度的上限,以dp为单位。
      OPTION_APPWIDGET_MAX_HEIGHT -- 包含 widget 当前高度的上限,以dp为单位。

    onAppWidgetOptionsChanged() 是 Android 4.1 引入的。

    onDeleted(Context, int[])
      当 widget 被删除时被触发。

    onEnabled(Context)
      当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。

    onDisabled(Context)
      当最后1个 widget 的实例被删除时触发。

    onReceive(Context, Intent)
      接收到任意广播时触发,并且会在上述的方法之前被调用。

      总结,AppWidgetProvider 继承于 BroadcastReceiver。实际上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中调用的;是onReceive()对特定事情的响应函数。参考android源码frameworks/base/core/java/android/appwidget/AppWidgetProvider.java中onReceive()的定义:

    public void onReceive(Context context, Intent intent) {
        // Protect against rogue update broadcasts (not really a security issue,
        // just filter bad broacasts out so subclasses are less likely to crash).
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (appWidgetIds != null && appWidgetIds.length > 0) {
                    this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
                }   
            }   
        }   
        else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
                final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                this.onDeleted(context, new int[] { appWidgetId }); 
            }   
        }   
        else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
                    && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
                int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
                this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                        appWidgetId, widgetExtras);
            }   
        }   
        else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        }   
        else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        }   
    }
    View Code

    2.2 AppWidgetProviderInfo

    AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类。这个应该在XML里定义。下面以XML示例来对AppWidgetProviderInfo中常用的类型进行说明。

    示例XML
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      android:minWidth="40dp"
      android:minHeight="40dp"
      android:updatePeriodMillis="86400000"
      android:previewImage="@drawable/preview"
      android:initialLayout="@layout/example_appwidget"
      android:configure="com.example.android.ExampleAppWidgetConfigure"
      android:resizeMode="horizontal|vertical"
      android:widgetCategory="home_screen|keyguard"
      android:initialKeyguardLayout="@layout/example_keyguard">
    </appwidget-provider>


    示例说明
    minWidth 和minHeight
      它们指定了App Widget布局需要的最小区域。
      缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
      注意:app widget的最小尺寸,不建议比 “4x4” 个单元格要大。关于app widget的尺寸,后面在详细说明。

    minResizeWidth 和 minResizeHeight
      它们属性指定了 widget 的最小绝对尺寸。也就是说,如果 widget 小于该尺寸,便会因为变得模糊、看不清或不可用。 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。
      注意:(01) 当 minResizeWidth 的值比 minWidth 大时,minResizeWidth 无效;当 resizeMode 的取值不包括 horizontal 时,minResizeWidth 无效。
               (02) 当 minResizeHeight 的值比 minHeight 大时,minResizeHeight 无效;当 resizeMode 的取值不包括 vertical 时,minResizeHeight 无效。

    updatePeriodMillis
      它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。 实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!关于这部分,后面会详细介绍。
      注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。

    initialLayout
      指向 widget 的布局资源文件

    configure
      可选属性,定义了 widget 的配置 Activity。如果定义了该项,那么当 widget 创建时,会自动启动该 Activity。

    previewImage
      指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标。该字段对应在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。

    autoAdvanceViewId
      指定一个子view ID,表明该子 view 会自动更新。在 Android 3.0 中引入。

    resizeMode
      指定了 widget 的调整尺寸的规则。可取的值有: "horizontal", "vertical", "none"。"horizontal"意味着widget可以水平拉伸,“vertical”意味着widget可以竖值拉伸,“none”意味着widget不能拉伸;默认值是"none"。Android 3.1 引入。

    widgetCategory
      指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:"home_screen" 和 "keyguard"。Android 4.2 引入。

    initialKeyguardLayout
      指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。


    3 App Widget布局说明

    3.1 添加 widget 到lock screen中

      默认情况下(即不设置android:widgetCategory属性),Android是将widget添加到 home screen 中。
      但在Android 4.2中,若用户希望 widget 可以被添加到lock screen中,可以通过设置 widget 的 android:widgetCategory 属性包含keyguard来完成。

      当你把 widget 添加到lock screen中时,你可能对它进行定制化操作,以区别于添加到home screen中的情况。 你能够通过 getAppWidgetOptions() 来进行判断 widget 是被添加到lock screen中,还是home screen中。通过 getApplicationOptions() 获取 Bundle对象,然后读取 Bundle 的OPTION_APPWIDGET_HOST_CATEGORY值:若值为 WIDGET_CATEGORY_HOME_SCREEN, 则表示该 widget 被添加到home screen中; 若值为 WIDGET_CATEGORY_KEYGUARD,则表示该 widget 被添加到lock screen中。

      另外,你应该为添加到lock screen中的 widget 单独使用一个layout,可以通过 android:initialKeyguardLayout 来指定。而 widget 添加到home screen中的layout则可以通过 android:initialLayout 来指定。


    3.2 布局

    一 Widget窗口组件

      如上图所示,典型的App Widget有三个主要组件:一个边界框(A bounding box),一个框架(a Frame),和控件的图形控件(Widget Controls)和其他元素。App Widget并不支持全部的视图窗口,它只是支持视图窗口的一个子集,后面会详细说明支持哪些视图窗口。
      要设计美观的App Widget,建议在“边界框和框架之间(Widget Margins)”以及“框架和控件(Widget Padding)”之间填留有空隙。在Android4.0以上的版本,系统为自动的保留这些空隙。

    二 Widget窗口大小

      在AppWidgetProviderInfo中已经介绍了,minWidth 和minHeight 用来指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
      例如,很多手机提供4x4网格,平板电脑能提供8x7网格。当widget被添加到时,在满足minWidth和minHeight约束的前提下,它将被占领的最小数目的细胞。

    粗略计算minWidth和minHeight,可以参考下面表格:

    单元格个数
    (行 / 列)
    对应的设置大小 (dp)
    (minWidth / minHeight)
    1 40dp
    2 110dp
    3 180dp
    4 250dp
    n 70 × n − 30



    详细计算minWidth和minHeight,要计算各个区域的大小。以下图为例:

    计算结果
    minWidth = 144dp + (2 × 8dp) + (2 × 56dp) = 272dp
    minHeight = 48dp + (2 × 4dp) = 56dp


    3.3 App Widget支持的布局和控件

    Widget并不支持所有的布局和控件,而仅仅只是支持Android布局和控件的一个子集。
    (01) App Widget支持的布局:
      FrameLayout
      LinearLayout
      RelativeLayout
      GridLayout
    (02) App Widget支持的控件:
      AnalogClock
      Button
      Chronometer
      ImageButton
      ImageView
      ProgressBar
      TextView
      ViewFlipper
      ListView
      GridView
      StackView
      AdapterViewFlipper

     


    4 App Widget应用实例

      应用实例需求:建立一个Widget示例,要求Widget能被添加到主屏中,widget包含3个成分:文本、按钮和图片。文本要求:显示提示信息;按钮要求:点击按钮,弹出一个Toast提示框;图片要求:每个5秒随机更新一张图片。

    第1步 新建Android工程
    新建空白的Android工程,即不需要在建立Activity子类。

    第2步 编辑manifest
    修改AndroidManifest.xml,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.skywang.widget"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="14"
            android:targetSdkVersion="17" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            
            <!-- 声明widget对应的AppWidgetProvider -->
            <receiver android:name=".ExampleAppWidgetProvider" >
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                    <action android:name="com.skywang.widget.UPDATE_ALL"/>
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                    android:resource="@xml/example_appwidget_info" />
            </receiver>
            
            <service android:name=".ExampleAppWidgetService" >
                <intent-filter>
                    <action android:name="android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE" />
                </intent-filter>
            </service>
            
        </application>
    
    </manifest>

    说明
    (01) ExampleAppWidgetProvider是继承于的AppWidgetProvider类,用来响应widget的添加、删除、更新等操作。
    (02) android.appwidget.action.APPWIDGET_UPDATE,必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它。
    (03) action android:name="com.skywang.widget.UPDATE_ALL,这是我自己添加了,是为了接收服务所发送的更新图片的广播。
    (04) <meta-data> 指定了 AppWidgetProviderInfo 对应的资源文件
        android:name -- 指定metadata名,通过android.appwidget.provider来辨别data。
        android:resource -- 指定 AppWidgetProviderInfo 对应的资源路径。即,xml/example_appwidget_info.xml。
    (05) ExampleAppWidgetService 是用于更新widget中的图片的服务。
    (06) android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE 用于启动服务的action。


    第3步 编辑AppWidgetProviderInfo对应的资源文件
    在当前工程下新建xml目录(若xml目录不存在的话);并在xml目录下新建example_appwidget_info.xml文件。xml文件内容如下:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="180dp"
        android:minHeight="180dp"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/example_appwidget"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="home_screen|keyguard">
        
        <!--
        android:minWidth : 最小宽度
        android:minHeight : 最小高度
        android:updatePeriodMillis : 更新widget的时间间隔(ms),"86400000"为1个小时
        android:previewImage : 预览图片
        android:initialLayout : 加载到桌面时对应的布局文件
        android:resizeMode : widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
        android:widgetCategory : widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
        android:initialKeyguardLayout : 加载到锁屏界面时对应的布局文件
         -->
        
    </appwidget-provider>

    说明:
    (01) android:previewImage,用于指定预览图片。即搜索到widget时,查看到的图片。若没有设置的话,系统为指定一张默认图片。
    (02) android:updatePeriodMillis 更新widget的时间间隔(ms)。在实际测试中,发现设置android:updatePeriodMillis的值为5秒时,不管用!跟踪android源代码,发现:
    当android:updatePeriodMillis的值小于30分钟时,会被设置为30分钟。也就意味着,即使将它的值设为5秒,实际上也是30分钟之后才更新。因此,我们若向动态的更新widget的某组件,最好通过service、AlarmManager、Timer等方式;本文采用的是service。

    android源码中widget服务文件:frameworks/base/services/java/com/android/server/AppWidgetService.java
    与 android:updatePeriodMillis相关代码如下:

        ...
    private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
        ...
    
    void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {
    
        ...
        // 获取updatePeriodMillis的值
        long period = p.info.updatePeriodMillis;
        // 当updatePeriodMillis小于30分钟时,设为它为30分钟
        if (period < MIN_UPDATE_PERIOD) {
            period = MIN_UPDATE_PERIOD;
        }    
        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                SystemClock.elapsedRealtime() + period, period, p.broadcast);
       ...
    }


    第4步 编辑example_appwidget.xml等资源文件
    新建layout/example_appwidget.xml,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
      
        <LinearLayout 
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"
            android:layout_gravity="center" 
            android:orientation="horizontal" >
            
            <TextView  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:text="HomeScreen Widget" />    
            
            <Button
                android:id="@+id/btn_show"
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"  
                android:text="Show" />
        </LinearLayout> 
            
        <ImageView
            android:id="@+id/iv_show"
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content" 
            android:layout_gravity="center"/> 
            
    </LinearLayout>

    说明:

    (01) example_appwidget布局中,包含了“1个文本,1个按钮和1个图片控件”。

    点击下载:本工程中需要用到的图片
    将所有的图片放到drawable目录中。这里的drawable广义的,指工程实际用到的图片所在目录;例如,我自己的是放在drawabld-hdmi中。


    第5步 编辑ExampleAppWidgetProvider.java
    在当前工程下,新建ExampleAppWidgetProvider.java,代码如下:

    package com.skywang.widget;
    
    import android.app.PendingIntent;
    import android.appwidget.AppWidgetManager;  
    import android.appwidget.AppWidgetProvider;  
    import android.content.Context;  
    import android.content.Intent;  
    import android.os.Bundle;
    import android.net.Uri;
    import android.widget.RemoteViews;
    import android.widget.Toast;
    import android.util.Log;
    
    import java.util.Set;
    import java.util.HashSet;
    import java.util.Iterator;
    
    /*
     * @author : skywang <wangkuiwu@gmail.com>
     * description : 提供App Widget
     */
    
    public class ExampleAppWidgetProvider extends AppWidgetProvider {
        private static final String TAG = "ExampleAppWidgetProvider";
    
        private boolean DEBUG = false; 
        // 启动ExampleAppWidgetService服务对应的action
        private final Intent EXAMPLE_SERVICE_INTENT = 
                new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE");
        // 更新 widget 的广播对应的action
        private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL";
        // 保存 widget 的id的HashSet,每新建一个 widget 都会为该 widget 分配一个 id。
        private static Set idsSet = new HashSet();
        // 按钮信息
        private static final int BUTTON_SHOW = 1;
        // 图片数组
        private static final int[] ARR_IMAGES = { 
            R.drawable.sample_0, 
            R.drawable.sample_1, 
            R.drawable.sample_2, 
            R.drawable.sample_3, 
            R.drawable.sample_4, 
            R.drawable.sample_5,
            R.drawable.sample_6,
            R.drawable.sample_7,
        };
        
        // onUpdate() 在更新 widget 时,被执行,
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            Log.d(TAG, "onUpdate(): appWidgetIds.length="+appWidgetIds.length);
    
            // 每次 widget 被创建时,对应的将widget的id添加到set中
            for (int appWidgetId : appWidgetIds) {
                idsSet.add(Integer.valueOf(appWidgetId));
            }
            prtSet();
        }
        
        // 当 widget 被初次添加 或者 当 widget 的大小被改变时,被调用 
        @Override  
        public void onAppWidgetOptionsChanged(Context context,  
                AppWidgetManager appWidgetManager, int appWidgetId,  
                Bundle newOptions) {
            Log.d(TAG, "onAppWidgetOptionsChanged");
            super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId,  
                    newOptions);  
        }  
        
        // widget被删除时调用  
        @Override  
        public void onDeleted(Context context, int[] appWidgetIds) {  
            Log.d(TAG, "onDeleted(): appWidgetIds.length="+appWidgetIds.length);
    
            // 当 widget 被删除时,对应的删除set中保存的widget的id
            for (int appWidgetId : appWidgetIds) {
                idsSet.remove(Integer.valueOf(appWidgetId));
            }
            prtSet();
            
            super.onDeleted(context, appWidgetIds);  
        }
    
        // 第一个widget被创建时调用  
        @Override  
        public void onEnabled(Context context) {  
            Log.d(TAG, "onEnabled");
            // 在第一个 widget 被创建时,开启服务
            context.startService(EXAMPLE_SERVICE_INTENT);
            
            super.onEnabled(context);  
        }  
        
        // 最后一个widget被删除时调用  
        @Override  
        public void onDisabled(Context context) {  
            Log.d(TAG, "onDisabled");
    
            // 在最后一个 widget 被删除时,终止服务
            context.stopService(EXAMPLE_SERVICE_INTENT);
    
            super.onDisabled(context);  
        }
        
        
        // 接收广播的回调函数
        @Override  
        public void onReceive(Context context, Intent intent) {  
    
            final String action = intent.getAction();
            Log.d(TAG, "OnReceive:Action: " + action);
            if (ACTION_UPDATE_ALL.equals(action)) {
                // “更新”广播
                updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
            } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
                // “按钮点击”广播
                Uri data = intent.getData();
                int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
                if (buttonId == BUTTON_SHOW) {
                    Log.d(TAG, "Button wifi clicked");
                    Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT).show();
                }
            }
            
            super.onReceive(context, intent);  
        }  
    
        // 更新所有的 widget 
        private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) {
    
            Log.d(TAG, "updateAllAppWidgets(): size="+set.size());
            
            // widget 的id
            int appID;
            // 迭代器,用于遍历所有保存的widget的id
            Iterator it = set.iterator();
    
            while (it.hasNext()) {
                appID = ((Integer)it.next()).intValue();    
                // 随机获取一张图片
                int index = (new java.util.Random().nextInt(ARR_IMAGES.length));
                
                if (DEBUG) Log.d(TAG, "onUpdate(): index="+index);            
                // 获取 example_appwidget.xml 对应的RemoteViews            
                RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);
                
                // 设置显示图片
                remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]);
                
                // 设置点击按钮对应的PendingIntent:即点击按钮时,发送广播。
                remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context,
                        BUTTON_SHOW));
    
                // 更新 widget
                appWidgetManager.updateAppWidget(appID, remoteView);        
            }        
        }
    
        private PendingIntent getPendingIntent(Context context, int buttonId) {
            Intent intent = new Intent();
            intent.setClass(context, ExampleAppWidgetProvider.class);
            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
            intent.setData(Uri.parse("custom:" + buttonId));
            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0 );
            return pi;
        }
    
        // 调试用:遍历set
        private void prtSet() {
            if (DEBUG) {
                int index = 0;
                int size = idsSet.size();
                Iterator it = idsSet.iterator();
                Log.d(TAG, "total:"+size);
                while (it.hasNext()) {
                    Log.d(TAG, index + " -- " + ((Integer)it.next()).intValue());
                }
            }
        }
    }

    说明
    (01) 当我们创建第一个widget到桌面时,会执行onEnabled()。在onEnabled()中通过 context.startService(EXAMPLE_SERVICE_INTENT) 启动服务ExampleAppWidgetService。服务的作用就是每隔5秒发送一个ACTION_UPDATE_ALL广播给我们,用于更新widget中的图片。
           仅仅当我们创建第一个widget时才会启动服务,因为onEnabled()只会在第一个widget被创建时才执行。
    (02) 当我们删除最后一个widget到桌面时,会执行onDisabled()。在onDisabled()中通过 context.stopService(EXAMPLE_SERVICE_INTENT) 终止服务ExampleAppWidgetService。
           仅仅当我们删除最后一个widget时才会终止服务,因为onDisabled()只会在最后一个widget被删除时才执行。
    (03) 本工程中,每添加一个widget都会执行onUpdate()。例外情况:在定义android:configure的前提下,第一次添加widget时不会执行onUpdate(),而是执行android:configure中定义的activity。
    (04) onReceive()中,处理两个广播:更新桌面的widget 以及 响应按钮点击广播。
           当收到ACTION_UPDATE_ALL广播时,调用updateAllAppWidgets()来更新所有的widget。
           当收到的广播的categery为Intent.CATEGORY_ALTERNATIVE,并且scheme为BUTTON_SHOW时,对应是按钮点击事件。按钮的监听是在updateAllAppWidgets()中注册的。

     

    第6步 编辑ExampleAppWidgetService.java
    在当前工程下,新建ExampleAppWidgetService.java,代码如下:

    package com.skywang.widget;
    
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.util.Log;
    
    /*
     * @author : skywang <wangkuiwu@gmail.com>
     * description : 周期性更新AppWidget的服务
     */
    
    public class ExampleAppWidgetService extends Service {
        
        private static final String TAG="ExampleAppWidgetService"; 
    
        // 更新 widget 的广播对应的action
        private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL";
        // 周期性更新 widget 的周期
        private static final int UPDATE_TIME = 5000;
        // 周期性更新 widget 的线程
        private UpdateThread mUpdateThread;
        private Context mContext;
        // 更新周期的计数
        private int count=0;      
    
        @Override
        public void onCreate() {
            
            // 创建并开启线程UpdateThread
            mUpdateThread = new UpdateThread();
            mUpdateThread.start();
            
            mContext = this.getApplicationContext();
    
            super.onCreate();
        }
        
        @Override
        public void onDestroy(){
            // 中断线程,即结束线程。
            if (mUpdateThread != null) {
                mUpdateThread.interrupt();
            }
            
            super.onDestroy();
        }
        
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        /*
         * 服务开始时,即调用startService()时,onStartCommand()被执行。
         */
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.d(TAG, "onStartCommand");        
            super.onStartCommand(intent, flags, startId);
            
            return START_STICKY;
        }
        
        private class UpdateThread extends Thread {
    
            @Override
            public void run() {
                super.run();
    
                try {
                    count = 0;
                    while (true) {
                        Log.d(TAG, "run ... count:"+count);
                        count++;
    
                        Intent updateIntent=new Intent(ACTION_UPDATE_ALL);
                        mContext.sendBroadcast(updateIntent);
                        
                        Thread.sleep(UPDATE_TIME);
                    } 
                } catch (InterruptedException e) {
                    // 将 InterruptedException 定义在while循环之外,意味着抛出 InterruptedException 异常时,终止线程。
                    e.printStackTrace();
                }
            }
        }
    }

    说明

    (01) onCreate() 在创建服务时被执行。它的作用是创建并启动线程UpdateThread()。
    (02) onDestroy() 在销毁服务时被执行。它的作用是注销线程UpdateThread()。
    (03) 服务UpdateThread 每隔5秒,发送1个广播ACTION_UPDATE_ALL。广播ACTION_UPDATE_ALL在ExampleAppWidgetProvider被处理:用来更新widget中的图片。

    点击下载源代码

    点击查看skywang博客索引

    widget在添加到桌面前的效果图

    widget在添加到桌面后的效果图


  • 相关阅读:
    DC中为什么要用Uniquify?
    hdu 1596 find the safest road
    hdu2112 HDU Today
    hdu 2066 一个人的旅行
    poj 3026 Borg Maze
    poj 1979 Red and Black
    poj 1321 棋盘问题
    hdu 1010 Tempter of the Bone
    hdu 4861 Couple doubi
    codeforces584B Kolya and Tanya
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3158310.html
Copyright © 2011-2022 走看看