深入探索通知与插件的实时刷新

  |   0 评论   |   2,519 浏览

在Android中, 除了本应用的视图外, 还允许操作远程视图(RemoteView). 其中包含两类实例, 一类是通知(Notification), 一类是插件(Widget), 这些都是附着于系统中, 通过广播更新页面. 系统为了避免频繁更新, 规定最低频率, 如果需要饶过这个机制, 则必须使用定时器(Alarm). 本文连接定时器与远程视图, 达到实时更新的目的, 两者都会涉及PendingIntent的使用. 本文包含源码.

本文源码的GitHub下载地址

插件

插件(Widget)实现两种功能, 一种是更新当前时间, 一种是更新启动状态, 需要注册两个IntentFilter. 使用Alarm定时器更新时间, 通过按钮控制启动状态.

注册

AppWidget(插件)在本质上是Receiver, 注册在AndroidManifest中也是, 不同的是定义特定的intent-filter, 即APPWIDGET_UPDATE.

<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>

并指定meta-datanameresourcename是固定的, resource指明所使用的资源信息.

<receiver android:name=".TimerAppWidget">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
        <action android:name="org.wangchenlong.timerappwidget.action.CHANGE_STATE" />
    </intent-filter>
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/timer_widget_provider"/>
</receiver>

AppWidget的描述信息.

<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/app_widget_timer"
    android:minHeight="52dp"
    android:minWidth="260dp"
    android:previewImage="@drawable/preview_widget"
    android:resizeMode="horizontal|vertical"/>

initialLayout: Widget的布局(Layout)文件.

minWidth & minHeight: 定义Widget的最小宽度和高度, 当数值不是桌面cell的整数倍时, 宽高会被增至最接近的cell大小.

previewImage: 当用户选择添加Widget时的预览图片. 如果未定义, 则展示应用的登录图标.

resizeMode: 在水平和竖直方向是否允许调整大小, 值可选: horizontal(水平方向), vertical(竖直方向), none(不允许调整).

定义

AppWidget继承于AppWidgetProvider, 而最终继承于BroadcastReceiveronUpdate负责更新显示数据.

public class TimerAppWidget extends AppWidgetProvider {  
  // 每次更新都会创建新的实例, 只能使用静态变量
    private static boolean sIsUpdate = false; // 是否启动更新时间
    private static int sImageIndex = 0;  
    private static long sUpdateImageLastTime = 0L; // 上次更新图片的时间

    @Override 
    public void onReceive(Context context, Intent intent) {    
        super.onReceive(context, intent);     
           if (intent.getAction().equals(CHANGE_STATE)) {
             sIsUpdate = !sIsUpdate;       
             if (sIsUpdate) {
                startUpdate(context); // 开始更新
             }else{
                stopUpdate(context); // 结束更新
             }
         }
    }   
     @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {
         super.onUpdate(context, appWidgetManager, appWidgetIds); 
         RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.app_widget_timer);
        ComponentName widget = new ComponentName(context, TimerAppWidget.class);

        Date date = new Date();  
        // 设置文字
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH);
        rv.setTextViewText(R.id.widget_tv_text, sdf.format(date)); // 设置文字

        // 更换图片
        long seconds = TimeUnit.MILLISECONDS.toSeconds(date.getTime());
        long interval = seconds - sUpdateImageLastTime;// 每隔5秒更换图片与图片文字
        if (sUpdateImageLastTime == 0 || interval % 5 == 0) {
            sImageIndex++;
            rv.setImageViewResource(R.id.widget_tv_image, mAvatars[sImageIndex % IMAGE_COUNT]); // 设置图片
            rv.setTextViewText(R.id.widget_tv_image_text, context.getString(mNames[sImageIndex % IMAGE_COUNT]));
            sUpdateImageLastTime = seconds;
        }        
        //点击头像跳转主页
        Intent mainIntent =new Intent(context,MainActivity.class);
        PendingIntent mainPi = PendingIntent.getActivity(context,0,mainIntent,FLAG_UPDATE_CURRENT);
        rv.setOnClickPendingIntent(R.id.widget_tv_image, mainPi); //设置点击事件

        // 开启或关闭时间的控制
        Intent changeIntent = new Intent(CHANGE_STATE);
        PendingIntent changePi = PendingIntent.getBroadcast(context,0,changeIntent,FLAG_UPDATE_CURRENT);
        rv.setOnClickPendingIntent(R.id.widget_b_control, changePi);
        // 更新插件
        appWidgetManager.updateAppWidget(widget, rv);
    }
}

获取当前的远程视图(RemoteViews)和组件信息(ComponentName), 最终用于更新插件, 即updateAppWidget.

RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.app_widget_timer);
ComponentName widget = new ComponentName(context, TimerAppWidget.class);
// ...
appWidgetManager.updateAppWidget(widget, rv);

RemoteViews中设置显示文字(TextView)与图片(ImageView)的控件, 或者点击事件. 注意, 事件触发使用PendingIntent广播.

rv.setTextViewText(R.id.widget_tv_text, sdf.format(date)); // 设置文字
rv.setImageViewResource(R.id.widget_tv_image, mAvatars[(int) interval % 4]); // 设置图片
Intent mainIntent = new Intent(context, MainActivity.class);
PendingIntent mainPi = PendingIntent.getBroadcast(context, 0, mainIntent, FLAG_CANCEL_CURRENT);
rv.setOnClickPendingIntent(R.id.widget_tv_image, mainPi); // 设置点击事件

更新

更新的延迟消息(PendingIntent), 获取组件与布局, 系统管理器更新插件, 获取插件ID组; 使用系统默认的插件活动ACTION_APPWIDGET_UPDATE与参数EXTRA_APPWIDGET_IDS设置延迟消息, 供定时器(AlarmManager)使用.

private PendingIntent getUpdateIntent(Context context, boolean isStart) {    
   // 获取当前的组件
    ComponentName widget = new ComponentName(context, TimerAppWidget.class);    
   // 获取布局, 并设置关闭显示
    RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.app_widget_timer);   
    if (isStart) {
        rv.setTextViewText(R.id.widget_b_control, context.getString(R.string.stop));
    } else {
        rv.setTextViewText(R.id.widget_b_control, context.getString(R.string.start));
    }    
   // 获取系统的AppWidgetManager
    AppWidgetManager awm = AppWidgetManager.getInstance(context);    
   // 更新页面组件
    awm.updateAppWidget(widget, rv);   
    int appWidgetIds[] = awm.getAppWidgetIds(widget);

    Intent alertIntent = new Intent(context, TimerAppWidget.class);
    alertIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // 设置更新活动
    alertIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); // 设置当前插件的ID
    return PendingIntent.getBroadcast(context, 0, alertIntent,
            FLAG_CANCEL_CURRENT); // 取消前一个更新
}

启动或关闭定时器, 每秒更新一次.

public void startUpdate(Context context) {
    AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);    
   // 设置更新
    am.setRepeating(AlarmManager.RTC, System.currentTimeMillis(),1000, getUpdateIntent(context, true));
}
public void stopUpdate(Context context) {
    AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    // 取消更新
    am.cancel(getUpdateIntent(context, false));
}

通知

通知实现与插件类似, 使用相同的控制广播, 保持同步.

定义

通知栏使用自定义布局, 设置头像跳转事件与状态修改事件, 发送相应的PendingIntent广播, 注意状态使用FLAG_UPDATE_CURRENT, 更新当前重复的Intent. 当与Widget的PendingIntent重复时, 进行相应的替换.

public static NotificationCompat.Builder getNotification(Context context) {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
    builder.setSmallIcon(R.drawable.avatar_jessica);
    builder.setWhen(System.currentTimeMillis());
    builder.setOngoing(true); // 始终存在

    // 开启或关闭时间的控制
    RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.notification_timer);
    // 点击更新状态按钮
    Intent changeIntent = new Intent(CHANGE_STATE);
    PendingIntent changePi = PendingIntent.getBroadcast(context, 0, changeIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);
     // 点击头像跳转主页
    Intent mainIntent = new Intent(context, MainActivity.class);
    PendingIntent mainPi = PendingIntent.getActivity(context, 0, mainIntent, FLAG_UPDATE_CURRENT);

    rv.setOnClickPendingIntent(R.id.widget_tv_image, mainPi); // 设置头像
    rv.setOnClickPendingIntent(R.id.widget_b_control, changePi); // 设置更新

    builder.setCustomContentView(rv);    
    return builder;
}

更新

收到通知后, 实时更新当前时间, 每隔5秒循环更新图片, 由于远程视图, 每次都会创建实例, 因此参数需要使用静态变量.

private void updateData(Context context) {
    NotificationCompat.Builder builder = getNotification(context);   
   // 获取布局, 并设置关闭显示
    RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.notification_timer);

    Date date = new Date();  
   // 设置文字
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH);
    rv.setTextViewText(R.id.widget_tv_text, sdf.format(date)); // 设置文字

    // 更换图片
    long seconds = TimeUnit.MILLISECONDS.toSeconds(date.getTime());  
    long interval = seconds - sUpdateImageLastTime; // 每隔5秒更换图片与图片文字
    if (sUpdateImageLastTime == 0 || interval % 5 == 0) {
        sImageIndex++;
        rv.setImageViewResource(R.id.widget_tv_image, mAvatars[sImageIndex % IMAGE_COUNT]); // 设置图片
        rv.setTextViewText(R.id.widget_tv_image_text, context.getString(mNames[sImageIndex % IMAGE_COUNT]));
        sUpdateImageLastTime = seconds;
    }

    builder.setCustomContentView(rv);
    Notification notification = builder.build();
    NotificationManager manager = (NotificationManager)
            context.getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(sId, notification);
}

动画


至此, 定时更新的插件与通知已经完成, 注意PendingIntent与定时器的使用方式. 这个实例对于开发插件与通知而言, 已经完全足够, 抛砖引玉.


转自:https://gold.xitu.io/post/5850adc11b69e6006c75ba22

>