The cause of

  • In a recent project to the company to do APK package volume optimization, including necessary to remove some unnecessary tripartite library, in the process found a series of Rx project related library, RxJava, RxBus, RxPermission, so heart a glimmer of murder.
  • Of course, RxJava is still quite powerful, based on the event stream chain call, time-consuming tasks, thread switching, is a good asynchronous operation library, after all, I wrote the last series of articles to explore the Android open source framework – 3. RxJava use and source parsing, if you want to have a deeper understanding of RxJava if you can take a look. However, with kotlin’s popularity, its coroutines and flows can also replace RxJava, so be prepared to try removing RxJava and its associated libraries, after all, to reduce the package size.
  • So let’s start with RxPermission.

Simple use of RxPermission

  • Let’s first introduce the use of RxPermission. After all, it makes our runtime permission requests much simpler because of the tree RxJava.
//1. Build. Gradle add dependency AllProjects {repositories {... Dependencies maven {url 'https://jitpack.io'}}} {implementation 'com. Making. Tbruyelle: rxpermissions: 0.12'} / / note: the latest version of rxpermission need use implementation RxJava3 'IO. Reactivex. RxJava3: rxjava: 3.0.4' implementation 'the IO. Reactivex. Rxjava3: rxandroid: 3.0.0' / / 2. New RxPermissions(this).request(manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, The Manifest. Permission. CAMERA). The subscribe (accept - > {if (accept) {LjyLogUtil. D (" allowed permission to apply for "); } else {ljylogutil. d(" permission request rejected "); }}); New RxPermissions(this).requesteach (manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA) .subscribe(permission -> { // will emit 2 Permission objects LjyLogUtil.d(permission.toString()); if (permission.granted) { // `permission.name` is granted ! Ljylogutil. d(" Permission request :" + permission. Name); } else if (permission.shouldShowRequestPermissionRationale) { // Denied permission without ask never again Ljylogutil. d(" Permission request cancelled :" + permission. Name); } else {// Denied permission with ask never again // Need to go to the Settings ljylogutil. d Please go to Settings to change :" + permission. }});Copy the code
  • As mentioned above, RxPermissions can be used to put permission requests and result callbacks into one chained code, and support uniform return of all permissions (Request) and return of permission request results (requestEach).
  • Its main principles are as follows: When creating a new RxPermissions class, the framework will quietly create a new RxPermissionsFragment class, which means that the framework encapsulates an internal fragment with no interface. The advantage of this is that callback requests for permissions can be implemented in the fragment. Don’t need the user to call onRequestPermissionsResult, combining various RxJava operators, will be very convenient to use.

Simple encapsulation

  • I have also encapsulated a permission request utility class before, the code is as follows
public class LjyPermissionUtil { private PermissionResultListener permissionResultListener; private LjyPermissionUtil() { } public static LjyPermissionUtil getInstance() { return PermissionUtilHolder.instancePermissionUtil; } private static class PermissionUtilHolder { private static final LjyPermissionUtil instancePermissionUtil = new LjyPermissionUtil(); } /** * Check whether the current application has specified permissions. */ public Boolean hasPermissions(Context, permissions) String[] permissions) { if (permissions == null || permissions.length == 0) { return true; } boolean ifSdk = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; for (String permission : permissions) { if (ifSdk && ! hasPermission(context, permission)) { return false; } } return true; } private boolean hasPermission(Context context, String permission) { return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; } /** * Cooperate hasPermission use, pay attention to in using the activity call onRequestPermissionsResult permissions apply the results of the callback * * @ param activity * @ param permissions * @ param  requestCode */ public void requestPermission(final Activity activity, final String[] permissions, final int requestCode, final PermissionResultListener permissionResultListener) { this.permissionResultListener = permissionResultListener; ActivityCompat.requestPermissions(activity, permissions, requestCode); } /** * The result of the request permission callback, Need to call * * in the Activity of onRequestPermissionsResult @ param grantResults * / public void onPermissionResult (Activity Activity, final int requestCode, String[] permissions, int[] grantResults) { boolean hasPermission = true; List<String> deniedList = new ArrayList<>(); List<String> cancelList = new ArrayList<>(); for (int i = 0; i < grantResults.length; i++) { boolean isAllow = grantResults[i] == PackageManager.PERMISSION_GRANTED; hasPermission &= isAllow; if (! isAllow) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ! activity.shouldShowRequestPermissionRationale(permissions[i])) { deniedList.add(permissions[i]); } else { cancelList.add(permissions[i]); } } } if (permissionResultListener ! = null) { if (hasPermission) { permissionResultListener.onSuccess(requestCode); } else { if (deniedList.size() > 0) { permissionResultListener.onDenied(deniedList); } if (cancelList.size() > 0) { permissionResultListener.onCancel(cancelList); }}}} /** * Callback interface for permission application results */ public interface PermissionResultListener {/** * Application success */ void onSuccess(final int requestCode); */ void onDenied(@nonnull List<String> deniedList); */ void onDenied(@nonnull List<String> deniedList); Void onCancel(@nonnull List<String> cancelList); void onCancel(@nonnull List<String> cancelList); }}Copy the code
  • Use the following
/ / baseActivity onRequestPermissionsResult open class baseActivity: AppCompatActivity() { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (grantResults.isNotEmpty()) { LjyPermissionUtil.getInstance().onPermissionResult(this, requestCode, permissions, grantResults) } } } class PermissionActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_permission) } fun onBtnClick(view: View) {when (view.id) {// get runtime permission r.i.d.button_perm_1 -> requestPermission()}} private fun requestPermission() {val  permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA) if (LjyPermissionUtil.getInstance().hasPermissions(this@PermissionActivity, permissions)) { printFileName() } else { val mRequestCode=1002 LjyPermissionUtil.getInstance().requestPermission( this@PermissionActivity, permissions, mRequestCode, object : LjyPermissionUtil.PermissionResultListener { override fun onSuccess(requestCode: Int) { if (requestCode == mRequestCode) { printFileName() } } override fun onDenied(deniedList: MutableList<String>) {ljytoastutil.toast (this@PermissionActivity, "Permission denied, will result in the normal use of APP, ") for (it in deniedList) {ljylogutil. d("deniedList:$it")}} Override fun onCancel(cancelList: MutableList<String>) { LjyToastUtil.toast(this@PermissionActivity, For (it in cancelList) {ljylogutil. d("failList:$it")}})}} private fun printFileName() { Ljylogutil. d(" Operation file...") )}}Copy the code
  • It can be used only slightly, but it is not elegant enough. One of them is to invade the base class BaseActivity, and the other is to write code that looks cumbersome

Create PermissionUtil

  • This time we’ll do it with Kotlin, so we can use all of its syntactic sugar to make the code a little more elegant,

Kotlin singleton

  • Since it is a utility class, let’s do a singleton first. Some of kotlin’s features and the implementation of singleton will be summarized in a separate article later.
Class PermissionUtil private constructor() : Serializable {// private fun readResolve(): Return instance} Companion Object {@jvmstatic // Use lazy proxy, Val instance SYNCHRONIZED: PermissionUtil by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { PermissionUtil() } } }Copy the code

Encapsulation PermissionFragment

  • Create a data class that requests results
data class Permission( var name: String? , var granted: Boolean, var shouldShowRequestPermissionRationale: Boolean )Copy the code
  • And then we also to encapsulate a no fragments of the interface, used to implement the real authorization request, avoid the user to call onRequestPermissionsResult, also need not like the one above invasion BaseActivity
class PermissionFragment : Fragment() {
    private val PERMISSIONS_REQUEST_CODE = 520
    var permissions: Array<String>? = null
    var callBack: ((Boolean) -> Unit)? = null

    fun removeFragment() {
        val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction()
        fragmentTransaction.remove(this).commit()
    }

    @TargetApi(Build.VERSION_CODES.M)
    fun requestPermissions(permissions: Array<String?>) {
        requestPermissions(
            permissions,
            PERMISSIONS_REQUEST_CODE
        )
    }

    @TargetApi(Build.VERSION_CODES.M)
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
}
Copy the code

Using registerForActivityResult

  • The above code, we find requestPermissions and onRequestPermissionsResult is outdated, point check, tip should have registerForActivityResult replacement, the code is as follows
class PermissionFragment : Fragment() { private lateinit var requestMultiplePermissionsLauncher: ActivityResultLauncher<Array<String>> var permissions: Array<String>? = null var callBack: ((Boolean) -> Unit)? = null fun removeFragment() { val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction() fragmentTransaction.remove(this).commit() } override fun onAttach(context: Context) { super.onAttach(context) requestMultiplePermissionsLauncher = RegisterForActivityResult (ActivityResultContracts RequestMultiplePermissions ()) {it - > / / by permissions val grantedList = FilterValues {it}. MapNotNull {it. Key} // Whether all permissions are granted by val allGranted = GrantedList. size == it Grantedlist.toset ()).map {it. Key} val deniedList = list.filter { ActivityCompat.shouldShowRequestPermissionRationale( requireActivity(), Val alwaysDeniedList = list-deniedlist.toset () callBack?.invoke(allGranted) removeFragment()}  if (permissions?.isNotEmpty() == true) requestMultiplePermissionsLauncher.launch(permissions) } }Copy the code

The use of a Flow

  • The permission request is basically implemented above, but you can use Kotlin’s Flow to further refine it
class PermissionFragment : Fragment() { private lateinit var requestMultiplePermissionsLauncher: ActivityResultLauncher<Array<String>> var permissions: Array<String>? = null var accept: ((Boolean) -> Unit)? = null var permissionResult: ((Permission) -> Unit)? = null var denied: ((String) -> Unit)? = null var alwaysDenied: ((String) -> Unit)? = null fun removeFragment() { val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction() fragmentTransaction.remove(this).commit() } override fun onAttach(context: Context) { super.onAttach(context) requestMultiplePermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { it -> lifecycleScope.launch { // Val allGranted = it.iterator().asflow ().flowon (dispatchers.main).map {it.value}.reduce {a, val allGranted = it.iterator().asflow ().flowon (dispatchers.main).map {it. b -> a && b } accept?.invoke(allGranted) it.iterator() .asFlow() .flowOn(Dispatchers.Main) .onEach { entry -> Log (" All permissions :" + entry.key) permissionResult?. Invoke (Permission(entry.key, entry.value, ActivityCompat.shouldShowRequestPermissionRationale( requireActivity(), entry.key ) ) ) } .filter { ! Value}. OnEach {entry -> log(" Denied permission :" + entry. Key) denied? .invoke(entry.key) } .filter { ! ActivityCompat.shouldShowRequestPermissionRationale( requireActivity(), It.key)}.oneach {entry -> log(" Deny and click "no more" permission :" + entry. Key) alwaysDenied? .invoke(entry.key) } .collect() if (isAdded) { removeFragment() } } } if (permissions? .isNotEmpty() == true) requestMultiplePermissionsLauncher.launch(permissions) } }Copy the code

Implement PermissionUtil

  • Now that you have a PermissionFragment, it’s time to use it in PermissionUtil
class PermissionUtil private constructor() : Serializable { private fun readResolve(): Any { return instance } companion object { @JvmStatic val instance: PermissionUtil by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { PermissionUtil() } } val permissionFragment: PermissionsRequest = permissionsRequest() /** * permissionsRequest(activity: FragmentActivity, permissions: Array<String>, accept: (allGranted: Boolean) -> Unit ) { permissionFragment.permissions = permissions permissionFragment.accept = accept val fragmentTransaction = activity.supportFragmentManager.beginTransaction() fragmentTransaction.add(permissionFragment, "permissionFragment@LJY").commit()} /** * all permissions return a result && getTopActivity */ fun permissionsRequest(permissions: Array<String>, accept: (allGranted: Boolean) -> Unit) { val context: Activity = ApplicationUtil.instance.getTopActivity() ?: throw java.lang.NullPointerException("Top Activity is Null!" ) permissionsRequest(context as FragmentActivity, permissions, Fun permissionsRequestEach(Activity: FragmentActivity, permissions: Array<String>, permissionResult: (Permission) -> Unit ) { permissionFragment.permissions = permissions permissionFragment.permissionResult = permissionResult val fragmentTransaction = activity.supportFragmentManager.beginTransaction() fragmentTransaction.add(permissionFragment, "permissionFragment@LJY").commit()} /** * return the result of the permission request one by one && getTopActivity */ fun permissionsRequestEach(permissions: Array<String>, permissionResult: (Permission) -> Unit) { val context: Activity = ApplicationUtil.instance.getTopActivity() ?: throw java.lang.NullPointerException("Top Activity is Null!" ) permissionsRequestEach(context as FragmentActivity, permissions, permissionResult) } }Copy the code

Using PermissionUtil

/ / all permissions unified return results permissionsRequest (arrayOf (Manifest. Permission. The CAMERA, the Manifest. Permission. SEND_SMS, Manifest.permission.CALL_PHONE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, ){if (it) {todoSomething()}else {toast.maketext (this, "we need the corresponding permissions, Please allow access application ", Toast. LENGTH_LONG) show ()}} / / will return to permissionsRequestEach permissions apply results one by one (arrayOf (Manifest. Permission. CAMERA, Manifest.permission.SEND_SMS, Manifest.permission.CALL_PHONE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, ) ){ when { it.granted -> { // `permission.name` is granted ! Log ("Activity. Allow permission request :" + it.name); } it.shouldShowRequestPermissionRationale -> { // Denied permission without ask never again log("Activity. Cancel permission request :" + it.name); } else -> { // Denied permission with ask never again // Need to go to the settings log("Activity. "+ it. Name); }}}Copy the code
  • This simple permission request is wrapped up, but needs to be refined if it is used online;

My name is Jinyang. If you want to learn more about jinyang, please pay attention to the wechat public number “Jinyang said” to receive my latest articles