UaalでNative側で認証したFirebaseAuthのidTokenを取得する で別プロセスをやめたことによって問題が起きたので、プロセス間通信を試してみた。
起きた問題
- プロセスが一緒になったことでUnity側のunloadがうまく動かない
- OverrideUnityActivityをfinishするとアプリごと終了してしまう
やったこと
- Messengerを使いプロセス間の通信でFirebaseのidTokenを渡すようにした
イメージ図
Messengerを使ったプロセス間通信について
AIDL を使用するよりも簡単
サービスのマルチスレッド化が重要である場合は、AIDL を使用する
ほとんど のアプリにおいて、サービスはマルチスレッドを実行する必要がない
https://developer.android.com/guide/components/bound-services?hl=ja#Messenger より
実装
Manifestで別processにする
// AndroidManifest.xml
...
<service
android:name=".service.MessengerService"
android:enabled="true"
android:exported="false"/>
<activity
android:name=".ui.sub.SubActivity"
android:process=":Unity" /> <!-- 別processにする -->
...
Activityは何もしなくていい(Fragemtnでやる)
// SubActivity.kt
package dev.kyokomi.uaal.android.ui.sub
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dev.kyokomi.uaal.android.R
class SubActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sub_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, SubFragment.newInstance())
.commitNow()
}
}
}
onStartでbindServiceし、buttonをclickするとMessageをsendする
// SubFragment.kt
package dev.kyokomi.uaal.android.ui.sub
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import dev.kyokomi.uaal.android.R
import dev.kyokomi.uaal.android.service.MSG_SAY_HELLO
import dev.kyokomi.uaal.android.service.MessengerService
class SubFragment : Fragment() {
/** Messenger for communicating with the service. */
private var mService: Messenger? = null
private var replyToService: Messenger? = null
/** Flag indicating whether we have called bind on the service. */
private var bound: Boolean = false
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> {
Toast.makeText(applicationContext, "reply!", Toast.LENGTH_SHORT).show()
Log.d(TAG, msg.data.getString("idToken") ?: "<No Token>")
}
else -> super.handleMessage(msg)
}
}
}
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
mService = Messenger(service)
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
mService = null
bound = false
}
}
fun sayHello() {
if (!bound) return
// Create and send a message to the service, using a supported 'what' value
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
msg.replyTo = replyToService
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
...
}
// SubFragment.kt
...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val root = inflater.inflate(R.layout.sub_fragment, container, false)
replyToService = Messenger(IncomingHandler(requireContext()))
val button: Button = root.findViewById(R.id.button4)
button.setOnClickListener {
sayHello()
}
return root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(SubViewModel::class.java)
}
override fun onStart() {
super.onStart()
// Bind to the service
if (!bound) {
Intent(context, MessengerService::class.java).also { intent ->
requireActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
}
}
override fun onStop() {
super.onStop()
// Unbind from the service
if (bound) {
requireActivity().unbindService(mConnection)
bound = false
}
}
}
// MessengerService.kt
package dev.kyokomi.uaal.android.service
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.FirebaseAuth
/** Command to the service to display a message */
const val MSG_SAY_HELLO = 1
// https://developer.android.com/guide/components/bound-services?hl=ja#Messenger
class MessengerService : Service() {
companion object {
private val TAG = MessengerService::class.java.simpleName
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private lateinit var mMessenger: Messenger
/**
* Handler of incoming messages from clients.
*/
internal class IncomingHandler(
context: Context,
private var firebaseAuthIdToken: LiveData<String>,
private val applicationContext: Context = context.applicationContext,
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> {
Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
if (msg.replyTo != null) {
val replyMes = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
replyMes.data.putString("idToken", firebaseAuthIdToken.value)
msg.replyTo.send(replyMes)
}
}
else -> super.handleMessage(msg)
}
}
}
private var firebaseAuthIdToken = MutableLiveData<String>()
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
// FirebaseAuthのidTokenを取得してliveDataに設定しておく
FirebaseAuth.getInstance().addIdTokenListener { firebaseAuth: FirebaseAuth ->
Log.d(TAG, "firebase:addIdTokenListener")
firebaseAuth.currentUser?.getIdToken(false)?.addOnSuccessListener {
firebaseAuthIdToken.value = it.token ?: ""
Log.d(TAG, "firebase:${firebaseAuthIdToken}")
}
}
// idTokenのLiveDataをIncomingHandlerにわたしておく
mMessenger = Messenger(IncomingHandler(this, firebaseAuthIdToken))
return mMessenger.binder
}
}
所感
- AIDLはすごい面倒なイメージあったので、Messengerで楽にできた
- それぞれのprocessにServiceを置けば双方向の通信もできそう