当使用Android绑定的服务类型时,服务器(服务)是否可以向客户端(活动)发起请求



通过网络上的示例,我将以下测试应用程序与单个活动(MainActivity(和远程服务(MyRemoteService(组合在一起。通过MainActivity,我可以建立连接并发送一条消息,该消息计数递增,服务通过toast语句向用户显示。

在某个时候,我希望将我的蓝牙代码放在MyRemoteService中,并将应用程序中的活动中的命令发送到该服务,以扫描、连接、发现服务并设置通知。一旦BLE设备开始向MyRemoteService发送通知,MyRemoteService有没有办法通知我的活动有可用的新数据?

我只看到了活动(客户端(发送消息,服务(服务器(响应的范例。也就是说,我还没有看到一个使用带有消息的Android绑定服务的服务器启动请求的例子。这可能吗?

我宁愿不使用轮询来查看发生了什么。或者我应该使用不同的方法或服务类型来处理这个问题吗?最终目标是,我可以从服务接收通知(或消息或广播(,该通知可以从应用程序当前所在的任何活动中处理(似乎我必须在每个活动中设置某种类型的侦听器(。

我通过添加android:process=":myPrivateProcess;到AndroidManifest.xml中的服务标签(只需要一个以冒号开头的进程名(。我这样做是为了BLE代码(最终(不会导致活动中的UI代码被阻塞,因为从我所读到的内容来看,绑定服务默认在主(或UI(线程上运行。

下面是组成我的远程服务示例代码的四个感兴趣的文件。如果服务器启动的请求是可能的,我可以使用一些指针来实现它

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.remote_service_example" >
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.REMOTE_SERVICE_EXAMPLE" >
<activity
android:name=".MainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyRemoteService"
android:enabled="true"
android:exported="false"
android:process=":myPrivateProcess" />
</application>
</manifest>

activity_mail.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView_A"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remote Service Experiment"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/button_send"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Message"
app:layout_constraintBottom_toTopOf="@+id/button_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_A" />
<Button
android:id="@+id/button_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Service"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_send" />
</androidx.constraintlayout.widget.ConstraintLayout>

主要活动.kt

package com.example.remote_service_example
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import com.example.remote_service_example.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var myService: Messenger? = null
var isBound: Boolean = false
var msgCount: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val intent = Intent(applicationContext, MyRemoteService::class.java)
binding.buttonSend.setOnClickListener{
sendMessage()
}
binding.buttonStop.setOnClickListener{
if (isBound) {
Log.d("DBG","Sending unbindService command")
unbindService(myConnection)
isBound = false
} else {
Log.d("DBG","Service already unbound - command not sent")
}
}
bindService(intent, myConnection, Context.BIND_AUTO_CREATE)
}
private val myConnection = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
myService = Messenger(service)
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d("DBG","Entered onServiceDisconnected")
myService = null
isBound = false
}
}
private fun sendMessage() {
Log.d("DBG","Entered sendMessage - isBound = $isBound")
if (!isBound) return
++msgCount
val msg = Message.obtain()
val bundle = Bundle()
bundle.putString("MY_MSG", "Message $msgCount Received")
msg.data = bundle
try {
myService?.send(msg)
} catch (e: RemoteException) {
Log.d("DBG","Error sending message")
e.printStackTrace()
}
}
}

MyRemoteService.kt

package com.example.remote_service_example
import android.app.Service
import android.content.Intent
import android.os.*
import android.util.Log
import android.widget.Toast
class MyRemoteService : Service() {
inner class IncomingHandler: Handler(Looper.getMainLooper() ){
override fun handleMessage(msg: Message) {
Log.d("DBG","Entered remote handleMessage")
val data = msg.data
val dataString = data.getString("MY_MSG")
Toast.makeText(applicationContext, dataString, Toast.LENGTH_SHORT).show()
}
}
private val myMessenger = Messenger(IncomingHandler())
override fun onBind(intent: Intent): IBinder {
Log.d("DBG","Entered onBind")
return myMessenger.binder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Toast.makeText(applicationContext, "Service destroyed", Toast.LENGTH_SHORT).show()
}
}

是的,但根据您的体系结构,它可能会受到限制。如果您处于同一过程中,您可以通过Binder向服务传递一个接口,该接口可以在服务需要发送事件时调用。

如果你不在同一个过程中,你可以使用AIDL。这是一种痛苦,而且你可以传递的数据类型和数据大小有限

您也可以使用BroadcastReceiver,但这比AIDL更为有限,尽管设置起来更容易。这样做的优点是可以轻松地发送到多个客户端。

最新更新