Bluetooth LE(低功耗蓝牙) - 第二部分
回顾
在前面的文章中我们介绍了Bluetooth LE的背景也说明了我们在本系列文章中将要开发什么,但是还没有实际的代码。我们将在这篇文章中纠正这一点,我们将通过定义 Service/Activity 架构来确保蓝牙操作从UI中解耦。
Service 与 Activity 通信
在我们继续之前,我应该指出的是,我们不打算在这篇文章中去探究BLE的细节。起初,我们打算建立一个Activity并绑定Service,它将使我们能够把所有的蓝牙操作从UI中解耦,同时让我们从BLE接收到数据后更新UI。
要做到这一点,我们将使用Messenger模式。它能够帮助我们不通过任何直接的方法调用而实现两个组件之间的通信。 Messenger模式要求每个组件来实现自身的Messenger实现:当类的实例被创建后处理传入的Message对象。Activity和Service的实现将运行在UI线程,但是我们将确保他们在各自的方法调用上彼此透明。
通过实现他们各自的Messenger实现以及在相应地方的逻辑处理,使我们的代码更容易理解和维护。

public class BleService extends Service {
    public static final String TAG = "BleService";
    static final int MSG_REGISTER = 1;
    static final int MSG_UNREGISTER = 2;
    private final Messenger mMessenger;
    private final List<Messenger> mClients =
        new LinkedList<Messenger>();
    public BleService() {
        mMessenger = new Messenger(
            new IncomingHandler(this));
    }
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
    private static class IncomingHandler extends Handler {
        private final WeakReference<BleService> mService;
        public IncomingHandler(BleService service) {
            mService = new WeakReference<BleService>(service);
        }
        @Override
        public void handleMessage(Message msg) {
            BleService service = mService.get();
            if (service != null) {
                switch (msg.what) {
                    case MSG_REGISTER:
                        service.mClients.add(msg.replyTo);
                        Log.d(TAG, "Registered");
                        break;
                    case MSG_UNREGISTER:
                        service.mClients.remove(msg.replyTo);
                        Log.d(TAG, "Unegistered");
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    }
}

基本代码非常简单,但仍有一些细微之处值得去解释。
首先,InnerHandler 声明为静态。不要试图以一个非静态内部类来实现这个,否则会泄漏内存(leaking memory),这可能非常严重。这是因为一个类的非静态内部类可以持有一个该类的实例,并可以直接访问其成员变量和方法。简单来说,Java垃圾收集器将不会破坏被其他对象引用的对象(实际上比这更复杂一点,但也足够解释所发生的情况)。该Handler的父类实例是一个Service对象(一个Android Context),所以如果有任意一个对象持有该Handler实例的引用,都将隐式地阻止该Service对象被垃圾回收掉。这就是所谓的“Context leak (上下文泄漏)”。它也是一个非常糟糕的事情,因为上下文可能相当大。
(译者注:关于“Context Leak”更多的介绍请查看http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html)
我们避免上下文泄漏的方法是始终将内部类声明为静态,if they are declared within classes which subclass Context (不知道该怎么翻译)。 这也意味着我们已经失去了使用内部类的一个主要优点:访问父类属性和(or或)方法的能力。但我们可以很容易地通过使用WeakReference(弱引用)来克服这一点。弱引用可以使我们保持对一个对象的引用,并且不会阻止它被垃圾回收机制回收。
所以我们的InnerHandler类被构造成拥有一个包裹在WeakReference对象中的它父类的实例的引用。而不是直接拥有其父类的引用。这样InnerHandler可以调用 WeakReference 的get()方法获取其父类实例的引用。我们需要做一个null判断因为父类的实例如果被垃圾回收那么该引用将为空,但如果它不为空,那么我们可以以与非静态内部类完全相同的方式使用该实例的引用。
另一件值得一提的事是,我们目前有两种类型的消息:注册和取消注册。这允许多个用户订阅Service发出的信息(Service会从我们的BLE设备中获得更新信息)。在我们的示例应用程序中只有Activity将从Service中得到更新信息,但在现实世界中应用程序可能有更多的组件所需要的数据,所以发布/订阅模型是合适的。
我们的Activity:

public class BleActivity extends Activity {
    public static final String TAG = "BluetoothLE";
    private final Messenger mMessenger;
    private Intent mServiceIntent;
    private Messenger mService = null;
    private ServiceConnection mConnection =
        new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name,
            IBinder service) {
            mService = new Messenger(service);
            try {
                Message msg = Message.obtain(null,
                    BleService.MSG_REGISTER);
                if (msg != null) {
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                } else {
                    mService = null;
                }
            } catch (Exception e) {
                Log.w(TAG, "Error connecting to BleService",
                    e);
                mService = null;
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
    public BleActivity() {
        super();
        mMessenger = new Messenger(new IncomingHandler(this));
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ble);
        mServiceIntent = new Intent(this, BleService.class);
    }
    @Override
    protected void onStop() {
        if (mService != null) {
            try {
                Message msg = Message.obtain(null,
                    BleService.MSG_UNREGISTER);
                if (msg != null) {
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                }
            } catch (Exception e) {
                Log.w(TAG,
                    "Error unregistering with BleService",
                     e);
                mService = null;
            } finally {
                unbindService(mConnection);
            }
        }
        super.onStop();
    }
    @Override
    protected void onStart() {
        super.onStart();
        bindService(mServiceIntent, mConnection,
            BIND_AUTO_CREATE);
    }
    private static class IncomingHandler extends Handler {
        private final WeakReference<BleActivity> mActivity;
        public IncomingHandler(BleActivity activity) {
            mActivity =
                new WeakReference<BleActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            BleActivity activity = mActivity.get();
            if (activity != null) {
                //TODO: Do something
            }
            super.handleMessage(msg);
        }
    }
}

在Activity的onstart()和onstop()方法中绑定和解绑Service。在ServiceConnection方法(当Service绑定和解除绑定操作完成时回调)中,Activity‘s Messenger 则通过向Service Messenger 发送适当的消息实现注册和反注册。
之后我们需要在Manifest中添加适当的声明(我没有将它展示在这里,但在源码中可以看到) 。我们有一个简单的Activity和Service配对,他们能够互相通信。如果我们运行这个app,它什么都没做,但在logcat中显示了我们所期望的注册/注销工作:
com.stylingandroid.ble D/BleService﹕ Registered
com.stylingandroid.ble D/BleService﹕ Unegistered
所以现在我们拥有了一个app的框架,它使我们能够从Service中收集数据并更新我们的UI。
下期预告
抱歉,我们已经从上周没有包含代码的文章中脱离出来了,这周的文章中虽然包含代码却和本系列文章的主题(Bluetooth LE)不怎么相关。但是对学习BLE来现在是一个好的开始,在下一篇文章将开始把BLE与我们的发现结合在一起。
这篇文章的源代码可以在 这里 找到。
Bluetooth LE(低功耗蓝牙) - 第二部分的更多相关文章
- Bluetooth LE(低功耗蓝牙) - 第一部分
		
前言 在写这篇文章的时候,谷歌刚刚发布了Android Wear ,摩托罗拉也发布了 Moto 360 智能手表.Android Wear的API还是相当基本的,是很好的文档材料,而且还会不断的更新, ...
 - Bluetooth LE(低功耗蓝牙) - 第三部分
		
回顾 在本系列的前两篇文章中,我们已经了解了一些关于Bluetooth LE的背景并建立一个简单的Activity / Service框架. 在这篇文章中,我们将探讨Bluetooth LE的细节 ...
 - Bluetooth LE(低功耗蓝牙) - 第五部分
		
回顾: 在本系列前面的文章中我们完成了发现BLE传感器并与之建立连接.现在只剩下从其中获取数据了,但是这并没有看起来那么简单.在这篇文章中我们将讨论GATT的特点以及如何促进主机与传感器之间的数据交换 ...
 - Bluetooth LE(低功耗蓝牙) - 第四部分
		
回顾 在本系列前几篇文章中我们完成了BLE设备的发现 , 为我们的app通过BLE显示从TI SensorTag设备中获取到环境温度和湿度的工作打下了基础.在这篇文章中我们将着眼于连接到我们所发现的S ...
 - Bluetooth LE(低功耗蓝牙) - 第六部分(完)
		
在本系列前面的文章中我们已经了解了,在我们从一个TI SensorTag中获取温度和湿度数据之前,我们需要经历的各种步骤.在本系列中的最后一篇文章,我们将完成注册并接收SensorTag的通知,并接收 ...
 - Bluetooth Low Energy——蓝牙低功耗
		
Android4.3(API级别18)引入内置平台支持BLE的central角色,同时提供API和app应用程序用来发现设备,查询服务,和读/写characteristics.与传统蓝牙(Classi ...
 - Android使用BLE(低功耗蓝牙,Bluetooth Low Energy)
		
背景 在学习BLE的过程中,积累了一些心得的DEMO,放到Github,形成本文.感兴趣的同学可以下载到源代码. github: https://github.com/vir56k/bluetooth ...
 - 低功耗之战!ANT VS Bluetooth LE
		
利用近距离无线通信技术将手机及可穿戴式传感器终端等与智能电话连接起来,实现新的功能.最近,以此为目标的行动正在展开.其中备受关注的近距离无线方式是“ANT”和“Bluetooth LE”.为了在各种便 ...
 - BLE——低功耗蓝牙(Bluetooth Low Energy)
		
1.简介 以下蓝牙协议特指低功耗蓝牙协议. 蓝牙协议是由SIG制定并维护的通信协议,蓝牙协议栈是蓝牙协议的具体实现. 各厂商都根据蓝牙协议实现了自己的一套函数库——蓝牙协议栈,所以不同厂商的蓝牙协议栈 ...
 
随机推荐
- fseek()
			
原文地址:fseek()作者:xiaoxin 意思是把文件指针指向文件的开头 fseek 函数名: fseek 功 能: 重定位流上的文件指针 用 法: int fseek(FILE *s ...
 - 求职,找工作,平台大PK
			
国内 猎聘网:www.lietou.com 拉钩网:Lagou.com 智联招聘:www.zhaopin.com 前程无忧:http://www.51job.com/ 中华英才网:chinahr.co ...
 - linq按需查询
			
将不确定变成确定~LINQ查询两种写法,性能没有影响,优化查询应该是“按需查询” 如果在linq中希望进行一对多的复合查询时,请直接在查询中使用join into,或者使用let 关键字,当然在建立实 ...
 - hibernate 使用in方式删除数据
			
1当删除一个表中数据时,可能会涉及中间表,中间表会有多条数据.这时删除可以采用for循环,逐条删除.但是每次删除都会连接一次数据库 2.可以采用in语句,一次删除即可,参考如下博文 http://ne ...
 - oracle session 相关优化
			
导读: 同学们是不是都用遇到过这种情况,一个业务系统开发期业务并发量只是估算一个值,而系统上线后这个并发量可能会出现溢出或是不够的 情况.在这种情况下我们DBA怎么给出合理的性能优化建议呢?本文就 ...
 - ios 用LLDB查看模拟器文件路径以及一些常用的命令
			
我看网络上有好多有关lldb调试命令的介绍,我都看了一遍,都没有这个方法,所以我在这里补充出来,帮助需要的人. 另外附上一些 实用LLDB命令 我们可以使用e命令定义变量 (lldb) e NSStr ...
 - demo_04绘制三角形
			
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
 - Android 中 View移动总结:ViewDragHelper学习及用法详解
			
如上图简单呈现出两个方块后,提出一个需求: 1.拖动方块时,方块(即子View)可以跟随手指移动. 2.一个方块移动时,另一个方块可以跟随移动. 3.将方块移动到左边区域(右边区域)后放开(即手指离开 ...
 - PHP创建桌面快捷方式实例
			
要利用php创建桌面快捷方式我们需要借助于header,InternetShortcut及一些我看不懂的代码. 方法:新建一个php文件,然后把下面的代码扔进去,保存为比如shortcut.php,放 ...
 - Docker安装Gitlab
			
一.Ubuntu16.4上Docker安装Gitlab 1.安装docker 参见:https://docs.docker.com/engine/installation/linux/ubuntuli ...