这几天想做一个开机后自动启动的一个服务,结果android8.1以后不允许静态注册广播的开机自动启动,所以我就想用widget桌面挂件来做,这个服务本身就正常服务不涉及用户隐私。完全后台开启服务android有好多限制,后面会介绍的到。
androidstudio 快捷创建一个widget
快捷创建一个widget系统会自动创建一个widget继承了AppWidgetProvider类的java文件,layout界面的xml文件(用于ui的显示),一个配置widget的文件。下面详细对这几个配置文件讲一下
widget 中java文件注意事项
widget运行在系统的进程里,widget继承了AppWidgetProvider类,根据跟人需要在相应回调的地方写入相应方法实现自己的功能。
onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) //系统更新小部件是回调到这里
onDeleted(Context context, int[] appWidgetIds)//当用户删除小部件时,删除与之关联的偏好。
onEnabled(Context context)//进入相关功能时创建第一个小部件
onDisabled(Context context) //进入相关功能当最后一个部件是禁用的
widget主要任务是刷新窗口小部件的UI,但是正因为系统进程在这里面操作,刷新UI的事情并不是一件很容易的事,所以我们要建立一个服务,使用服务来刷新widget的界面。android8以后谷歌对后android进行了一些优化,一部分是针对后台服务的开启与关闭的。我在调试的时候就碰到了好多的困难。
在widget中正确开启服务的方法:只有使用startForegroundService()才能开启服务,开启服务后必须使用通知进行服务开启后对用户进行提示必须调用( startForeground(10, notification)😉,否则5秒钟后自动关闭服务,否则5秒钟后自动关闭服务,否则5秒钟后自动关闭服务。。(重要的事多说几遍)最恶心的是关闭服务并不报故障,为这事调了半天才找到原因。
@Override
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
Log.e("信息","刷新了小部件");
super.onUpdate(context, appWidgetManager, appWidgetIds);
Intent bindIntent = new Intent(context, WidgetService.class);
context.startForegroundService(bindIntent);//开启服务
}
怎么能正确使用进行服务开启后对用户进行通知提示呢?代码如下:(加上这段代码就不会5秒关闭服务了)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("信息", "onStartCommand()");
// 在API11之后构建Notification的方式
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
// 构建 Notification
Notification.Builder builder = new Notification.Builder(getApplicationContext());
builder.setContentTitle("控制管理")
.setSmallIcon(R.mipmap.ic_launcher)//设置图标
.setContentText("控制后台服务已经开启")
.setDefaults(Notification.DEFAULT_ALL);//设置振铃
// 兼容 API 26,Android 8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 第三个参数表示通知的重要程度,默认则只在通知栏闪烁一下
NotificationChannel notificationChannel = new NotificationChannel("AppTestNotificationId", "AppTestNotificationName", NotificationManager.IMPORTANCE_DEFAULT);
// 注册通道,注册后除非卸载再安装否则不改变
notificationManager.createNotificationChannel(notificationChannel);
builder.setChannelId("AppTestNotificationId");
}
// notificationManager.notify(10, builder.build());// 发出通知
Notification notification = builder.build(); // 获取构建好的Notification
startForeground(10, notification);// 开始前台服务
return super.onStartCommand(intent, flags, startId);
}
使用服务更新widget UI
废了半天劲终于可以开启更新服务了。name我们怎么跟他更新UI呢?使用RemoteViews这个类才能更新因为,RemoteViews这个类具有跨进程的能力。
但是我们的问题又来了,RemoteViews好多的控件和布局竟然不支持!!
支持的布局:
- AdapterViewFlipper
- FrameLayout
- GridLayout
- GridView
- LinearLayout
- ListView
- RelativeLayout
- StackView
- ViewFlipper
支持的控件: - AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextClock
- TextView
如果上面的控件满足不了我们的要求,首先想到的是自定义view,但是添加上自定义view了无法加载控件,说明也不支持。那我们有什么办法呢?我们可以用ImageView模拟出来具体看下面的一段代码,下面代码的意思就是画一个画展示出来
private void updateView(boolean state) {
RemoteViews rViews = new RemoteViews(getPackageName(), R.layout.new_app_widget);
rViews.setImageViewResource(R.id.widget_logo,R.mipmap.ic_launcher);//LOG显示
rViews.setImageViewBitmap(R.id.hardware_view,getDeskClockIcon());
// 刷新
AppWidgetManager manager = AppWidgetManager.getInstance(getApplicationContext());
ComponentName cName = new ComponentName(getApplicationContext(), NewAppWidget.class);
manager.updateAppWidget(cName, rViews);
}
private Bitmap getDeskClockIcon() {
//todo:好像是如果设置的比例不同拉伸的时候就会造成显示不全的问题
//todo:只要符合比应该就能填充满这个view 记得在XML里面设置imageView属性
int widget_width=500;
int widget_heigh=500;
Bitmap widget_window = Bitmap.createBitmap(widget_width, widget_heigh, Bitmap.Config.ARGB_4444);//创建一个空间这个空间就是widget的大小
Canvas canvas = new Canvas(widget_window);//widget窗口当做画布
Paint paint=new Paint();//准备一个画笔
//画一个边框
paint.setARGB(125,255,255,255);//隐形红色
paint.setStyle(Paint.Style.STROKE);//空心
paint.setStrokeWidth((float) 30.0);//设置线宽
RectF upperRectF=new RectF(0,0,widget_width,widget_width);//找到找到上半部分的绘图区
canvas.drawRoundRect(upperRectF, 80, 80, paint);//画圆角矩形
return widget_window;
}
如何设置点击按钮弹出一个activity?
// 需要开启的activity
Intent startActivityIntent=new Intent(this ,SettingsActivity.class);
// Intent实例化一个PendingIntent
PendingIntent Pintent= PendingIntent.getActivity(this, 0, startActivityIntent, 0);
RemoteViews ActivityView= new RemoteViews(getPackageName(), R.layout.new_app_widget);// 实例化RemoteView
ActivityView.setOnClickPendingIntent(R.id.widget_logo, Pintent); //设置点击事件
//更新控件
AppWidgetManager manager = AppWidgetManager.getInstance(getApplicationContext());
ComponentName cName = new ComponentName(getApplicationContext(), NewAppWidget.class);
manager.updateAppWidget(cName, ActivityView);
widge窗口的信息(xml)配置
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/new_app_widget"
android:initialLayout="@layout/new_app_widget"
android:minWidth="300dp"//窗口最小宽度
android:minHeight="200dp"//窗口最小高度
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="none"//是否可以改变窗口的大小 none代表不能
android:updatePeriodMillis="1800000" //更新时间最小也得是三十分,
android:widgetCategory="home_screen"></appwidget-provider>