蓝牙电话开发总结

一、 基本概念

HFP协议 :蓝牙电话控制协议,如拨号,接听,拒接,挂断等操作。车机上判断是否连接上蓝牙,主要就是判断HFP协议的连接状态。

PBAP协议:通讯录访问协议,主要指下载联系人,同步通话记录。

A2DP协议:蓝牙音频传输协议,用来传输蓝牙音乐数据。(蓝牙电话不涉及)

AVRCP协议:音视频远程控制协议,比如上下曲的控制。(蓝牙电话不涉及)

二、 功能需求

蓝牙设置:a.开关蓝牙 b.搜索可用设备 c.对搜索到的设备进行配对操作 d.对已配对的设备发起连接。

蓝牙电话:a.同步通讯录 b.同步通话记录 c.快速搜索联系人d.响应通话状态。

三、 系统整体架构

在这里插入图片描述

蓝牙厂商模块,包括蓝牙硬件设备以及配套的软件服务以及SDK。我们系统自己的蓝牙服务类似于一个中间件,它集成了厂商的SDK,负责直接跟蓝牙模块通信。同时也会进一步封装接口,提供给上层应用(蓝牙电话、蓝牙音乐、状态栏等)使用。蓝牙应用更多的是负责上层的业务逻辑和界面显示。他们之间都是通过binder实现IPC通信,值得一提的是,服务回调,是通过ContentObserve的方式触发的,在客户端接收到触发信号之后,再通过接口去获取数据。这样做使得回调消息更及时,同时避免了在回调接口中去传递数据而引发的问题。

四、 蓝牙电话应用架构

蓝牙电话采用MVP的架构会使得整个应用逻辑更加清晰,也便于后期需求的扩展。

在这里插入图片描述

五、 重点功能分析

1.三方通话

在通话双方接通之后,此时另外一个电话打进来,或者主动去拨打另外一个电话,这样的场景叫做三方通话。
当三方来电的时候,之前的通话处于active状态,新的来电处于waiting状态。这个时候可以选择:

a.挂断已经接通的通话

b.接听新的来电并且保持上一个通话,此时新的来电会变成active,而之前接通的通话会变成held状态

c.接听新的来电并且挂断上一个通话。

当主动去添加一个新的通话,即主动拨打第三方电话,此时上一个接通的电话会变成held,新的拨打电话则是dialing或者alerting状态。
新的通话接通后变成active状态,上一个通话被挂起变成held状态。此时可以操作切换通话来转换两个通话之间的状态。

难点:两个通话状态的管理,需要区分当前通话和非当前通话,界面需要不断地变换场景,同时分别要对两个通话做计时处理。

解决方法:通过两个PhoneCall对象来管理两个通话,一个是firstCall,代表当前通话;一个是secondCall,代表挂起的通话。在添加通话、三方来电、切换通话的时候,需要交换两个对象。界面采用动态增加删除view,并且调整Layout属性来适应界面场景变化。

2.联系人排序

从手机同步过来的联系人是无序的,我们要对联系人排序。但是联系人名字可能会是中文、英文、数字、其他语言的文字、特殊符号,以及它们的随机组合。

解决方法:使用正则表达式过标记出不符合规范的联系人,然后使用Comparator比较器对集合内容做比较。
首先进行中文的排序:

	Collator collator = Collator.getInstance(Locale.CHINA);
    int presult = collator.compare(o1.getName(),o2.getName());

但是会发现有极少数的名字没有按照汉字拼音的规则排好序,同时也不能解决多音字的问题。因此我们需要再对联系人做一次首字母的排序。

3.搜索联系人

联系人的搜索需要单独在子线程中进行,然后返回结果通知主线程刷新。搜索联系人的核心是匹配算法,这里也会用到正则表达式。
我们称用户输入的字符串为key。首先使用matcher.find去匹配联系人名字;如果没有找到,则使用matcher.lookingAt去匹配联系人的拼音首字母,例如林俊杰,首字母拼音是ljj;没匹配到,则matcher.lookingAt匹配全拼,即linjunjie;没匹配到,则判断key是否是数字,是则去匹配号码。

4. 异步操作变成同步操作

蓝牙电话中存在很多异步的操作,如:
a.点击同步联系人,会向模块发起同步联系人的请求,但是要等3s左右才会有信号反馈,程序才会进入到下载中的状态。
b.点击拨号操作,请求蓝牙模块拨打电话,手机收到请求信号才开始拨打电话,饭后把拨号状态反馈给蓝牙模块,模块再返回给蓝牙电话,再进入通话界面,这期间也是耗时的。
如果蓝牙电话只是等到信号反馈才做界面的响应,那么用户会觉得程序反应很慢,体验很差。

解决方法:应用程序提前进入反馈状态,以及时响应用户的操作。同时在程序内部也要增加一个意外检测机制,即在提前改变状态的时候,利用handler延迟发送一个对应的检测消息,如果在指定时间范围内没有收到模块反馈的信号,则需要做异常处理。

拨号的状态图:
在这里插入图片描述

5. 同步联系人头像并显示

联系人头像信息是在同步通讯录的时候,存在于联系人数据结构中的一个字段,以byte[]的形式存在。在联系人列表、通话界面、通话记录列表中,均会使用到头像信息,并且图片需要经过裁剪成圆形的方式显示。

难点:既然涉及到图片的解析和显示,那就必须考虑图片的异步加载、图片缓存还有内存管理。虽然可以简单除暴地使用glide来处理上述问题,但是glide框架太过于重型,我们这里仅仅只使用到其中一小部分,并且一些特定的业务场景,如图片裁剪功能,glide不能完全胜任。

解决方案:自定义一个ImageLoadUtil工具类,使用LruCache实现缓存并管理图片,并在内部启动一个子线程来实现查找对应联系人和加载的操作。

初始化的时候:

int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取最大内存
        int cacheSize = maxMemory / 8;//大小为最大内存的1/8
        cache = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        }; 

在子线程中实现一个Handler机制,去执行异步任务

poolThread = new Thread(){
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Looper.prepare();
                poolHandler = new PoolHandler();
                semaphore.release();
                Looper.loop();
            }
        };

        poolThread.start();

使用LinkedList实现一个栈,把需要异步加载的任务入栈,并且通过LIFO算法去执行任务。

private LinkedList<TaskHolder> tasks = = new LinkedList<>();
. . . . . .
tasks.add(new TaskHolder(number,srcbyte,imageView,width,height,defaultresId));
. . . . . . 
TaskHolder holder = tasks.getLast();

读取到图片之后,还要做裁剪的工作:

Bitmap circleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(circleBitmap);
        Paint paint = new Paint();
        Rect rect = new Rect(0, 0, width, height);
        RectF rectF = new RectF(rect);
        float roundPx = 0.0f;
        if (width > height) {
            roundPx = height / 2.0f;
        } else {
            roundPx = width / 2.0f;
        }
        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(Color.WHITE);

        //绘制圆弧矩形 roundPx是圆角的弧度
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        //这里是设置图像合成的模式
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

        canvas.drawBitmap(Bitmap.createScaledBitmap(srcBitmap, width, height, true), rect, rect, paint);

        cache.put(tag,circleBitmap);

最后还要回到UI线程中去更新ImageView的内容

class UIHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            if(msg.obj == null){
                return;
            }
            ImageHolder holder = (ImageHolder) msg.obj;
            if (holder.bitmap != null){
                holder.imageView.setImageBitmap(holder.bitmap);
            }else {
                holder.imageView.setImageResource(holder.defaultresId);
            }
        }
    }
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值