Android ActivityResultContracts 替代 startActivityForResult

Posted 匆忙拥挤repeat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android ActivityResultContracts 替代 startActivityForResult相关的知识,希望对你有一定的参考价值。

文章目录

关联博客

Android ActivityResultContracts 请求权限(封装;含android 11权限变更)

I. Doc

官方文档

implementation 'androidx.fragment:fragment-ktx:1.3.1'
implementation 'androidx.activity:activity-ktx:1.2.1'
implementation 'androidx.appcompat:appcompat:1.2.0' //主要是这个依赖,其会自动依赖前两个

II. Source Analysis

II.a. 启动器 ActivityResultLauncher

public abstract class ActivityResultLauncher<I> 

    /**
     * Executes an @link ActivityResultContract.
     *
     * <p>This method throws @link android.content.ActivityNotFoundException
     * if there was no Activity found to run the given Intent.

     * @param input the input required to execute an @link ActivityResultContract.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public void launch(@SuppressLint("UnknownNullness") I input) 
        launch(input, null);
    

    /**
     * Executes an @link ActivityResultContract.
     *
     * <p>This method throws @link android.content.ActivityNotFoundException
     * if there was no Activity found to run the given Intent.
     *
     * @param input the input required to execute an @link ActivityResultContract.
     * @param options Additional options for how the Activity should be started.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public abstract void launch(@SuppressLint("UnknownNullness") I input,
            @Nullable ActivityOptionsCompat options);

    /**
     * Unregisters this launcher, releasing the underlying result callback, and any references
     * captured within it.
     *
     * You should call this if the registry may live longer than the callback registered for this
     * launcher.
     */
    @MainThread
    public abstract void unregister();

    /**
     * Get the @link ActivityResultContract that was used to create this launcher.
     *
     * @return the contract that was used to create this launcher
     */
    @NonNull
    public abstract ActivityResultContract<I, ?> getContract();


II.b. 协议 ActivityResultContract

public abstract class ActivityResultContract<I, O>  //泛型为输入、输出类型

    /** Create an intent that can be used for @link Activity#startActivityForResult */
    public abstract @NonNull Intent createIntent(@NonNull Context context,
            @SuppressLint("UnknownNullness") I input);

    /** Convert result obtained from @link Activity#onActivityResult to O */
    @SuppressLint("UnknownNullness")
    public abstract O parseResult(int resultCode, @Nullable Intent intent);

    /**
     * An optional method you can implement that can be used to potentially provide a result in
     * lieu of starting an activity.
     *
     * @return the result wrapped in a @link SynchronousResult or @code null if the call
     * should proceed to start an activity.
     */
    public @Nullable SynchronousResult<O> getSynchronousResult(
            @NonNull Context context,
            @SuppressLint("UnknownNullness") I input) 
        return null;
    

    /**
     * The wrapper for a result provided in @link #getSynchronousResult
     *
     * @param <T> type of the result
     */
    public static final class SynchronousResult<T> 
        private final @SuppressLint("UnknownNullness") T mValue;

        /**
         * Create a new result wrapper
         *
         * @param value the result value
         */
        public SynchronousResult(@SuppressLint("UnknownNullness") T value) 
            this.mValue = value;
        

        /**
         * @return the result value
         */
        public @SuppressLint("UnknownNullness") T getValue() 
            return mValue;
        
    


ActivityResultContracts 提供了一些静态内部类 声明的 协议。

GetContent
PickContact
RequestPermission
RequestMultiplePermissions
TakePicture
TakePicturePreview
TakeVideo
....

II.c. 注册表 ActivityResultRegistry

粗略分析一下,内部会在每次注册register() 时,自动生成一个 requestCode ;以Map关联 requestCode 和 注册时传入的string key。此外,还有 key-requestCode 、key-callback、key-LifecycleContainer、key-reuslt 的Map。

注册方法有两个重载,一个基于 LifecycleOwner,并向其注册LifecycleEventObserver。一个不基于。

部分源码

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);
    
  
;

II.d. ActivityResultContract.SynchronousResult 作用

在 ComponentActivity 中 注册表初始化代码:


private ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() 

  @Override
  public <I, O> void onLaunch(
    final int requestCode,
    @NonNull ActivityResultContract<I, O> contract,
    I input,
    @Nullable ActivityOptionsCompat options) 
    ComponentActivity activity = ComponentActivity.this;

    // Immediate result path
    final ActivityResultContract.SynchronousResult<O> synchronousResult =
      contract.getSynchronousResult(activity, input);
    if (synchronousResult != null) 
      new Handler(Looper.getMainLooper()).post(new Runnable() 
        @Override
        public void run() 
          dispatchResult(requestCode, synchronousResult.getValue());
        
      );
      return;
    
  ...

由此分析,若 contract#getSynchronousResult() 返回 非空的 SynchronousResult 对象,则直接 dispatchResult,不再调用 contract#parseResult()

II.e. Fragment 注册协议与回调返回 launcher

 public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) 
        return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() 
            @Override
            public ActivityResultRegistry apply(Void input) 
                if (mHost instanceof ActivityResultRegistryOwner) 
                    return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
                
                return requireActivity().getActivityResultRegistry();
            
        , callback);
    

public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultRegistry registry,
            @NonNull final ActivityResultCallback<O> callback) 
        return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() 
            @Override
            public ActivityResultRegistry apply(Void input) 
                return registry;
            
        , callback);
    

 private <I, O> ActivityResultLauncher<I> prepareCallInternal(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final Function<Void, ActivityResultRegistry> registryProvider,
            @NonNull final ActivityResultCallback<O> callback) 
        // Throw if attempting to register after the Fragment is CREATED.
        if (mState > CREATED) 
            throw new IllegalStateException("Fragment " + this + " is attempting to "
                    + "registerForActivityResult after being created. Fragments must call "
                    + "registerForActivityResult() before they are created (i.e. initialization, "
                    + "onAttach(), or onCreate()).");
        
  	... 
 

两个重载方法,一个有 ActivityResultRegistry,一个没有。没有的 使用 mHost 即其宿主 activity 的 ActivityResultRegistry 实例对象。

II.f. Activity 注册协议与回调返回 launcher

与Fragment一样,也有类似的方法。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller 
    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
      @NonNull final ActivityResultContract<I, O> contract,
      @NonNull final ActivityResultRegistry registry,
      @NonNull final ActivityResultCallback<O> callback) 
      return registry.register(
        "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
    

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
      @NonNull ActivityResultContract<I, O> contract,
      @NonNull ActivityResultCallback<O> callback) 
      return registerForActivityResult(contract, mActivityResultRegistry, callback);
    

II.g. 自定义注册

  • 没有ActivityResultRegistry的函数,默认使用当前宿主 Activity 的 mActivityResultRegistry 注册。

  • 可以自定义传入ActivityResultRegistry 参数, 在非 Activity/Fragment中注册。

  • registerForActivityResult() ,在fragment中 需要在 created 之前

     before they are created (i.e. initialization, onAttach(), or onCreate()).
    

    在Activity中,需要在 started 之前

    LifecycleOwners must call register before they are STARTED.
    

II.h. 整个launcher的启动入口

不难猜,肯定还是在以前的 onActivityResult里面。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller  
    @CallSuper
    @Override
    @Deprecated
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) 
        if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) 
            super.onActivityResult(requestCode, resultCode, data);
        
    


public abstract class FragmentManager implements FragmentResultOwner 
  void launchStartActivityForResult(@NonNull Fragment f,
            @SuppressLint("UnknownNullness") Intent intent,
            int requestCode, @Nullable Bundle options) 
        if (mStartActivityForResult != null) 
            LaunchedFragmentInfo info = new LaunchedFragmentInfo(f.mWho, requestCode);
            mLaunchedFragments.addLast(info);
            if (intent != null && options != null) 
                intent.putExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE, options);
            
            mStartActivityForResult.launch(intent);
         else 
            mHost.onStartActivityFromFragment(f, intent, requestCode, options);
        
    

III. Extension Demo (kotlin)

III.a. 替换普通的stratActivityForResult

// Fragment 函数扩展
inline fun SupportFragment.requestForResult(crossinline block: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> 
    return registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    )  result ->
        block(result)
    


class DemoFragment: BaseExtendFragment() 
	// 定义 launcher 实例
	private val mLanguageLauncher: ActivityResultLauncher<Intent> = requestForResult  result ->
        result.data?.getStringExtra(ConstantConfig.KEY_DATA)?.let 
           ...
        
  
  
  fun startLanguageSet() 
  	 mViewBind.tvLanguage.clickWithTrigger  // 点击事件
  	   // launch
       mLanguageLauncher.launch(
         Intent(requireContext(), ShellPointActivity::class.java).putExtra(
           ShellActivity.FRAGMENT_KEY, LanguageSetupFragment::class.java.canonicalName
         )
       )
     
  


class LanguageSetupFragment 
	fun test() 
  		...
  		// 正确结果标记 result code 为 RESULT_OK
    	_mActivity.setResult(RESULT_OK, Intent.apply  ... )
  	

III.b. 系统拍照

inline fun SupportFragment.requestTakePhoto(crossinline block: (Boolean) -> Unit): ActivityResultLauncher<Uri> 
    return registerForActivityResult(
            ActivityResultContracts.TakePicture()
    )  result ->
        block(result)
    


class DemoFragment: BaseExtendFragment() 
	val mTakeLauncher = requestTakePhoto 
    if (it) 
      userToast("take photo and save")
    
  
  
  @NeedsPermission(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  fun takePhoto() 
  	 mViewBind.tvHelp.clickWithTrigger 
       val con = contentValuesOf()
       con.put(MediaStore.Images.Media.DISPLAY_NAME, "stone-test.jpg")
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
         con.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/Pictures")
        else 
         con.put(MediaStore.Images.Media.DATA, File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"test.jpg").absolutePath)
       
       con.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG")
       val uri = requireActivity().contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, con)

       mTakeLauncher.launch以上是关于Android ActivityResultContracts 替代 startActivityForResult的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录