我一直在玩 Hilt 在一个小应用程序中对视图模型的支持,并且需要我的视图模型来启动共享活动:

@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
) : ViewModel() {

  // ... some code that invokes share()

  private fun share(content: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
      type = "text/plain"
      putExtra(Intent.EXTRA_TEXT, content)
    }
    val chooserIntent = Intent.createChooser(intent, "Share with…")

    val activity = TODO("Need an activity here!")
    activity.startActivity(chooserIntent)
  }
}

视图模型在活动配置更改中保留,因此活动不可注入,这完全有道理:在视图模型中注入活动会导致配置更改泄漏。

不幸的是,Activity该类提供了很多实用程序,因此需要访问它是相当普遍的。

大多数在线资源建议将代码移动到 Activity 或有权访问它的协作者,让其监听指示要执行的操作的事件,然后从 ViewModel 发送事件。

我不在乎这些“最佳”做法。我想要那个代码在它被使用的地方,我不想要不必要的解耦。

无论如何,这里有一点 Hilt 黑客技术可以在不更改任何Activity代码的情况下支持这一点。

首先,让我们创建一个CurrentActivityProvider作用域 to @ActivityRetainedScoped,它将负责保存当前的活动实例:

@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  // TODO Set and clear currentActivity
  private var currentActivity: Activity? = null

  fun <T> withActivity(block: Activity.() -> T) : T {
    checkMainThread()
    val activity = currentActivity
    check(activity != null) {
      "Don't call this after the activity is finished!"
    }
    return activity.block()
  }
}

然后我们可以根据需要使用它。请注意,这withActivity()使得将活动实例意外存储在错误的位置变得更加困难:

@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : ViewModel() {

  private fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}

CurrentActivityProvider.currentActivity现在我们需要为每个ActivityRetainedComponent范围进行设置。为此,我们创建了一个作用域为活动 ( ActivityComponent) 的入口点,它将提供对CurrentActivityProvider(位于父ActivityRetainedComponent作用域中的)的访问。入口点:

@EntryPoint
@InstallIn(ActivityComponent::class)
interface ActivityProviderEntryPoint {
  val activityProvider: CurrentActivityProvider
}

现在我们可以从一个活动实例中检索作用域活动提供者:

val entryPoint: ActivityProviderEntryPoint =
  EntryPointAccessors.fromActivity(this)
val activityProvider = entryPoint.activityProvider

这仅在活动是 Hilt 感知的情况下才有效,所以让我们检查它是否实现GeneratedComponentManagerHolder(🤫 它在 Hilt 的内部包中,但它也是公共的,所以🤷♂️),让我们为此制作一个小Activity.withProvider()实用程序:

activity.withProvider { activityProvider ->
  // TODO
}

    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -> Unit
    ) {
      if (this is GeneratedComponentManagerHolder) {
        val entryPoint: ActivityProviderEntryPoint =
          EntryPointAccessors.fromActivity(this)
        val provider = entryPoint.activityProvider
        provider.block()
      }
    }

注意:Android 应用可以同时有多个处于创建状态的活动。这里的代码通过依赖ActivityRetainedComponent范围来支持这一点,这将为堆栈中的每个活动提供一个新组件,但当通过配置更改重新创建活动时仍然返回相同的逻辑组件。

现在让我们添加方法来更新生命周期更改的活动引用:

@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  private var currentActivity: Activity? = null

  fun <T> withActivity(block: Activity.() -> T) : T { /* ... */  }

  companion object {
    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -> Unit
    ) { /* ... */ }

    fun onActivityCreated(activity: Activity) {
      activity.withProvider {
        currentActivity = activity
      }
    }

    fun onActivityDestroyed(activity: Activity) {
      activity.withProvider {
        if (currentActivity === activity) {
          currentActivity = null
        }
      }
    }
  }
}

最后让我们从我的Application班级挂钩生命周期回调:

@HiltAndroidApp
class MyCuteLittleApp : Application() {

  override fun onCreate() {
    super.onCreate()

    registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        CurrentActivityProvider.onActivityCreated(activity)
      }

      override fun onActivityDestroyed(activity: Activity) {
        CurrentActivityProvider.onActivityDestroyed(activity)
      }
    })
  }

}
有了这个,我们现在可以注入CurrentActivityProvider任何ActivityRetainedComponent范围(以及较低的范围)并轻松地使用activityProvider.withActivity().

测试测试 1 2 3 🎤

为了MyCuteLittleViewModel更容易测试,我们可以将共享责任转移给注入的协作者,例如Sharer:

interface Sharer {
  fun share(content: String)
}

class ActivitySharer @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : Sharer {
  override fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}

@Module
@InstallIn(ActivityRetainedComponent::class)
interface SharerModule {
  @Binds fun bindSharer(sharer: ActivitySharer): Sharer
}