Android的Widget桌面应用学习

一、Android应用的Widget的介绍

介绍:Android应用的Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个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()对特定事情的响应函数。

2.2、AppWidgetProviderInfo

AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类,这个文件是在res/xml中定义的,后缀为xml。下面是一个事例,还有AppWidgetProviderInfo中的一些常用的属性介绍:

文件名:widget_weather.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_weather"
    android:minHeight="180dp"
    android:minWidth="180dp"
    android:previewImage="@mipmap/sunny"
    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>

示例说明:

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”。Android3.1 引入。

widgetCategory

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

initialKeyguardLayout

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

其中比较重要的是:android:initialLayout=”@layout/widget_weather”这句话,这句话指向的是Widget的布局文件名。

三、Widget使用步骤

1、第一步,设计应用的布局。

就是一个普通的布局文件,不过长宽要设置合适。

如图,我就给一张布局的图片,布局代码自行完成。

注意:这里介绍一些关于布局的知识,也是摘自别人的博客,一起学习一下。

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 来指定。

2、布局

如上图所示,典型的App Widget有三个主要组件:一个边界框(A bounding box),一个框架(a Frame),和控件的图形控件(Widget Controls)和其他元素。App Widget并不支持全部的视图窗口,它只是支持视图窗口的一个子集,后面会详细说明支持哪些视图窗口。

要设计美观的App Widget,建议在“边界框和框架之间(Widget Margins)”以及“框架和控件(Widget

Padding)”之间填留有空隙。在Android4.0以上的版本,系统为自动的保留这些空隙。

3、Widget窗口大小

在AppWidgetProviderInfo中已经介绍了,minWidth 和minHeight 用来指定了App

Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App

Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App

Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。

例如,很多手机提供4x4网格,平板电脑能提供8x7网格。当widget被添加到时,在满足minWidth和minHeight约束的前提下,它将被占领的最小数目的细胞。

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

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

  

2、第二步,编辑AppWidgetProviderInfo对应的资源文件。

文件名:weather_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_weather"
    android:minHeight="180dp"
    android:minWidth="180dp"
    android:previewImage="@mipmap/sunny"
    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。

3、第三步,编写类文件继承AppWidgetProvider类,重写方法

文件名:AppWidgetProvider.java
public class AppWidgetProvider extends android.appwidget.AppWidgetProvider {
    Gson gson;
    RecentWeatherBean recentWeatherBean;
    RetDataBean retDataBean;
    TodayBean todayBean;
    GetDate getDate;
    Context context;
    //保存地址和ID
    SharedPreferences sharedPreferences;
    //启动AppWidgetService服务对应的action
    private final Intent SERVICE_INTENT = new Intent("android.appwidget.action.APP_WIDGET_SERVICE");
    //更新widget的广播对应的action
    private final String ACTION_UPDATE_ALL = "com.llay.widget.UPDATE_ALL";

    //onUpdate()在更新widget时,被执行(当用户点击wiget界面时候可以调用这个方法)
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int i = 0; i < appWidgetIds.length; i++) {
            //创建一个Intent对象,跳转到app界面
            Intent intent = new Intent(context, GetLocationActivity.class);
            //创建一个PendingIntent,包主intent
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
            //获取wiget布局
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
            remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }

    //当widget被初次添加或者当widget的大小被改变时,被调用
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
    }

    //第一个widget被创建时调用
    @Override
    public void onEnabled(Context context) {
        //在第一个widget被创建时,开启服务
        SERVICE_INTENT.setPackage("com.llay.admin.weather");
        context.startService(SERVICE_INTENT);
        super.onEnabled(context);
    }

    //最后一个widget被删除时调用
    @Override
    public void onDisabled(Context context) {
        //在最后一个widget被删除时,终止服务
        SERVICE_INTENT.setPackage("com.llay.admin.weather");
        context.stopService(SERVICE_INTENT);
        super.onDisabled(context);
    }

    //接收广播的回调函数
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        this.context = context;
        if (ACTION_UPDATE_ALL.equals(action)) {
            getDate = new GetDate();
            getDate.execute("http://apis.baidu.com/apistore/weatherservice/recentweathers");
        } else {
            super.onReceive(context, intent);
        }

    }

    //请求数据
    public class GetDate extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            sharedPreferences = context.getSharedPreferences("CityAndCode", Context.MODE_PRIVATE);
            String recentJson = Utils.request(params[0], "cityname=" + sharedPreferences.getString("locationCityName", "南通") + "&cityid=" + sharedPreferences.getInt("locationCityCode", 101190501));
            return recentJson;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            if (s != null) {
                gson = new Gson();
                recentWeatherBean = new RecentWeatherBean();
                recentWeatherBean = gson.fromJson(s, RecentWeatherBean.class);
                retDataBean = new RetDataBean();
                retDataBean = recentWeatherBean.getRetData();
                todayBean = new TodayBean();
                todayBean = retDataBean.getToday();
                //更新界面
                RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
                switch (todayBean.getType()) {
                    case "晴":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.sunny);
                        break;
                    case "多云":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.cloudy);
                        break;
                    case "阴":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.overcast);
                        break;
                    case "阵雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.shower);
                        break;
                    case "雷阵雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.thundershower);
                        break;
                    case "小雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.light_rain);
                        break;
                    case "中雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.moderate_rain);
                        break;
                    case "大雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_rain);
                        break;
                    case "暴雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.storm);
                        break;
                    case "大暴雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_storm);
                        break;
                    case "小到中雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.light_to_moderate_rain);
                        break;
                    case "中到大雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.moderate_to_heavy_rain);
                        break;
                    case "大到暴雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.heavy_to_storm);
                        break;
                    case "暴雨到大暴雨":
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.storm_to_heavy_storm);
                        break;
                    default:
                        remoteViews.setImageViewResource(R.id.wigdet_image, R.mipmap.undefined);
                        break;
                }
                remoteViews.setTextViewText(R.id.widget_type, todayBean.getType());
                remoteViews.setTextViewText(R.id.widget_curtemp, todayBean.getCurTemp());
                remoteViews.setTextViewText(R.id.widget_date, todayBean.getDate());
                remoteViews.setTextViewText(R.id.widget_location, retDataBean.getCity());
                remoteViews.setTextViewText(R.id.widget_week, todayBean.getWeek());
                remoteViews.setTextViewText(R.id.widget_lowtemp, todayBean.getLowtemp());
                remoteViews.setTextViewText(R.id.widget_hightemp, todayBean.getHightemp());
                remoteViews.setTextViewText(R.id.widget_fengxiang, todayBean.getFengxiang());
                remoteViews.setTextViewText(R.id.widget_fengli, todayBean.getFengli());
                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                ComponentName componentName = new ComponentName(context, AppWidgetProvider.class);
                appWidgetManager.updateAppWidget(componentName, remoteViews);
            }
        }
    }

}

说明:

(01)当我们创建第一个widget到桌面时,会执行onEnabled()。在onEnabled()中通过 context.startService(SERVICE_INTENT) 启动服务AppWidgetService。服务的作用就是每隔5秒发送一个ACTION_UPDATE_ALL广播给我们,用于更新widget中的数据。仅仅当我们创建第一个widget时才会启动服务,因为onEnabled()只会在第一个widget被创建时才执行。

(02)当我们删除最后一个widget到桌面时,会执行onDisabled()。在onDisabled()中通过 context.stopService(SERVICE_INTENT) 终止服务AppWidgetService。仅仅当我们删除最后一个widget时才会终止服务,因为onDisabled()只会在最后一个widget被删除时才执行。

(03)本工程中,每添加一个widget都会执行onUpdate()。例外情况:在定义android:configure的前提下,第一次添加widget时不会执行onUpdate(),而是执行android:configure中定义的activity。在onUpdate()方法中,有两个类要特别注意:PendingIntent和RemoteViews。PendingIntent用户包裹住一个Intent,然后再调用remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);方法时,运行Intent。RemoteViews用户获取Widget中的控件,点击Widget中的控件,调用remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);方法,可以实现一些功能,如点击Widget然后打开应用。

(04)onReceive()中,更新桌面的widget 以及响应按钮点击广播。当收到ACTION_UPDATE_ALL广播时,调用getDate.execute()方法来请求接口,然后再onPostExecute()中更新的widget的数据。

4、第四步,在AndroidManifest.xml中注册AppWidgetProvider

因为AppWidgetProvider原本就是一个BroadCastReceiver,需要在AndroidManifest.xml中注册。

<!-- 声明widget对应的AppWidgetProvider -->
        <receiver android:name=".broadcastreceiver.AppWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="com.llay.widget.UPDATE_ALL" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/weather_widget_info" />
        </receiver>

5、第五步,其实这里已经结束了,由于该项目是在后台使用Service实施每10S更新一次,所以还要添加Service类

文件名:AppWidgetService.java
public class AppWidgetService extends Service {
    //更新widget的广播对应的action
    private final String ACTION_UPDATE_ALL = "com.llay.widget.UPDATE_ALL";
    //周期性更新widget的周期
    private static final int UPDATE_TIME = 10000;
    //周期性更新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) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    //线程中发送广播
    private class UpdateThread extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                count = 0;
                //一直在后台中发送广播,action为"com.llay.widget.UPDATE_ALL"
                while (true) {
                    count++;
                    Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
                    mContext.sendBroadcast(updateIntent);
                    Thread.sleep(UPDATE_TIME);
                }
            } catch (InterruptedException e) {
                //将InterruptedException定义在while循环之外,意味着抛出InterruptedException异常时,终止线程。
                e.printStackTrace();
            }
        }
    }
}

在AndroidManifest.xml中注册Service

<service android:name=".service.AppWidgetService">
        <intent-filter>
            <action android:name="android.appwidget.action.APP_WIDGET_SERVICE" />
        </intent-filter>
    </service>

说明:

(01) onCreate() 在创建服务时被执行。它的作用是创建并启动线程UpdateThread()。

(02) onDestroy() 在销毁服务时被执行。它的作用是注销线程UpdateThread()。

(03) 服务UpdateThread 每隔10秒,发送1个广播ACTION_UPDATE_ALL。广播ACTION_UPDATE_ALL在AppWidgetProvider被处理,用来更新widget中的数据。

总结:主要的核心代码

1、当创建一个Widget时,调用onUpdate()方法和onEnabled()方法。

onUpdage()方法,用于用户点击桌面上的Widget,可以开发应用。核心代码如下

for (int i = 0; i < appWidgetIds.length; i++) {
            //创建一个Intent对象,跳转到app界面
            Intent intent = new Intent(context, GetLocationActivity.class);
            //创建一个PendingIntent,包裹住intent
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
            //获取wiget布局
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_weather);
            remoteViews.setOnClickPendingIntent(R.id.widget_line, pendingIntent);
            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);

onEnabled()方法,用于新建第一个Widget时,发送Service对应的action,运行AppWidgetService类。核心代码如下

//第一个widget被创建时调用
    @Override
    public void onEnabled(Context context) {
        //在第一个widget被创建时,开启服务
        SERVICE_INTENT.setPackage("com.llay.admin.weather");
        context.startService(SERVICE_INTENT);
        super.onEnabled(context);
    }

2、运行Service,每10S发送BroadCast对应的action,核心代码如下

//线程中发送广播
    private class UpdateThread extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                count = 0;
                //一直在后台中发送广播,action为"com.llay.widget.UPDATE_ALL"
                while (true) {
                    count++;
                    Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
                    mContext.sendBroadcast(updateIntent);
                    Thread.sleep(UPDATE_TIME);
                }
            } catch (InterruptedException e) {
                //将InterruptedException定义在while循环之外,意味着抛出InterruptedException异常时,终止线程。
                e.printStackTrace();
            }
        }
    }

3、在AppWidgetProvider中的onReceive()方法中接收,BroadCast发送的action,执行相应的更新数据的方法。核心代码如下

//接收广播的回调函数
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        this.context = context;
        if (ACTION_UPDATE_ALL.equals(action)) {
            getDate = new GetDate();
            getDate.execute("http://apis.baidu.com/apistore/weatherservice/recentweathers");
        } else {
            super.onReceive(context, intent);
        }

    }

总结:思路

创建第一个Widget,发送action为”android.appwidget.action.APP_WIDGET_SERVICE”->

AndroidManifest.xml中收到此action,运行AppWidgetService.java文件->

AppWidgetService.java中每10S发送一个广播action为”com.llay.widget.UPDATE_ALL”->

在AppWidgetProvider.java中的onReceive()方法,接收这个广播action,执行更新->

删除最后一个Widget,停止Service

–本文参考自http://blog.csdn.net/sasoritattoo/article/details/17616597

时间: 2024-10-08 06:57:33

Android的Widget桌面应用学习的相关文章

Android开源项目SlidingMenu本学习笔记(两)

我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGVuZzB6aGFvdGFp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" > 点击Bluetooth能够切换到对应的

【原创】Android之Widget

简介 1 Android widget Android widget 也称为桌面插件,其是android系统应用开发层面的一部分,但是又有特殊用途,而且会成为整个android系统的亮点.Android中的AppWidget与google widget和中移动的widget并不是一个概念,这里的AppWidget只是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法. 2 AppWidget Framework 2.1 定义 Android系统增加了AppWidget 框架,用以支持widge

Android使用ksoap2-android调用WebService学习

转自 Android使用ksoap2-android调用WebService学习 之前主要做客户端UI交互,很少处理数据和接触服务端,但现在的移动设备根本不可能离得开网络连接,数据的交换.最近学习的是在android端如何去调用远程WebService,都说WebService是一种基于SOAP协议的远程调用标准,对于这个协议理解不深,知道webservice可以将不同操作系统平台.不同语言.不同技术整合到一块,android SDK没有直接调用webservice的库,最常用的是借助ksoap

(转).net开发者对android开发一周的学习体会

春节期间,相对比较闲,上班时也没什么事情做.利用这一周的时间,简单的学习了一下移动方面的开发.主要是针对android,其实我对IOS更感兴趣 (因为我用iphone),苦于暂时没有苹果电脑,只能把它放到以后学习.我的工作中暂时没有用到移动方面的开发,自己以前也一直做.net方面的应用.在这里以一个.net开发人员的思维记一下对android开发学习中的一点自己的心得和体会.初学者的胡言乱语,高手请一笑而过. 开发环境搭建 先下载JDK安装.对于android的SDK和eclipse,可以直接到

Android App Widget的简单使用

App Widget是一些桌面的小插件,比如说天气和某些音乐播放应用,放到桌面去的那部分: 例如: 实现步骤及代码如下: (1)首先,在AndroidManifest.xml中声明一个App Widget: (1)定义AppWidgetProviderInfo对象:为App Widget提供元数据,包括布局.更新频率等,这个对象定义在XML文件当中: 在res/xml文件夹中定义一个名为example_appwidget_info.xml的文件: (2)为App Widget指定样式和布局: 定

Android开源项目SlidingMenu的学习笔记(二)

在前面已经介绍了SlidingMenu的用法:Android开源项目SlidingMenu的学习笔记(一),接下来再深入学习下,根据滑出项的Menu切换到对应的页面 目录结构: 点击Bluetooth可以切换到相应的界面 关键代码 MainActivity.java package com.dzt.slidingmenudemo; import android.app.Fragment; import android.app.FragmentManager; import android.app

Android自复制传播APP原理学习(翻译)

 Android自复制传播APP原理学习(翻译) 1 背景介绍 论文链接:http://arxiv.org/abs/1511.00444 项目地址:https://github.com/Tribler/self-compile-Android 吃完晚饭偶然看到这篇论文,当时就被吸引了,马上翻译总结了一下.如有错误欢迎斧正. 该论文的研究出发点比较高大上这里我们就不多说了,简而言之就是想通过移动设备来实现一个自组网,在发生灾难的时候,手机之间能够自动传输关键数据,减少损失.整个目标通过设计一个能够

Android中关于JNI 的学习(五)在C文件中使用LogCat

事实上,本文是在Peter Jerde的How much information can be stored by ordering 52 playing cards文章基础上翻译.改编和扩展而来的.当然这是经过Jerde本人首肯的. 注意本文方法并非最优,也没有完全利用所有的信息空间,只是简单的尝试. 有数字的地方就有信息.所以扑克牌中保存信息不是什么新鲜事. PDF文档点这里:下载 原文(英文)点这里:访问 这里有两个DEMO. 编码DEMO,解码DEMO 首先是"DEEP IN SHALL

舌尖上的安卓(android触控事件机制学习笔记录)

对于一个"我们从来不生产代码,我们只是大自然代码的搬运工"的码农来说.对android的触控机制一直是模棱两可的状态,特别是当要求一些自定义的控件和androide的自带控件(比如ViewPager,ListView,ScrollView)高度嵌套在一起使用时. 花了点时间梳理了下,做个笔记.对于一个触控的事件从用户输入到传递到Actigvity到最外层的Viewgroup在到子View,中间过程还可能穿插多个Viewgroup,android在ViewGroup提供了3个方法来控制流