
Android把你的程序放到桌面——桌面部件Widget
Android 桌面小部件是我们经常看到的,比如时钟、天气、音乐播放器等等。
它可以让 App 的某些功能直接展示在桌面上,极大的增加了用户的关注度。
首先纠正一个误区:
当 App 的小部件被放到了桌面之后,并不代表你的 App 就可以一直在手机后台运行了。该被杀,它还是会被杀掉的。
所以如果你做小部件的目的是为了让程序常驻后台,那么你可以死心了。
但是!!!
虽然它还是能被杀掉,但是用户能看的见它了啊,用户可以点击就打开我们的 APP,所以还是很不错的。
Android 桌面小部件可以做什么?
小部件可以做什么呢?也就是我们需要实现什么功能。
- 展示。每隔 N 秒/分钟,刷新一次数据;
- 交互。点击操作 App 的数据;
- 打开App。打开主页或指定页面。
这三个功能,大概就能满足我们绝大部分需求了吧。
实现桌面小部件需要什么?
如果你从来没有做过桌面部件,那肯定总是感觉有点慌,无从下手,毫无逻辑。
所以,实现它到底需要什么呢?
- **先声明 Widget 的一些属性。**在 res 新建 xml 文件夹,创建 appwidget-provider 标签的 xml 文件。
- 创建桌面要显示的布局。 在 layout 创建 app_widget.xml。
- **然后来管理 Widget 状态。**实现一个继承 AppWidgetProvider 的类。
- 最后在 AndroidManifest.xml 里,将 AppWidgetProvider类 和 xml属性 注册到一块。
- 通常我们会加一个 Service 来控制 Widget 的更新时间,后面再讲为什么。
做完这些,如果不出错,就完成了桌面部件。
其实挺简单的,下面就让我们来看看具体的实现吧。
实现一个桌面计数器
先上效果图:
1. 声明 Widget 的属性
在 res 新建 xml 文件夹,创建一个 app_widget.xml 的文件。
如果 res 下没有 xml 文件,则先创建。
app_widget.xml 内容如下:
属性的注释在上面写的很清楚了,这里需要说两点。
- 关于宽度和高度的数值定义是很有讲究的,在桌面其实是按照“格子”排列的。
看 Google 给的图。上面我们代码定义 110dp 也就是说,它占了2*2的空间。
- 第二点很重要。有个 updatePeriodMillis 属性,更新widget的时间间隔(ms)。
官方给提供了小部件的自动更新时间,但是却给了限制,你更新的时间必须大于30分钟,如果小于30分钟,那默认就是30分钟。
可以我们就是要5分钟更新啊,怎么办呢?
所以就不能使用这个默认更新,我们要自己来通过发送广播控制更新时间,也就是一开始总步骤里面第4步,加一个 Service 来控制 Widget 的更新时间,这个在最后一步添加。
2. 创建布局文件
在 layout 创建 app_widget.xml 文件。
这里要注意的就是 桌面部件并不支持 Android 所有的控件。
支持的控件如下:
3. 管理 Widget 状态
这里代码看起来可能有点多,先听我讲几个逻辑,再来看代码。
- Android 的各种东西都有自己的生命周期,Widget 也不例外,它有几个方法来管理自己的生命周期。
-
同一个小部件是可以添加多次的,所以更新控件的时候,要把所有的都更新。
-
onReceive() 用来接收广播,它并不在生命周期里。但是,其实 onReceive() 是掌控生命周期的。
如下是 onReceive() 父类的源码,右边是每个广播对应的方法。
上面我画的生命周期的图,也比较清楚。
然后我们再来看代码。
新建一个 WidgetProvider 类,继承 AppWidgetProvider。
主要逻辑在 onReceive() 里,其他的都是生命周期切换时,所处理的事情。
我们在下面分析 onReceive()。
onReceive(Context context, Intent intent)
它传了两个值回来,Context 是跳转、发广播用的。
我们用来判断的是 Intent ,这里用到了 Intent 的两种方式。
Intent 作为信息传递者。
它要把信息传给谁,可以有三个匹配依据:一个是action,一个是category,一个是data。
String ACTION_UPDATE_ALL = "com.lyl.widget.UPDATE_ALL";
这个最后会在 AndroidManifest.xml 里面注册时写进去。
当每隔 N 秒/分钟,就发送一次这个广播,更新所有UI。
intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)
是广播事件里携带的 Intent 里设置的,用来匹配。
点击“恢复”按钮,计数器清零。
然后是 updateAllAppWidgets() 这个方法,更新 UI。
更新 UI 用到了一个新东西——RemoteViews。
怎么来理解 RemoteViews 呢?
因为,桌面部件并不像平常布局直接展示,它需要通过某种服务去更新UI。但是我们的App怎么能去控制桌面上的布局呢?
所以就需要有一个中间人,类似传递者。
我告诉传递者,你让他把我的 R.id.widget_txt ,更新成 “hello world”。
你让他把我的 R.id.widget_btn_open 按钮点击之后去响应 PendingIntent 这件事。
RemoteViews 就是承担着一个这样的角色。
然后再去理解代码,是不是稍微好一点了?
4. 最后就是 Service 控制 Widget 的更新时间
说好的 当每隔 N 秒/分钟,就发送一次这个广播。
那到底在哪发呢?也就是我们刚开始说的,用 Service 来控制时间。
新建一个 WidgetService 类,继承 Service。代码如下:
在 onCreate 开启一个计时线程,每1秒发送一个广播,广播就是我们自己定义的类型。
5. 在 AndroidManifest.xml 注册 桌面部件 和 服务
然后就只剩最后一步了,注册相关信息
相应的注释都在上面,如果我们的App进程被杀掉,服务也被关掉,那就没办法更新UI了。
也可以再创建一个 BroadcastReceiver 监听系统的各种动态,来唤醒我们的通知服务,这就属于进程保活了。
至此,以上代码写完,如果不出问题,运行之后直接去桌面看小工具,我们的App就在里面了,可以添加到桌面。
对于需要定时更新的桌面部件,保证自己的服务在后台运行也是一件比较重要的事情。
这个我们还是可以好好做一下,毕竟用户都已经愿意把我们的程序放到桌面上,所以只要友好的引导用户给你一定的权限,存活概率还是很大。
再不济,让用户主动点开App,也不失为一种办法。
好的创意才能造就好的App,代码只是实现。
最后放上项目地址:
https://github.com/Wing-Li/Widget
- 感谢你赐予我前进的力量