Android 前台服务配置问题导致蓝牙对讲功能异常

文章目录

    基于 STM32 WB 蓝牙模块的开源 SDK 实现蓝牙对讲功能时,总是无法建立连接。
    而使用官方 APP ST BLE Sensor 或者我自己不用 SDK 实现的 App 都可以正常建立连接。

    最终发现是前台服务,及 Android 12 的兼容性问题引起的。做了下修改就可以正常运行了。

    连接异常

    我的状态异常:

    Lost connection with the node BVL-WB1

    而官方 APP 是在正常连接中显示:

    连接到

    对应的翻译文件:

    <string name="progressDialogConnTitle">Connecting..</string>
    <string name="progressDialogConnMsg">Connecting to: %s</string>
    <string name="progressDialogConnMsgLostNodeError">Lost connection with the node %s</string>
    

    显示代码在 ConnectionStatusView/ConnectionStatusView.java

    @Override
    public void showLostNodeError(String nodeName) {
    	final String error = String.format(getContext().getString(R.string.progressDialogConnMsgLostNodeError), nodeName);
    	mGuiThread.post(() -> showError(error));
    }
    

    ConnectionStatusView/ConnectionStatusController.java

    private Node.NodeStateListener mNodeStateListener = new Node.NodeStateListener() {
    	@Override
    	public void onStateChange(@NonNull Node node, @NonNull Node.State newState, @NonNull Node.State prevState) {
    		switch (newState){
    			case Connecting:
    				mView.showConnecting(node.getName());
    				break;
    			case Connected:
    				mView.showConnected();
    				break;
    			case Disconnecting:
    				break;
    			case Lost:
    				mView.showLostNodeError(node.getName());
    				break;
    			case Unreachable:
    				mView.showUnreachableNodeError(node.getName());
    				break;
    			case Dead:
    				mView.showDeadNodeError(node.getName());
    				break;
    			case Init:
    			case Idle:
    				break;
    		}
    	}
    

    为何状态会变成 Lost 呢

    日志中显示:

    BVL-WB1 Idle->Lost

    蓝牙模块有哪些状态

    com/st/BlueSTSDK/Node.java

    /** State of the node */
    public enum State {
    	/** dummy initial state */
    	Init,
    
    	/** the node is waiting for a connection, it is sending advertise message*/
    	Idle,
    
    	/** we open a connection with the node */
    	Connecting,
    
    	/** we are connected with the node, this status can be fired 2 times if we do a secure
    	 * connection using bt pairing */
    	Connected,
    
    	/** we are closing the node connection */
    	Disconnecting,
    
    	/** we saw the advertise message for some time but now we do not receive anymore*/
    	Lost,
    
    	/** we were connected with the node but now it is disappear without
    	 disconnecting */
    	Unreachable,
    
    	/** dummy final state */
    	Dead
    }//State
    

    建立连接的过程

    DemosActivity.java onResume

    if(!mNode.isConnected()){
    	mNode.addNodeStateListener(mBuildDemoAdapterOnConnection);
    	NodeConnectionService.connect(this,mNode, mConnectionOption);
    

    NodeConnectionService.java

    /**
     * start the service asking to connect with the node
     * @param c context used for start the service
     * @param n node to connect
     * @param option connection options, if no parameters present the default one will be used
     */
    static public void connect(Context c, Node n,@Nullable ConnectionOption option ){
    	Intent i = new Intent(c,NodeConnectionService.class);
    	i.setAction(CONNECT_ACTION);
    	i.putExtra(NODE_TAG_ARG,n.getTag());
    	if(option==null)
    		option = ConnectionOption.builder().build();
    	i.putExtra(CONNECTION_PARAM_ARG,option);
    	ContextCompat.startForegroundService(c,i);
    }
    

    ContextCompat.startForegroundService(c,i);

    调用之后,Service 对应的入口函数在哪里?

    Inside the service, usually in onStartCommand(), you can request that your service run in the foreground. To do so, call startForeground(). This method takes two parameters: a positive integer that uniquely identifies the notification in the status bar and the Notification object itself.

    在 onStartCommand 中打印日志,并没有显示。说明没有走这里。

    加个 onCreate 试试,也没有显示日志。

    感觉没有建立连接

    但是我用自己写的 BLE 工具就可以正常的建立连接。

    前台服务的 Manifest 配置

    白天没定位到问题,下班的班车上搜了一下关于前台服务 (Foreground Service) 的资料。在微信公众号上找到不少有用的文章。

    对照着,确实发现了几个问题:

    Manifest 文件增加配置:

    <!-- needed for the NodeConnectionService: Android 9 (API level 28) or higher -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    
    
    <!-- application -->
    <service
    	android:name=".NodeConnectionService"
    	android:enabled="true"
    	android:exported="false" />
    

    修改之后,就可以看到建立连接的状态显示了。

    唯一难以理解的是,为何之前没有加配置任何提示都没有。

    FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent

    java.lang.RuntimeException: Unable to start service com.sunzhongwei.bluevoice.NodeConnectionService@f2b942d with Intent { act=com.sunzhongwei.bluevoice.NodeConnectionService.CONNECT cmp=com.sunzhongwei.bluevoice/.NodeConnectionService (has extras) }: java.lang.IllegalArgumentException: com.sunzhongwei.bluevoice: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

    修复:

         private PendingIntent getDisconnectPendingIntent(Node n){
             Intent stopServiceIntent = buildDisconnectIntent(this,n);
             return PendingIntent.getService(this, STOP_SERVICE, stopServiceIntent,
    -                PendingIntent.FLAG_ONE_SHOT);
    +                //PendingIntent.FLAG_ONE_SHOT);
    +                PendingIntent.FLAG_IMMUTABLE
    +        );
         }
    

    这里的 pending intent 用于点击通知条上的断开连接按钮时触发。

    最后

    至此,功能终于调通了,顺便学习了 Service 及 Foreground Service 的使用。

    看来边动手写代码,边查,更适合我。

    参考

    • https://medium.com/androiddevelopers/all-about-pendingintents-748c8eb8619

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式