初探Android N 那些让人感兴趣的东西--桌面提示未读信息

转载请注明出处:王亟亟的大牛之路

之前一旦时间觉得不知道看些什么学些什么还打游戏,有点颓废。然后想想总得继续学习,正好I/O大会刚结束,那就来看一些新东西

大篇幅安利:https://github.com/ddwhan0123/Useful-Open-Source-Android(我每天都在更啊!!)

平时总有一些小伙子们都在问,如何做到iOS的那些桌面图标显示未读,这次的官方Smaple给出了解决方式!!!看下图!!!!

乍看之下就是普通的Notification

然后点了查阅了这条Message返回桌面之后

看着Icon上面的数字,是不是很开熏?是不是就是一直想要的?

OK,我们来看下这东西在哪!

这里给出URL:https://developer.android.com/samples/MessagingService/index.html

这里给出下载地址:https://developer.android.com/downloads/samples/MessagingService.zip

如果Google的不行,用这个:https://github.com/googlesamples/android-MessagingService/archive/master.zip

下载好build之后是这样子的

我们来看下这个功能室如何使用的(还是老风格先学会用,暂时不太详细的看实现)

先解释下这个Sample程序是干嘛的?

This sample shows a simple service that sends notifications using NotificationCompat. It also extends the notification with Remote Input to allow Android N devices to reply via text directly from the notification without having to open an App. The same Remote Input object also allows Android Auto users to respond by voice when the notification is presented there. Note: Each unread conversation from a user is sent as a distinct notification.

机械翻译一下

此示例显示了一个简单的服务,使用NotificationCompat发送通知。它还扩展了远程输入的通知,让Android的N个器件通过文字直接从该通知,而无需打开应用答复。相同的远程输入对象还允许时通报提出有Android的汽车用户通过语音响应。注意:从用户的每个未读会话作为一个单独发送通知。

Sample业务流程大致如下:

绑定一个Service然后点击按钮发送消息然后创建了消息通知,当通知被处理ReplyReceiver接收conversationId再ReadReceiver通过MessageLogger记录每个事件,并将其显示TextView上。

来看下manifest文件的内容

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.messagingservice">

    <application android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">

        <meta-data android:name="com.google.android.gms.car.application"
                   android:resource="@xml/automotive_app_desc"/>

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".MessagingService">
        </service>

        <receiver
            android:name=".MessageReadReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_READ"/>
            </intent-filter>
        </receiver>

        <receiver
            android:name=".MessageReplyReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.example.android.messagingservice.ACTION_MESSAGE_REPLY"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>

2个receiver ,1个activity,1个service。

receiver用于用户操作接收

service作为通知发送

activity前台UI

这里要提一点的是以下部分

  <meta-data android:name="com.google.android.gms.car.application"
                   android:resource="@xml/automotive_app_desc"/>

这里应用了一个资源,我们点进去看看是什么

<automotiveApp>
    <uses name="notification"/>
</automotiveApp>

那加了这段东西能干吗呢?

应用程序要显示Android的自动概览屏幕上的通知(简单地说就是通知用)

我们来看一下具体是怎么操作的

先是UI(作者是activity里面贴了个Fragment)



public class MessagingFragment extends Fragment implements View.OnClickListener {

    private static final String TAG = MessagingFragment.class.getSimpleName();

    private Button mSendSingleConversation;
    private Button mSendTwoConversations;
    private Button mSendConversationWithThreeMessages;
    private TextView mDataPortView;
    private Button mClearLogButton;

    private Messenger mService;
    private boolean mBound;

    private final ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            mService = new Messenger(service);
            mBound = true;
            setButtonsState(true);
            Log.d(TAG,"--->onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
            mBound = false;
            setButtonsState(false);
            Log.d(TAG,"--->onServiceDisconnected");
        }
    };
//根据SharedPreference的变化来刷新UI
    private final SharedPreferences.OnSharedPreferenceChangeListener listener =
            new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            if (MessageLogger.LOG_KEY.equals(key)) {
                mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
            }
        }
    };

    public MessagingFragment() {
    }
    //获取控件
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_message_me, container, false);

        mSendSingleConversation = (Button) rootView.findViewById(R.id.send_1_conversation);
        mSendSingleConversation.setOnClickListener(this);

        mSendTwoConversations = (Button) rootView.findViewById(R.id.send_2_conversations);
        mSendTwoConversations.setOnClickListener(this);

        mSendConversationWithThreeMessages =
                (Button) rootView.findViewById(R.id.send_1_conversation_3_messages);
        mSendConversationWithThreeMessages.setOnClickListener(this);

        mDataPortView = (TextView) rootView.findViewById(R.id.data_port);
        mDataPortView.setMovementMethod(new ScrollingMovementMethod());

        mClearLogButton = (Button) rootView.findViewById(R.id.clear);
        mClearLogButton.setOnClickListener(this);

        setButtonsState(false);

        return rootView;
    }

    //模拟消息发送行为
    @Override
    public void onClick(View view) {
        if (view == mSendSingleConversation) {
            sendMsg(1, 1);
        } else if (view == mSendTwoConversations) {
            sendMsg(2, 1);
        } else if (view == mSendConversationWithThreeMessages) {
            sendMsg(1, 3);
        } else if (view == mClearLogButton) {
            MessageLogger.clear(getActivity());
            mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
        }
    }

    //绑定MessagingService
    @Override
    public void onStart() {
        super.onStart();
        getActivity().bindService(new Intent(getActivity(), MessagingService.class), mConnection,
                Context.BIND_AUTO_CREATE);
    }

    //注销监听
    @Override
    public void onPause() {
        super.onPause();
        MessageLogger.getPrefs(getActivity()).unregisterOnSharedPreferenceChangeListener(listener);
    }

//注册监听SharedPreference
    @Override
    public void onResume() {
        super.onResume();
        mDataPortView.setText(MessageLogger.getAllMessages(getActivity()));
        MessageLogger.getPrefs(getActivity()).registerOnSharedPreferenceChangeListener(listener);
    }

    //解绑监听
    @Override
    public void onStop() {
        super.onStop();
        if (mBound) {
            getActivity().unbindService(mConnection);
            mBound = false;
        }
    }

    //具体发送消息,传入2个参数,一个是通知条数,一个是通知内消息的条数
    private void sendMsg(int howManyConversations, int messagesPerConversation) {
        if (mBound) {
            Message msg = Message.obtain(null, MessagingService.MSG_SEND_NOTIFICATION,
                    howManyConversations, messagesPerConversation);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                Log.e(TAG, "Error sending a message", e);
                MessageLogger.logMessage(getActivity(), "Error occurred while sending a message.");
            }
        }
    }

    private void setButtonsState(boolean enable) {
        mSendSingleConversation.setEnabled(enable);
        mSendTwoConversations.setEnabled(enable);
        mSendConversationWithThreeMessages.setEnabled(enable);
    }
}

UI(Fragment)这边定义了一个ServiceConnection作为连接等行为的监听,在刚创建时就已经进行了相应的绑定,日志如下,其他就看注解吧

05-25 10:59:53.874 24203-24203/com.example.android.messagingservice D/MessagingFragment: --->onServiceConnected

用户点击发送某一种消息业务交由MessagingService处理

public class MessagingService extends Service {
    private static final String TAG = MessagingService.class.getSimpleName();
    private static final String EOL = "\n";
    private static final String READ_ACTION =
            "com.example.android.messagingservice.ACTION_MESSAGE_READ";
    public static final String REPLY_ACTION =
            "com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
    public static final String CONVERSATION_ID = "conversation_id";
    public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
    public static final int MSG_SEND_NOTIFICATION = 1;

    private NotificationManagerCompat mNotificationManager;

    private final Messenger mMessenger = new Messenger(new IncomingHandler(this));

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        mNotificationManager = NotificationManagerCompat.from(getApplicationContext());
    }

    //初始化时,将Messenger的行为交由了Handler处理
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return mMessenger.getBinder();
    }

    // Creates an intent that will be triggered when a message is marked as read.
    private Intent getMessageReadIntent(int id) {
        return new Intent()
                .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
                .setAction(READ_ACTION)
                .putExtra(CONVERSATION_ID, id);
    }

    // Creates an Intent that will be triggered when a voice reply is received.
    private Intent getMessageReplyIntent(int conversationId) {
        return new Intent()
                .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
                .setAction(REPLY_ACTION)
                .putExtra(CONVERSATION_ID, conversationId);
    }

    //发送通知
    private void sendNotification(int howManyConversations, int messagesPerConversation) {
        Conversations.Conversation[] conversations = Conversations.getUnreadConversations(
                howManyConversations, messagesPerConversation);
        for (Conversations.Conversation conv : conversations) {
            sendNotificationForConversation(conv);
        }
    }

    //发送通知谈话
    private void sendNotificationForConversation(Conversations.Conversation conversation) {
        // A pending Intent for reads
        PendingIntent readPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
                conversation.getConversationId(),
                getMessageReadIntent(conversation.getConversationId()),
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Build a RemoteInput for receiving voice input in a Car Notification or text input on
        // devices that support text input (like devices on Android N and above).
        RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REMOTE_REPLY)
                .setLabel(getString(R.string.reply))
                .build();

        // Building a Pending Intent for the reply action to trigger
        PendingIntent replyIntent = PendingIntent.getBroadcast(getApplicationContext(),
                conversation.getConversationId(),
                getMessageReplyIntent(conversation.getConversationId()),
                PendingIntent.FLAG_UPDATE_CURRENT);

        // 建立一个Android N兼容的远程输入启用行为。
        NotificationCompat.Action actionReplyByRemoteInput = new NotificationCompat.Action.Builder(
                R.drawable.notification_icon, getString(R.string.reply), replyIntent)
                .addRemoteInput(remoteInput)
                .build();

        // Create the UnreadConversation and populate it with the participant name
        // read and reply intents.
        UnreadConversation.Builder unreadConvBuilder =
                new UnreadConversation.Builder(conversation.getParticipantName())
                .setLatestTimestamp(conversation.getTimestamp())
                .setReadPendingIntent(readPendingIntent)
                .setReplyAction(replyIntent, remoteInput);

        // Note: Add messages from oldest to newest to the UnreadConversation.Builder
        StringBuilder messageForNotification = new StringBuilder();
        for (Iterator<String> messages = conversation.getMessages().iterator();
             messages.hasNext(); ) {
            String message = messages.next();
            unreadConvBuilder.addMessage(message);
            messageForNotification.append(message);
            if (messages.hasNext()) {
                messageForNotification.append(EOL);
            }
        }

        //构建NotificationCompat
        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext())
                .setSmallIcon(R.drawable.notification_icon)
                .setLargeIcon(BitmapFactory.decodeResource(
                        getApplicationContext().getResources(), R.drawable.android_contact))
                .setContentText(messageForNotification.toString())
                .setWhen(conversation.getTimestamp())
                .setContentTitle(conversation.getParticipantName())
                .setContentIntent(readPendingIntent)
                .extend(new CarExtender()
                        .setUnreadConversation(unreadConvBuilder.build())
                        .setColor(getApplicationContext().getResources()
                                .getColor(R.color.default_color_light)))
                .addAction(actionReplyByRemoteInput);

        MessageLogger.logMessage(getApplicationContext(), "Sending notification "
                + conversation.getConversationId() + " conversation: " + conversation);

//刷新通知内容
        mNotificationManager.notify(conversation.getConversationId(), builder.build());
    }

    /**
     * 处理来自客户端的消息。
     */
    private static class IncomingHandler extends Handler {
        private final WeakReference<MessagingService> mReference;

        IncomingHandler(MessagingService service) {
            mReference = new WeakReference<>(service);
        }

        @Override
        public void handleMessage(Message msg) {
            MessagingService service = mReference.get();
            switch (msg.what) {
                case MSG_SEND_NOTIFICATION:
                    int howManyConversations = msg.arg1 <= 0 ? 1 : msg.arg1;
                    int messagesPerConversation = msg.arg2 <= 0 ? 1 : msg.arg2;
                    if (service != null) {
                        service.sendNotification(howManyConversations, messagesPerConversation);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}

事件进行一系列的分发传给了2个Receiver

一个是我们点击Notification就会有回应的MessageReadReceiver

这里很明显一点,用户点了就清除这一条了

 NotificationManagerCompat.from(context)
                    .cancel(conversationId);

还有个是刷新Notification以及处理可能出现的交互行为的MessageReplyReceiver

主要实现行为如下

  notificationManager.notify(conversationId, repliedNotification);

然后SP内容变化了UI就变化了,这一个环路的一圈就走完了,主要实现就是Service里的一系列事件分发。

当然这里只是一个规范性的Demo,你自己的项目也许并不需要定义这么多东西,搞点内部类就好了。

总结:

读的过程中可能有点乱,但是大家主要看明白以下几点就好:

1 如何创建,初始化,使用一个NotificationManagerCompat

2 如何传递消息(Intent Messenger)

3 如何反馈行为 (各类Receiver)

当然还有Handler 以及其他一些刷新UI的方法,这里用的SP,你也可以不用SP,特别是异步行为时。

MessagingService里有英文注解的我就没翻译了,看原版的更好点(毕竟我翻译错了,就误人子弟了)

如果有时间的话我会去找一下别人封装好的Git内容,如果没得话我找时间造轮子吧。

这一篇就讲到这里,接下来一段时间都会去看 Android N的一些官方Sample,有疑问和意见的可以 微信我,微信在下面。

工作愉快!!

时间: 2024-10-12 22:14:47

初探Android N 那些让人感兴趣的东西--桌面提示未读信息的相关文章

看看iOS和安卓用户都对什么感兴趣

www.ithome.com:看看iOS和安卓用户都对什么感兴趣 移动,现在已经成为主流计算平台了,而在整个"移动战场"上,平台之战最重要的主角无疑就是iOS和Android两大系统.安卓系统赢得了群众,这主要归功于他们设备成本较低,便于普及:而苹果公司则赢得了高质量的消费者.现在的问题是,如何精确的总结归纳,才能让我们更好地去分析移动用户. Gravity研究显示,iOS用户对于媒体和消费者平台更感兴趣,比如这些人对Kindle Fire的感兴趣程度是平均水平的25.03倍,而对So

最近对数据挖掘感兴趣了,为什么国外的课程会这么好

贴上671coder的一篇帖子 : 原址见 :http://blog.csdn.net/liuqiyao_01/article/details/37904611 前言 事实上有许多的途径可以了解机器学习,也有许多的资源例如书籍.公开课等可为所用,一些相关的比赛和工具也是你了解这个领域的好帮手.本文我将围绕这个话题,给出一些总结性的认识,并为你由程序员到机器学习高手的蜕变旅程中提供一些学习指引. 机器学习的四个层次 根据能力可以将学习过程分成四个阶段.这也是一个有助于我们将所有学习资源进行分类的好

【Android开源项目解析】RecyclerView侧滑删除粒子效果实现——初探Android开源粒子库 Leonids

前两天在微博上看到了这个侧滑删除的粒子效果,但是只有IOS的,所以心血来潮,写了个玩玩,下面简单介绍下实现的思路 项目简介 实现原理解析 代码实现 如何使用 更多参考 项目简介 先不废话,上效果图 项目地址:https://github.com/ZhaoKaiQiang/ParticleLayout 实现原理解析 其实看了那么多的关于侧滑删除的项目,再来思考这个问题,就so easy了! 咱们先分析下需求: - 侧滑手势检测 - 粒子跟手效果 - 删除状态判断 - 数据源刷新 ok,知道需求了,

你写的并不是你真正感兴趣的!

买了一本讲电影剧本的书,翻开第一章却深深刺激了我,这是在说电影剧本吗?--这是在说游戏制作吗?!讲得实在太好了,在网上找到电子版,敬录如下: 你的剧本逊毙了! 作者: (美)威廉?M?埃克斯 第一场 构 思 □√ 1.你写的并不是你真正感兴趣的! 写那些让你深深着迷欲罢不能的东西,那些让你血液沸腾,让你午夜难以入眠,让你在鸡尾酒会上不顾场合热烈争论,甚至不惜和老友闹翻的东西. "写剧本将改变你的人生,就算你不能卖掉它,最起码你改变了你的人生." --约翰·特鲁比 我们应该读懂好莱坞传奇

【练习3.5】使用感兴趣区域(ROI)

2014-05-29 第三章 初探OpenCV 练习第5题: 题目:学习使用感兴趣区域(ROI).创建一个210*210的单通道图像并将其归0.在图中使用ROI和cvSet建立一个增长如金字塔状的数组,也就是:外部边界为0,下一个内部边界应该为20,在下一个内部边界为40,依此类推,直到最后内部值为200,所有边界应该为10个像素宽度.最后显示这个图形. 按照题目要求使用ROI和cvSet实现 #include "stdafx.h" #include "cv.h"

我对什么都感兴趣,可我迷茫了(转载)

我对什么都感兴趣,可我迷茫了 2013-12-05 余弦 懒人在思考 我收到一个同学给我的邮件问了个在我看来属于“太阳系”级的难题,比宇宙终极难题还差那么些^^ 他问: ----------------- 这几天一直挺困惑.说下我的问题,你有空的时候帮我解答下吧. 今天问自己个问题,找个自己的特长现在开始发展它. 基本上以后主要就靠这个特长工作. 但我不知道自己到底对什么非常感兴趣,并能足以支撑我发展研究下去. 我现在对很多都很有兴趣,这个好玩的程序,渗透个网站,XSS,偶尔挖洞,看见移动设备安

对必应实习生或者工作感兴趣的读者, 目前我的项目是...

网上有一些同学问我的项目和具体情况,  了解实习的机会, 等等.   这是我目前 (2014年春夏之交) 在必应团队具体管的项目: 中国区: 必应输入法 (PC 版本)  (安卓版) 必应词典客户端 (PC/Android/WP/iPhone/iPad/Surface) 必应搜索 App (iPhone/ iPad) 必应导航  (哈,  你不知道我们还做这个吧!) 必应读心机器人 必应缤纷桌面 ... 国际: 上面一些项目的国际版,  还有一些独特的 (内部) 服务和应用, 例如国际版的 Bi

【opencv入门之二】感兴趣区域ROI,线性混合addWeighted

参考网站: http://blog.csdn.net/poem_qianmo/article/details/20911629 1.感兴趣区域ROI //[2]定义一个Mat类型并给其设定ROI区域 Mat imageROI = srcImage1( Rect(200, 250, logoImage.cols, logoImage.rows )); //[3]加载掩摸(必须是灰度图) Mat mask = imread( "dota_logo.jpg", 0 ); //[4]将掩摸拷贝

获取图片中感兴趣区域的信息(Matlab实现)

内容提要 如果一幅图中只有一小部分图像你感兴趣(你想研究的部分),那么截图工具就可以了,但是如果你想知道这个区域在原图像中的坐标位置呢? 这可是截图工具所办不到的,前段时间我就需要这个功能,于是将其用Matlab实现. 其实只要用到Matlab中的两个函数: 函数: imrect 函数: getPosition 如果要截取其中的部分图像,就离不开下面的函数: 函数: imcrop 代码实现 clc; clear; close all; %-----------------------------