啪!啪!给 JobIntentService 打针, Hilt 号的大针,看你爽不爽?哎呦,Espresso 看不到结果,用 UiAutomator 测。

这篇博客介绍了如何在Android应用中使用JobIntentService,结合MVVM和Dagger-Hilt进行服务管理,并通过Espresso和UiAutomator进行测试。作者详细阐述了JobIntentService的启动和关闭,权限管理,以及在Service中使用LiveData的问题和解决方案。同时,由于Espresso无法检测到Service的结果,文章转向使用UiAutomator进行更有效的测试。

0. 简介 Service

Service 不一定用得很长久,那不就成了长佣了吗?我们可以用 JobIntentService ——临时佣人,它跟你的 App 同生共死,真好!但是,启动容易,关闭就毫无头绪了。因为 Service 在后台跑,跟 UI 是不沾边的。如果用 MVVM,我们可以塞 LiveData 。通过方程启动,系统会弹出 “LiveData has not initialed”。如果用 Service 的构造函数,系统会说不接受参数。饶头啊,对不?

没关系,我们可以使用插入式,我提议的是 Dagger-Hilt ,给系统打针。


📦 1. MVVM 包

🌮 Gradle —— 资料库选择:

  • View Binding:
buildFeatures {
   
   
    viewBinding true
}
//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 种参数:

  1. Context
  2. Service class
  3. Job ID
  4. 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 观察和打针

👁‍🗨 观察点

我需要提供两个观察点:

  1. isRunning:服务状态。
  2. userInput:客户输入的内容。有两种方式 IntentExtra 和 LiveData 。我将会测试那种有保证。

🔆 app/ServiceApp.kt 提供 Hilt 应用

@HiltAndroidApp
class ServiceApp: Application()

🗡 di/LiveDataModule.kt

@Module
@InstallIn(SingletonComponent::class)
object LiveDataModule {
   
   

    @Provides
    @Singleton
    fun 
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值