Android
优雅的封装ActivityResultLauncher
启动活动和请求权限
一般我们在请求权限或者处理Activity
返回结果的时候都会使用Activity#onActivityResult()
和Activity#onRequestPermissionResult()
这俩个方法,如今谷歌已经提供了新的请求方法:https://developer.android.com/training/permissions/requesting?hl=zh-cn。
我想把每个处理结果回调的代码块直接写在开始上代码,准备动手 首先解决这个ActivityResultLauncher.launch()
方法中(1)中的问题:ActivityResultLauncher
对象要用ActivityResultCaller#registerForActivityResult()
得到(2)中的问题:注册必须在Activity#onStart()
方法之前处理意外情况:Activity_A
启动另一个activity_B
,然后A
意外被kill
掉了,这时候从B
返回了,A
重新创建了,原先注册的地方不会执行回调
registerForActivityResult()
方法要在onStart()
之前调用
直接在
Activity#onCreate()
中拿到ActivityResultLauncher
对象,并且将回调写好
open class BaseActivityResultLauncher<I, O>(
caller: ActivityResultCaller,
contract: ActivityResultContract<I, O>
) {
private var launcher: ActivityResultLauncher<I>
private lateinit var callback: ActivityResultCallback<O>
init {
launcher = caller.registerForActivityResult(contract) { result ->
// ---- 1----
callback.onActivityResult(result)
}
}
// 在调用launch的时候把callback传入进去,然后结果回调会走 1 处代码
open fun launch(
input: I,
callback: ActivityResultCallback<O>
) {
this.callback = callback
launcher.launch(input)
}
}
定义一些常用的
ActivityResultLauncher
,可以看系统的ActivityResultContracts
,参考着抄一遍
class SinglePermissionLauncher(
caller: ActivityResultCaller
): BaseActivityResultLauncher<String, Boolean>(
caller,
ActivityResultContracts.RequestPermission()
)
class MultiPermissionsLauncher(
caller: ActivityResultCaller
): BaseActivityResultLauncher<Array<String>, MutableMap<String, Boolean>>(
caller,
ActivityResultContracts.RequestMultiplePermissions()
)
class StartActivityForResultLauncher(
caller: ActivityResultCaller
): BaseActivityResultLauncher<Intent, ActivityResult>(
caller,
ActivityResultContracts.StartActivityForResult()
)
现在开始封装(抽出来一个BaseActivity
)
BaseActivity {
private lateinit var singlePermissionLauncher: SinglePermissionLauncher
private lateinit var multiPermissionLauncher: MultiPermissionsLauncher
private lateinit var startForResultLauncher: StartActivityForResultLauncher
override fun onCreate(savedInstanceState: Bundle?) {
singlePermissionLauncher = SinglePermissionLauncher(this)
multiPermissionLauncher = MultiPermissionsLauncher(this)
startForResultLauncher = StartActivityForResultLauncher(this)
}
/**
* 请求单个权限
*
* @param permission String
* @param callback
*/
fun requestSinglePermission(
permission: String,
callback: (isGranted: Boolean, shouldShowRequestPermissionRationale: Boolean) -> Unit) {
singlePermissionLauncher.launch(permission) {
callback(it, shouldShowRequestPermissionRationale(permission))
}
}
/**
* 同时请求多个权限
*
* @param permissions Array
* @param callback
*/
fun requestMultiPermissions(
permissions: Array<String>,
callback: (
isAllGranted: Boolean,
grantedList: List<String>,
deniedList: List<String>,
alwaysDeniedList: List<String>
) -> Unit
) {
multiPermissionLauncher.launch(permissions) {
val grantedList = it.filterValues { it }.mapNotNull { it.key }
val leftoverList = (it - grantedList).map { it.key }
val deniedList = leftoverList.filter { shouldShowRequestPermissionRationale(it) }
callback(grantedList.size == it.size, grantedList,
deniedList, leftoverList - deniedList)
}
}
/**
* 代替 [AppCompatActivity.startActivityForResult]
*
* @param intent Intent
* @param callback ActivityResultCallback
*/
fun startActivityForResult(
intent: Intent,
callback: (requestCode: Int, data: Intent?) -> Unit
) {
startForResultLauncher.launch(intent) {
callback(it.resultCode, it.data)
}
}
}
使用
activity.requestMultiPermissions(permissions) { isAllGranted, _, _, _ ->
// 具体的回调代码逻辑
}
处理极端情况:原先发起请求的Activity
被意外Kill
了,我之前写的callback
咋办?
核心处理方法:自定义一个保存注册回调的注册表,然后仿照系统的Activity
处理流程把onStart()
之前注册的逻辑屏蔽掉,让我们的注册可以在onResume()之后,并且将处理意外情况要保存的数据保存到onSaveInstanceState()
中(androidx.activity.result.ActivityResultRegistry
)
// ================= 屏蔽这里的代码 ============================= /*if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is " + "attempting to register while current state is " + lifecycle.getCurrentState() + ". LifecycleOwners must call register before " + "they are STARTED."); }*/ // ================= 屏蔽这里的代码 =============================
抽象出来一个public abstract class LazyActivityResultRegistry { private static final String KEY_COMPONENT_ACTIVITY_REGISTERED_RCS = "KEY_COMPONENT_ACTIVITY_REGISTERED_RCS"; private static final String KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS = "KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS"; private static final String KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS = "KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS"; private static final String KEY_COMPONENT_ACTIVITY_PENDING_RESULTS = "KEY_COMPONENT_ACTIVITY_PENDING_RESULT"; private static final String KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT = "KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT"; private static final String LOG_TAG = "ActivityResultRegistry"; private static final int INITIAL_REQUEST_CODE_VALUE = 0x00010000; private Random mRandom = new Random(); private final Map<Integer, String> mRcToKey = new HashMap<>(); final Map<String, Integer> mKeyToRc = new HashMap<>(); private final Map<String, LifecycleContainer> mKeyToLifecycleContainers = new HashMap<>(); ArrayList<String> mLaunchedKeys = new ArrayList<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ final transient Map<String, CallbackAndContract<?>> mKeyToCallback = new HashMap<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ final Map<String, Object> mParsedPendingResults = new HashMap<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ final Bundle/*
*/ mPendingResults = new Bundle(); @MainThread public abstract <I, O> void onLaunch( int requestCode, @NonNull ActivityResultContract<I, O> contract, @SuppressLint("UnknownNullness") I input, @Nullable ActivityOptionsCompat options); @NonNull public final <I, O> ActivityResultLauncher<I> register( @NonNull final String key, @NonNull final LifecycleOwner lifecycleOwner, @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultCallback<O> callback) { Lifecycle lifecycle = lifecycleOwner.getLifecycle(); // ================= 屏蔽这里的代码 ============================= /*if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is " + "attempting to register while current state is " + lifecycle.getCurrentState() + ". LifecycleOwners must call register before " + "they are STARTED."); }*/ // ================= 屏蔽这里的代码 ============================= final int requestCode = registerKey(key); LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key); if (lifecycleContainer == null) { lifecycleContainer = new LifecycleContainer(lifecycle); } LifecycleEventObserver observer = new LifecycleEventObserver() { @Override public void onStateChanged( @NonNull LifecycleOwner lifecycleOwner, @NonNull Lifecycle.Event event) { if (Lifecycle.Event.ON_START.equals(event)) { mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract)); if (mParsedPendingResults.containsKey(key)) { @SuppressWarnings("unchecked") final O parsedPendingResult = (O) mParsedPendingResults.get(key); mParsedPendingResults.remove(key); callback.onActivityResult(parsedPendingResult); } final ActivityResult pendingResult = mPendingResults.getParcelable(key); if (pendingResult != null) { mPendingResults.remove(key); callback.onActivityResult(contract.parseResult( pendingResult.getResultCode(), pendingResult.getData())); } } else if (Lifecycle.Event.ON_STOP.equals(event)) { mKeyToCallback.remove(key); } else if (Lifecycle.Event.ON_DESTROY.equals(event)) { unregister(key); } } }; lifecycleContainer.addObserver(observer); mKeyToLifecycleContainers.put(key, lifecycleContainer); return new ActivityResultLauncher<I>() { @Override public void launch(I input, @Nullable ActivityOptionsCompat options) { mLaunchedKeys.add(key); Integer innerCode = mKeyToRc.get(key); onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options); } @Override public void unregister() { LazyActivityResultRegistry.this.unregister(key); } @NonNull @Override public ActivityResultContract<I, ?> getContract() { return contract; } }; } @NonNull public final <I, O> ActivityResultLauncher<I> register( @NonNull final String key, @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultCallback<O> callback) { final int requestCode = registerKey(key); mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract)); if (mParsedPendingResults.containsKey(key)) { @SuppressWarnings("unchecked") final O parsedPendingResult = (O) mParsedPendingResults.get(key); mParsedPendingResults.remove(key); callback.onActivityResult(parsedPendingResult); } final ActivityResult pendingResult = mPendingResults.getParcelable(key); if (pendingResult != null) { mPendingResults.remove(key); callback.onActivityResult(contract.parseResult( pendingResult.getResultCode(), pendingResult.getData())); } return new ActivityResultLauncher<I>() { @Override public void launch(I input, @Nullable ActivityOptionsCompat options) { mLaunchedKeys.add(key); Integer innerCode = mKeyToRc.get(key); onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options); } @Override public void unregister() { LazyActivityResultRegistry.this.unregister(key); } @NonNull @Override public ActivityResultContract<I, ?> getContract() { return contract; } }; } @MainThread final void unregister(@NonNull String key) { if (!mLaunchedKeys.contains(key)) { // Only remove the key -> requestCode mapping if there isn't a launch in flight Integer rc = mKeyToRc.remove(key); if (rc != null) { mRcToKey.remove(rc); } } mKeyToCallback.remove(key); if (mParsedPendingResults.containsKey(key)) { Log.w(LOG_TAG, "Dropping pending result for request " + key + ": " + mParsedPendingResults.get(key)); mParsedPendingResults.remove(key); } if (mPendingResults.containsKey(key)) { Log.w(LOG_TAG, "Dropping pending result for request " + key + ": " + mPendingResults.<ActivityResult>getParcelable(key)); mPendingResults.remove(key); } LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key); if (lifecycleContainer != null) { lifecycleContainer.clearObservers(); mKeyToLifecycleContainers.remove(key); } } public final void onSaveInstanceState(@NonNull Bundle outState) { outState.putIntegerArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_RCS, new ArrayList<>(mKeyToRc.values())); outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS, new ArrayList<>(mKeyToRc.keySet())); outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS, new ArrayList<>(mLaunchedKeys)); outState.putBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS, (Bundle) mPendingResults.clone()); outState.putSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT, mRandom); } public final void onRestoreInstanceState(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { return; } ArrayList<Integer> rcs = savedInstanceState.getIntegerArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_RCS); ArrayList<String> keys = savedInstanceState.getStringArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS); if (keys == null || rcs == null) { return; } mLaunchedKeys = savedInstanceState.getStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS); mRandom = (Random) savedInstanceState.getSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT); mPendingResults.putAll( savedInstanceState.getBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS)); for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); if (mKeyToRc.containsKey(key)) { if (!mPendingResults.containsKey(key)) { mRcToKey.remove(newRequestCode); } } bindRcKey(rcs.get(i), keys.get(i)); } } @MainThread public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) { String key = mRcToKey.get(requestCode); if (key == null) { return false; } mLaunchedKeys.remove(key); doDispatch(key, resultCode, data, mKeyToCallback.get(key)); return true; } @MainThread public final <O> boolean dispatchResult(int requestCode, @SuppressLint("UnknownNullness") O result) { String key = mRcToKey.get(requestCode); if (key == null) { return false; } mLaunchedKeys.remove(key); CallbackAndContract<?> callbackAndContract = mKeyToCallback.get(key); if (callbackAndContract == null || callbackAndContract.mCallback == null) { // Remove any pending result mPendingResults.remove(key); // And add these pre-parsed pending results in their place mParsedPendingResults.put(key, result); } else { @SuppressWarnings("unchecked") ActivityResultCallback<O> callback = (ActivityResultCallback<O>) callbackAndContract.mCallback; callback.onActivityResult(result); } return true; } private <O> void doDispatch(String key, int resultCode, @Nullable Intent data, @Nullable CallbackAndContract<O> callbackAndContract) { if (callbackAndContract != null && callbackAndContract.mCallback != null) { ActivityResultCallback<O> callback = callbackAndContract.mCallback; ActivityResultContract<?, O> contract = callbackAndContract.mContract; callback.onActivityResult(contract.parseResult(resultCode, data)); } else { // Remove any parsed pending result mParsedPendingResults.remove(key); // And add these pending results in their place mPendingResults.putParcelable(key, new ActivityResult(resultCode, data)); } } private int registerKey(String key) { Integer existing = mKeyToRc.get(key); if (existing != null) { return existing; } int rc = generateRandomNumber(); bindRcKey(rc, key); return rc; } private int generateRandomNumber() { int number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; while (mRcToKey.containsKey(number)) { number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1) + INITIAL_REQUEST_CODE_VALUE; } return number; } private void bindRcKey(int rc, String key) { mRcToKey.put(rc, key); mKeyToRc.put(key, rc); } private static class CallbackAndContract<O> { final ActivityResultCallback<O> mCallback; final ActivityResultContract<?, O> mContract; CallbackAndContract( ActivityResultCallback<O> callback, ActivityResultContract<?, O> contract) { mCallback = callback; mContract = contract; } } private static class LifecycleContainer { final Lifecycle mLifecycle; private final ArrayList<LifecycleEventObserver> mObservers; LifecycleContainer(@NonNull Lifecycle lifecycle) { mLifecycle = lifecycle; mObservers = new ArrayList<>(); } void addObserver(@NonNull LifecycleEventObserver observer) { mLifecycle.addObserver(observer); mObservers.add(observer); } void clearObservers() { for (LifecycleEventObserver observer: mObservers) { mLifecycle.removeObserver(observer); } mObservers.clear(); } } }
Activity
abstract class LazyRegisterActivity : FragmentActivity(), INoteActivity {
private val mActivityCallbacks = mutableListOf<INoteActivity.ActivityCallbacks>()
var isLoadComplete: Boolean by Delegates.observable(false) { _, old, new ->
if (new != old) {
pendingResultMap.forEach { (_, data) ->
noteActivityResultRegistry.dispatchResult(data.requestCode,
data.resultCode, data.data)
}
pendingResultMap.clear()
}
}
private val nextLocalRequestCode = AtomicInteger()
private data class ResultData(val requestCode: Int, val resultCode: Int, val data: Intent?)
private val pendingResultMap = mutableMapOf<Int, ResultData>()
// copy from ComponentActivity
private val customActivityResultRegistry: LazyActivityResultRegistry = object : LazyActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?) {
val activity: ComponentActivity = this@LazyRegisterActivity
// Immediate result path
val synchronousResult = contract.getSynchronousResult(activity, input)
if (synchronousResult != null) {
Handler(Looper.getMainLooper()).post { dispatchResult(requestCode, synchronousResult.value) }
return
}
// Start activity path
val intent = contract.createIntent(activity, input)
var optionsBundle: Bundle? = null
// If there are any extras, we should defensively set the classLoader
if (intent.extras != null && intent.extras!!.classLoader == null) {
intent.setExtrasClassLoader(activity.classLoader)
}
if (intent.hasExtra(ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE)) {
optionsBundle = intent.getBundleExtra(ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE)
intent.removeExtra(ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE)
} else if (options != null) {
optionsBundle = options.toBundle()
}
when {
ActivityResultContracts.RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS == intent.action -> {
// requestPermissions path
var permissions = intent.getStringArrayExtra(ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSIONS)
if (permissions == null) {
permissions = arrayOfNulls(0)
}
ActivityCompat.requestPermissions(activity, permissions, requestCode)
}
ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST == intent.action -> {
val request: IntentSenderRequest = intent.getParcelableExtra(ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST)!!
try {
// startIntentSenderForResult path
ActivityCompat.startIntentSenderForResult(activity, request.intentSender,
requestCode, request.fillInIntent, request.flagsMask,
request.flagsValues, 0, optionsBundle)
} catch (e: SendIntentException) {
Handler(Looper.getMainLooper()).post {
dispatchResult(requestCode, RESULT_CANCELED,
Intent().setAction(ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST)
.putExtra(ActivityResultContracts.StartIntentSenderForResult.EXTRA_SEND_INTENT_EXCEPTION, e))
}
}
}
else -> {
// startActivityForResult path
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle)
}
}
}
}
companion object {
private const val ACTIVITY_RESULT_TAG = "note:activity-result"
}
override fun registerActivityCallbacks(callback: INoteActivity.ActivityCallbacks) {
synchronized(mActivityCallbacks) { mActivityCallbacks.add(callback) }
registerActivityLifecycleCallbacks(callback)
}
override fun unregisterActivityCallbacks(callback: INoteActivity.ActivityCallbacks) {
synchronized(mActivityCallbacks) { mActivityCallbacks.remove(callback) }
unregisterActivityLifecycleCallbacks(callback)
}
@CallSuper
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
synchronized(mActivityCallbacks) {
mActivityCallbacks.forEach {
it.onMultiWindowModeChanged(this, isInMultiWindowMode, newConfig)
}
}
}
@CallSuper
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
synchronized(mActivityCallbacks) {
mActivityCallbacks.forEach {
it.onWindowFocusChanged(this, hasFocus)
}
}
}
@CallSuper
override fun onDestroy() {
super.onDestroy()
synchronized(mActivityCallbacks) { mActivityCallbacks.clear() }
}
@CallSuper
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (isLoadComplete) {
customActivityResultRegistry.dispatchResult(requestCode, resultCode, data)
} else {
pendingResultMap[requestCode] = ResultData(requestCode, resultCode, data)
}
}
override fun <I, O> registerForLazyActivityResult(contract: ActivityResultContract<I, O>,
callback: ActivityResultCallback<O>): ActivityResultLauncher<I> {
if (isLoadComplete) {
throw Throwable("can only register before load complete!")
}
return customActivityResultRegistry.register(
"activity_rq#" + nextLocalRequestCode.getAndIncrement(),
this, contract, callback)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val data = Intent()
.putExtra(ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSIONS, permissions)
.putExtra(ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS, grantResults)
if (isLoadComplete) {
customActivityResultRegistry.dispatchResult(requestCode, RESULT_OK, data)
} else {
pendingResultMap[requestCode] = ResultData(requestCode, RESULT_OK, data)
}
}
init {
savedStateRegistry.registerSavedStateProvider(ACTIVITY_RESULT_TAG
) {
val outState = Bundle()
customActivityResultRegistry.onSaveInstanceState(outState)
outState
}
addOnContextAvailableListener {
val savedInstanceState = savedStateRegistry
.consumeRestoredStateForKey(ACTIVITY_RESULT_TAG)
if (savedInstanceState != null) {
customActivityResultRegistry.onRestoreInstanceState(savedInstanceState)
}
}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)