Widget的使用和需要注意的问题

276 篇文章 5 订阅
252 篇文章 4 订阅

一.简单上手

1. 配置并显示widget

1.1 继承AppWidgetProvider

自定义MyWidgetProvider继承AppWidgetProvider,重写相关方法。

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        //当更新widget的时候会触发,添加的时候也会触发
    }
    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        super.onDeleted(context, appWidgetIds)
        //删除widget的时候会触发
    }
    override fun onDisabled(context: Context?) {
        super.onDisabled(context)
        //最后一个widget被删除的时候触发
    }
    override fun onEnabled(context: Context?) {
        super.onEnabled(context)
        //第一个widget被添加的时候触发
    }
    override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
        //当widget被第一次添加或者widget大小改变的时候触发
    }
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        //处理方式和普通广播一样
    }
}

AppWidgetProvider实质上就是一个广播,其中处理了相关的action并给出了回调方法。

1.2 配置appwidget-provider

找到res目录下的xml目录,若没有xml目录就新建一个,然后新建一个文件widget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/widget_layout"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="@dimen/dp_320"
    android:minHeight="@dimen/dp_110"
    android:previewImage="@mipmap/img_widget_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="5"
    android:widgetCategory="home_screen">
</appwidget-provider>

这里介绍下常用属性

  • android:initialLayout添加到桌面的widget布局
  • android:initialKeyguardLayout添加到锁屏页面的widget布局
  • android:minWidth最小宽度,通用计算方式: (N * 70)-30=宽度
  • android:minHeight最小高度,通用计算方式: (N * 70)-30=高度,宽度和高度的格数按照google标准是这样设置的,但是有很多厂家对Launcher重新定义,所以比如你设置的是5 * 1,但是某些手机上就会变成4 * 1。
  • android:previewImage预览图
  • android:resizeMode允许横向纵向拉伸
  • android:updatePeriodMillis刷新间隔,最小刷新间隔是半小时,设置小于半小时也会按半小时算,且这里还有一点要注意,并不是每过半小时就一定会准时刷新,受设备影响这个时间可能略有提前或延迟。还有当手机息屏后可能会进入休眠状态,在休眠状态时不会自动更新,当设备解锁从休眠状态恢复时会立即刷新widget。

1.3 配置AndroidManifest.xml

        <receiver
            android:name=".MyWidgetProvider"
            android:label="@string/app_widget_string">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>

在AndroidManifest.xml中需要配置一个广播接受者,其中固定的两个配置参数

  • <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>指定这个才能接收到widget更新。
  • android:name="android.appwidget.provider"告诉系统这个广播接受者是一个widget。
    还可以在intent-filter里面配置自定义的action,用法就和普通广播一样。

现在已经可以添加widget显示啦,显示的内容为widget_layout.xml里的布局,没错就是这么简单。

2. 更新widget

上面已经显示了widget,接下来就要给widget更新UI。
更新widget的UI是通过AppWidgetManager的updateAppWidget方法实例来更新的,我们可以通过AppWidgetManager.getInstance(context)来获取实例。updateAppWidget有三个重载方法。

  • updateAppWidget(ComponentName provider, RemoteViews views)
    指定要刷新widget的ComponentName和RemoteViews,通过AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)来刷新。举个例子,我在桌面第一页和第三页都添加了同一个widget,现在若点击其中一个的刷新按钮两个widget要同时都更新界面,这时就可以用这个方法。这个方法也是最常用来更新widget的方式,可以刷新添加到桌面的所有widget。一般来说,更新widget并不要求在AppWidgetProvider中进行,因为AppWidgetProvider本质上就是一个广播,只要通过指定remoteView和ComponentName,可在任何包含上下文的环境下更新widget。
  • updateAppWidget(int[] appWidgetIds, RemoteViews views)
    刷新部分指定的widget
  • updateAppWidget(int appWidgetId, RemoteViews views)
    刷新一个指定的widget

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        for (appWidgetId in appWidgetIds) {
            appWidgetManager.updateAppWidget(appWidgetId, remoteView)
        }
        //uploadWidget(context)
    }
    private fun uploadWidget(context: Context) { 
        val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
        val componentName = ComponentName(context, javaClass) 
        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)
    }
}

上面代码两种方式都能刷新全部widget

3. widget的点击

package com.example.kotlintest.widget

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.example.kotlintest.R

class MyWidgetProvider : AppWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        val intent = Intent(REFRESH_CLICK).apply {
            component = ComponentName(context, MyWidgetProvider::class.java)
        }
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            R.id.tv_refresh,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
        remoteView.setOnClickPendingIntent(R.id.tv_refresh, pendingIntent)
        uploadWidget(context,remoteView)
    }
    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        when (intent.action) {
            REFRESH_CLICK -> {
                //点击事件
            }
        }
    }
    private fun uploadWidget(context: Context,remoteView: RemoteViews) {
        val componentName = ComponentName(context, javaClass)
        AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteView)
    }
    companion object {
        const val REFRESH_CLICK = "com.example.kotlintest.action.CLICK_REFRESH"
    }
}

二. 开发widget中需要注意处理的点

1. 初始化问题

当widget刷新时,如果应用没有处于开启状态下,这时会创建APP进程并初始化Application,之后回调widget的onUpdate方法。然而这里会有一个问题,由于部分app为了性能优化,将部分初始化操作移动到了引导页或Main页面里了,这样当widget想使用某些功能时,由于只创建了Application,在引导页或main页面里进行初始化的那部分功能没有进行初始化,便会抛出各种异常。所以这里开发的时候需要重点检查一遍。

2. UI设置

  • 当添加widget出现小组件添加错误、显示失败等,优先检查xml布局是否正确,尤其是不能包含自定义View等。
  • 通过RemoteViews更新widget,可能每次更新都创建了一个RemoteViews对象,但是RemoteViews只是一个action集合,只代表你对systemServer端widget的操作,一旦通过RemoteViews更新过widget,有些步骤就可以不用重复设置(列如点击事件)
  • widget不支持动画,如果一定要实现动画,可以开子线程循环刷新bitmap。

3. 网络请求

尽量不要直接在AppWidgetProvider中进行网络请求,和耗时操作。

  • 在AppWidgetProvider中进行网络请求,当未开启APP情况下,会请求失败抛出SocketTimeoutException异常。这一点很重要,很多系统都会限制在后台程序里静态广播的网络请求。如果有需要,请开启Service,在Service中进行网络请求。
  • 由于AppWidgetProvider优先级很低,代表当前进程容易被系统回收,所以尽量不要再AppWidgetProvider中进行耗时操作,否则可能会出现AppWidgetProvider中的任务未执行完进程就已经被系统回收。建议耗时操作开启Service执行。

4. 定时任务

很大一部分app都有定时刷新widget的需求,而系统的刷新间隔要求大于等于30分钟,这显然是满足不了需求。这里有两种方案。

  1. 单独进程的前台service
  2. 通过JobScheduler
    如果对实时性要求不是太高,可以考虑使用JobScheduler

5. 关于Service通知问题

我们知道在Android8.0后开启Service需要指定为前台通知,这样就会有一个通知栏效果。如果在widget中想开启Service进行网络请求,而又不想出通知,可以使用bindService方式。
bindService是Context的方法,网上大部分文章都拿Activity做例子,导致很多人不知道bindService其实在Application等Context的子类中都能使用。



作者:爱是空白日记
链接:https://www.jianshu.com/p/a75dac9e8462
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值