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在添加到桌面后的效果图


  • 相关阅读:
    各种工具网站汇总
    Python中numpy库unique函数解析
    matlab中集合运算函数——解析
    hash算法搜索获得api函数地址的实现,"kernel32.dll", "CreateThread"
    PEB及LDR链
    PE文件结构及其加载机制
    spring boot2.0一看就会的超详细快速入门(七)-访问静态资源
    spring boot2.0一看就会的超详细快速入门(六)-集成Thymeleaf模板引擎
    spring boot2.0一看就会的超详细快速入门(五)-开发人员工具devtools
    spring boot2.0一看就会的超详细快速入门(四)-自动配置原理
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3158310.html
Copyright © 2011-2022 走看看