美文网首页高级AndroidAndroid控件专题今日看点
深入探索通知与插件的实时刷新

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

作者: SpikeKing | 来源:发表于2016-12-05 17:53 被阅读584次

    欢迎Follow我的GitHub, 关注我的简书, 博客目录.

    Widget

    本文的合集已经编著成书,高级Android开发强化实战,欢迎各位读友的建议和指导。在京东即可购买:https://item.jd.com/12385680.html

    Android

    在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-datanameresource. name是固定的, 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, 而最终继承于BroadcastReceiver. onUpdate负责更新显示数据.

    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与定时器的使用方式. 这个实例对于开发插件与通知而言, 已经完全足够, 抛砖引玉.

    OK, that's all! Enjoy it!

    相关文章

      网友评论

      本文标题:深入探索通知与插件的实时刷新

      本文链接:https://www.haomeiwen.com/subject/mdzkmttx.html