与Service实现双向通信(三)

  1. 与Service实现双向通信(一)
  2. 与Service实现双向通信(二)
  3. 与Service实现双向通信(三)

这是系列文章《与Service实现双向通信》的第三篇,也是完结篇。通过前两篇介绍,我们基本已经实现了Client到Service的调用了。但是Service怎么反过来调用到我们的Client端呢?

我们接着上篇的例子,我们希望能够实现,当有用户加入(join())或者离开(leave())的时候,能够通知客户端。

实现方法是,客户端注册一个回调到Service中,当Service有用户加入或者离开的时候,就调用此回调。因为普通的interface对象不能通过AIDL注册到Service中,我们需要定义一个AIDL接口,如下:

// IParticipateCallback.aidl
package com.race604.servicelib;

interface IParticipateCallback {  
    // 用户加入或者离开的回调
    void onParticipate(String name, boolean joinOrLeave);
}

同时,在IRemoteService.aidl中添加两个方法如下:

// IRemoteService.aidl
package com.race604.servicelib;

// 注意这里需要import
import com.race604.servicelib.IParticipateCallback;

interface IRemoteService {  
    ...

    void registerParticipateCallback(IParticipateCallback cb);
    void unregisterParticipateCallback(IParticipateCallback cb);
}

这里需要注意的是,需要import com.race604.servicelib.IParticipateCallback;

我们可以先设想一下,需要在Service的实现中,用一个List来保存注册进来的IParticipateCallback实例。你肯定很快就想到上一篇中说的问题,如果客户端意外退出的话,需要从List列表中删掉对应的实例。否则不仅浪费资源,而且在回调的时候,会出现DeadObjectException。当然,我们可以使用上一篇文章中的方法,传入一个IBinder对象,使用Link-To-Death回调。

幸运的是,这是一个典型的应用场景,Android SDK提供一个封装好的对象:RemoteCallbackList,帮我自动处理了Link-To-Death的问题,这就帮我们剩下了很多代码了。

下面来看我们的RemoteService的实现:

package com.race604.remoteservice;

import ...

public class RemoteService extends Service {  
    ...
    private RemoteCallbackList<IParticipateCallback> mCallbacks = new RemoteCallbackList<>();

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        ...
        @Override
        public void registerParticipateCallback(IParticipateCallback cb) throws RemoteException {
            mCallbacks.register(cb);
        }

        @Override
        public void unregisterParticipateCallback(IParticipateCallback cb) throws RemoteException {
            mCallbacks.unregister(cb);
        }

        @Override
        public void join(IBinder token, String name) throws RemoteException {
            ...
            // 通知client加入
            notifyParticipate(client.mName, true);
        }

        @Override
        public void leave(IBinder token) throws RemoteException {
            ...
            // 通知client离开
            notifyParticipate(client.mName, false);
        }
    };

}

我们在Service中用mCallbacks来保存回调列表,在注册和反注册IParticipateCallback回调的时候,只要调用mCallbacks.register(cb);mCallbacks.unregister(cb);即可。是不是非常容易?

然后我们来看怎么在用户加入或者退出的时候,怎么通知回调。上面的代码中,在join()leave()函数中分别都调用了notifyParticipate()函数,我们来看它的实现:

private void notifyParticipate(String name, boolean joinOrLeave) {  
    final int len = mCallbacks.beginBroadcast();
    for (int i = 0; i < len; i++) {
        try {
            // 通知回调
            mCallbacks.getBroadcastItem(i).onParticipate(name, joinOrLeave);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    mCallbacks.finishBroadcast();
}

这里我们也是使用一个循环,获取每个callback,然后调用onParticipate()。循环开始前,使用mCallbacks.beginBroadcast();,来准备开始通知Callbacks,此函数返回值是mCallbacks中回调对象个数。循环结束的时候,调用mCallbacks.finishBroadcast();来宣告完成。

另外,在Service销毁的时候,需要清除掉mCallbacks中的所有的对象,如下:

@Override
public void onDestroy() {  
    super.onDestroy();
    // 取消掉所有的回调
    mCallbacks.kill();
}

客户端使用IParticipateCallback的方法,只要实现IParticipateCallback.Stub即可,如下:

package com.race604.client;

import ...  
public class MainActivity extends ActionBarActivity {  
    ...
    private ArrayAdapter<String> mAdapter;
    private IParticipateCallback mParticipateCallback = new IParticipateCallback.Stub() {

        @Override
        public void onParticipate(String name, boolean joinOrLeave) throws RemoteException {
            if (joinOrLeave) {
                mAdapter.add(name);
            } else {
                mAdapter.remove(name);
            }
        }
    };
}

此时,如果有用户加入或者离开,客户端就能自动受到回调了。完整的代码请参考这个Commit

本系列文章的完整源代码托管在Github上,有需要的可以参考。

到这里,我们就实现了真正意义上的与Sevice实现双向通信。虽然写了三篇文章,但是其实也不是很复杂。本文只是涉及到Service的Binder应用。其实Binder机制是Android的精华之一,为了深入理解,可以学习一下其实现原理,可以参考大神罗升阳的《老罗的Android之旅》的系列博客,关于Binder,可以从这里开始看。

总结:

参考:

  1. Android Develops | RemoteMessengerServiceSample

Read more

Android 上的低功耗蓝牙实践

这是我在 Droidcon Beijing 2016 和 GDG Devfest 2016 上做的分享,以下是正文: Slide 01 我今天分享的主题是 Android 上低功耗蓝牙的实践。这个主题比较小众。我在过去的一年多的时间里,主要是在做低功耗蓝牙相关的开发。接触过程中发现,BLE 的开发和通常的 Android APP 的开发有点不一样,这里需要访问硬件资源,而且涉及到一些协议相关的内容,而且这方面的资料也比较少。今天我从 Android 开发者的角度,来分享一下低功耗蓝牙开发实践。 Slide 02 今天分享的内容,主要包含如下几个部分:首先对蓝牙和低功耗蓝牙做一个简单的介绍;然后介绍 Android 上对低功耗蓝牙的支持;再介绍一下在 Android 平台上可以开发哪些低功耗蓝牙应用;然后是,开发过程中,可以帮助我们调试的工具;最后,总结一下所谓的 “最佳实践”,低功耗蓝牙开发的一些小经验。 Slide

By Race604

React Native 触摸事件处理详解

触控是移动设备的核心功能,也移动应用交互的基础,Android 和 iOS 各自都有完善的触摸事件处理机制。React Native(以下简称 RN)提供了一套统一的处理方式,能够方便的处理界面中组件的触摸事件、用户手势等。本文尝试介绍 RN 中触摸事件处理。 1. RN 基本触摸组件 RN 的组件除了 Text,其他组件默认是不支持点击事件,也不能响应基本触摸事件,所以 RN 中提供了几个直接处理响应事件的组件,基本上能够满大部分的点击处理需求TouchableHighlight, TouchableNativeFeedback, TouchableOpacity 和 TouchableWithoutFeedback。因为这几个组件的功能和使用方法基本类似,只是 Touch 的反馈效果不一样,所以一般我们用 Touchable** 代替。Touchable** 有如下几个回调方法: * onPressIn:点击开始; * onPressOut:点击结束或者离开; * onPress:单击事件回调; * onLongPress:长按事件回调。 它们的基本使用方法如下,

By Race604

React Native 中 ScrollView 性能探究

1 基本使用 ScrollView 是 React Native(后面简称:RN) 中最常见的组件之一。理解 ScrollView 的原理,有利于写出高性能的 RN 应用。 ScrollView 的基本使用也非常简单,如下: <ScrollView> <Child1 /> <Child2 /> ... </ScrollView> 它和 View 组件一样,可以包含一个或者多个子组件。对子组件的布局可以是垂直或者水平的,通过属性 horizontal=true/false 来控制。甚至还默认支持“下拉”刷新操作。另外还有一个特别赞的特性,超出屏幕的 View 会自动被移除,从而节省资源和提高绘制效率。我们来看如下一个例子: class

By Race604

30 天入门 Android 开发, Google 与你一起圆梦

经常会有朋友让我推荐 Android 开发入门的教程或者视频,我一直是推荐看官方的教程。大部分人或者觉得比较迷茫,或者觉得坚持不下去。这次推荐这个《30 天入门 Android 开发》是 Google 亲自发起的免费教学,以学习小组方式,大家可以一起学习和交流。一个好的开始,是成功的一半。让 Google 工程师带领你一起进入多彩的 Android 开发大门。点击这里 报名。 Android 设备已经随处可见,你想尝试一下在 Android 设备上的开发和创新吗?快来跟随 Google 的步伐,一起学习 Android 入门课吧! Google Study Jams 活动介绍 Study Jams 是一个学习 Google 在线课程的活动。该活动由学员自主发起课程学习小组,带领小组成员入门 Android 开发,最终将

By Race604