0. 简介 Service
Service 不一定用得很长久,那不就成了长佣了吗?我们可以用 JobIntentService ——临时佣人,它跟你的 App 同生共死,真好!但是,启动容易,关闭就毫无头绪了。因为 Service 在后台跑,跟 UI 是不沾边的。如果用 MVVM,我们可以塞 LiveData 。通过方程启动,系统会弹出 “LiveData has not initialed”。如果用 Service 的构造函数,系统会说不接受参数。饶头啊,对不?
没关系,我们可以使用插入式,我提议的是 Dagger-Hilt ,给系统打针。
📦 1. MVVM 包
🌮 Gradle —— 资料库选择:
- View Binding:
buildFeatures {
viewBinding true
}
- Dagger Hilt ——请自学安装。
- ViewModel:
//region activity and fragment
// Activity and Fragment
def activity_version = "1.2.1"
implementation "androidx.activity:activity-ktx:$activity_version"
def fragment_version = "1.3.2"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
//endregion
这里随便提提,其实你们可以抄我以前写的,Gradle 太占地方了,所以省略一二。
…
🔰 MVVM —— 文件排列
🖐🏻 Helper —— 帮手
- helper/LogHelper.kt
import android.util.Log
const val TAG = "MLOG"
fun lgd(s:String) = Log.d(TAG, s)
fun lgi(s:String) = Log.i(TAG, s)
fun lge(s:String) = Log.e(TAG, s)
fun lgv(s:String) = Log.v(TAG, s)
fun lgw(s:String) = Log.w(TAG, s)
- helper/MessageHelper.kt
import android.content.Context
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import android.widget.Toast.LENGTH_SHORT
fun msg(context: Context, s: String, len: Int) =
if (len > 0) Toast.makeText(context, s, LENGTH_LONG).show()
else Toast.makeText(context, s, LENGTH_SHORT).show()
🖌️ 2. UI Design 平面设计
et_message :输入资料进Service。
tv_service :Service 的反应。
💼 3. JobIntentService
JobIntentService 是 IntentService 的改良版。
🐔 开始服务
这个服务是用方程启动的——enqueueWork
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, MyIntentService::class.java, JOB_ID, work)
}
这个 enqueueWork 有 4 种参数:
- Context
- Service class
- Job ID
- Intent
⌛️ 关闭服务
用 instance 关闭。
class MyIntentService: JobIntentService() {
init {
instance = this
}
companion object {
private lateinit var instance: MyIntentService
private val JOB_ID = 4343443
fun enqueueWork(context: Context, work: Intent) {
...}
fun stopService() {
lgd("MyIntentService: Service is stopping...")
instance.stopSelf()
}
}
}
你瞧,自己关自己。
🚪 4. Permission
📍 AndroidManifest.xml
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
...
<activity android:name=".ui.MainActivity">
...
</activity>
<service android:name=".service.MyIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true" />
</application>
…
✒️ ui/MainActivity.kt
// check manifests for permissions
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.WAKE_LOCK
)
class MainActivity : AppCompatActivity() {
// app permission
private val reqMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
permissions ->
permissions.entries.forEach {
lgd("mainAct: Permission: ${
it.key} = ${
it.value}")
if (!it.value) {
// toast
msg(this, "Permission: ${
it.key} denied!", 1)
finish()
}
}
}
// =============== Variables
// view binding
private lateinit var binding: ActivityMainBinding
// view model
val viewModel: MainViewModel by viewModels()
// =============== END of Variables
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// check app permissions
reqMultiplePermissions.launch(REQUIRED_PERMISSIONS)
}
companion object {
const val USER_INPUT = "USER_INPUT"
}
}
⌚5. Observables & Hilt 观察和打针
👁🗨 观察点
我需要提供两个观察点:
- isRunning:服务状态。
- userInput:客户输入的内容。有两种方式 IntentExtra 和 LiveData 。我将会测试那种有保证。
🔆 app/ServiceApp.kt 提供 Hilt 应用
@HiltAndroidApp
class ServiceApp: Application()
…
🗡 di/LiveDataModule.kt
@Module
@InstallIn(SingletonComponent::class)