AndroidX Activity 改动 及ActivityResultLauncher 简单修改

前言

google 在 androidX 上对 activity 及 fragment 部分功能调整(startActivityForResult ,requestPermission,Save/Restore InstanceState,onBackPress)简化为 callback 的可回调方式

使用(暂时还会修改,建议 beta 版再更)
 // build.gradle
 implementation 'androidx.activity:activity-ktx:1.2.0-alpha03'
 implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha03'

demo

1 构造方法直接传 layoutId

class MyActivity : AppCompatActivity(R.layout.my_activity)
class MyFragmentActivity: FragmentActivity(R.layout.my_fragment_activity)
class MyFragment : Fragment(R.layout.my_fragment)

2 BackPress

class MyFragment : Fragment() {
  override fun onAttach(context: Context) {
    super.onAttach(context)
    val callback = object : OnBackPressedCallback(true) {
      override fun handleOnBackPressed() {
        // Do something
      }
    }
    requireActivity().onBackPressedDispatcher.addCallback(this, callback)
  }
}

3 SaveInstance

class MyActivity : AppCompatActivity() {
  companion object {
    private const val MY_SAVED_STATE_KEY = "my_saved_state"
    private const val SOME_VALUE_KEY = "some_value"
  }  
  private lateinit var someValue: String
    
  private val savedStateProvider = SavedStateRegistry.SavedStateProvider {    
    Bundle().apply {
      putString(SOME_VALUE_KEY, someValue)
    }
  }
  
  override fun onCreate(savedInstanceState: Bundle?) {    
    super.onCreate(savedInstanceState)
    savedStateRegistry
      .registerSavedStateProvider(MY_SAVED_STATE_KEY, savedStateProvider)
  }
  
  fun someMethod() {
    someValue = savedStateRegistry
      .consumeRestoredStateForKey(MY_SAVED_STATE_KEY)
      ?.getString(SOME_VALUE_KEY)
      ?: ""
  }
}

4 Permission 请求

val requestPermission = prepareCall(RequestPermission()) { isGranted ->
                toast("Location granted: $isGranted")
            }
requestPermission(ACCESS_FINE_LOCATION)

5 FragmentFactory

class MyFragmentFactory : FragmentFactory() {

  override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
    // 调用loadFragmentClass()获得Class对象
    val fragmentClass = loadFragmentClass(classLoader, className)
    // 现在,您可以使用className / fragmentClass来确定您的首选方式
    // 实例化Fragment对象,然后在此处进行操作。  
    // 或者调用常规FragmentFactory来实例化Fragment
    // 没有参数的构造函数
    return super.instantiate(classLoader, className)
  }
}

6 kotlin 对 FragmentManager 提供操作符

// Before
supportFragmentManager
  .beginTransaction()
  .add(R.id.container, MyFragment::class.java, null)
  .commit()

// After
supportFragmentManager.commit {
  replace<MyFragment>(R.id.container)
}

7 FragmentContainerView

如果您正在使用 FrameLayout  作为 Fragment 的父级,您应该切换到 FragmentContainerView代替。
它修复了一些动画z索引顺序问题和窗口插入调度。
FragmentContainerView 从 AndroidX Fragment 1.2.0 开始可用。

最后我们看下 startActivityForResult 使用、原理及参考源码做的修改
使用:MainActivity -> SecondActivity

class MainActivity : BaseActivity(R.layout.activity_main) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        tvJump.setOnClickListener {
            prepareCall(StartSecondActivityForResultContract()){
                console("output",it)
            }.launch("input:哈哈")      
        }
    }
}

class StartSecondActivityForResultContract : ActivityResultContract<String, String>() {
    companion object {
        const val INPUT = "INPUT"
        const val OUTPUT = "OUTPUT"
    }
    override fun createIntent(context: Context, input: String?): Intent =
            Intent(context, SecondActivity::class.java).apply {
                putExtra(INPUT, input)
            }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        return when (resultCode) {
            Activity.RESULT_OK -> intent?.getStringExtra(OUTPUT)
            else -> null
        }
    }
}
class SecondActivity : AppCompatActivity(R.layout.activity_second) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        tvInput.text = intent?.getStringExtra(INPUT)?:"Null"
        btn.setOnClickListener {
            setResultAndFinish()
        }
    }
}

fun Activity.setResultAndFinish(){
    setResult(Activity.RESULT_OK, Intent().putExtra(OUTPUT," nice "))
    finish()
}

原理:观察者模式
1: prepareCall 会调用 ActivityResultRegistry 的 register 方法拿到 ActivityResultLauncher 对象
2: register 方法调用时 将 ActivityResultCallback 对象 登记在注册中心(ActivityResultRegistry) 的 Map 里
3: prepareCall 返回 ActivityResultLauncher对象后会调用 launch 方法
4: launch 方法会调用 ActivityResultRegistry 的抽象方法 invoke
5: 而 invoke 会 调用 startActivityForResult
6: 最终等 ComponentActivity 回调到 onActivityResult
7: 再通过 dispatchResult(requestCode, resultCode, data) 分发 result
8: 通过 requestCode 找到 key ,再从 Map 里 拿到 ActivityResultCallback 对象并调用 他的 onActivityResult( O result) 返回结果

以下是相关类图:
截屏2020-04-22下午8.47.17.png

上面 demo 代码并不简单,还需要创建 ActivityResultContract 实现类。虽然多了也可以像 google 的 ActivityResultContracts 那样管理,但对于我们还是只想知道
callback,不必关心 contracts。
于是我们可以根据这个思路做个精简版的 ActivityResultCallback

// MainActivity
prepareCall(ActivityResultCallback<String> { result ->
                console(
                    "output",
                    result ?: "Null"
                )
            }).launch(Intent(this, SecondActivity::class.java).apply {
                putExtra("INPUT", "hhhhh")
            })
// SecondActivity
class SecondActivity : BaseActivity(R.layout.activity_second) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        tvInput.text = intent?.getStringExtra("INPUT") ?: "Null"
        btn.setOnClickListener {
            setResult(" nice ")
            finish()
        }
    }
}

hh, 没有 contracts 并且不用关心 extras 的 key 名字了
code 地址
end...
1:BaseActivity 相当于ComponentActivity 重写了 onActivityResult / setResult 等方法
2: ActivityResultRegistry 提供注册 分发 result 等gong n
2:另外需注意使用场景,result 需基本数据类型和序列化对象,数据量不能超过1m
注册中心代码:

abstract class ActivityResultRegistry {

    private val requestCodeToCallback = SparseArray<ActivityResultCallback<*>>()
    private val code = AtomicInteger(0)

    fun <O> register(
        owner: LifecycleOwner,
        callback: ActivityResultCallback<O>
    ): ActivityResultLauncher {
        val requestCode = code.getAndIncrement()
        requestCodeToCallback.put(requestCode, callback)
        val lifecycle = owner.lifecycle
        lifecycle.addObserver(LifecycleEventObserver { _, event ->
            if (Lifecycle.Event.ON_DESTROY == event) {
                unregister(requestCode)
            }
        })
        return ActivityResultLauncher {
               intent -> invoke(requestCode, intent)
        }
    }

    abstract operator fun invoke(rc: Int, intent: Intent?)

    private fun unregister(requestCode: Int) {
        requestCodeToCallback.remove(requestCode)
    }

    fun dispatchResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
        val callback = requestCodeToCallback[requestCode] ?: return false
        doDispatch(resultCode, data, callback)
        return true
    }

    @Suppress("UNCHECKED_CAST")
    private fun <O> doDispatch(
        resultCode: Int, data: Intent?,
        callback: ActivityResultCallback<O?>?
    ) {
        if (resultCode == Activity.RESULT_OK)
            callback?.onGetResult(data?.extras?.get(OUTPUT_EXTRA) as O?)
    }
}

感谢:
How AndroidX changes the way we work with Activities and Fragments
A first look at AndroidX Activity Result APIs

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 本文重点介绍应用程序的启动过程,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细...
    天宇sonny阅读 387评论 1 0
  • 从源码的角度描述Activity的启动过程 Activity作为Android四大组件之一,也是我们平时开发中使用...
    淡雅如兰_往事随风阅读 319评论 0 0
  • Activity是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操...
    进攻的蚂蚁阅读 1,153评论 0 0
  • Activity启动流程 一、执行startActivity Activity.startActivity(Int...
    Joe_blake阅读 288评论 0 0
  • Activity https://developer.android.com/guide/components/a...
    XLsn0w阅读 696评论 0 4