Fragment优雅的申明权限的几种方式

639 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第十天,点击查看活动详情

Fragment优雅的申明权限的几种方式

前言

Fragment是在开发中除了Activity最常见的一种界面,Fragment可以定义和管理自己的布局,具有自己的生命周期。在开发中经常使用Fragment,在我们不使用第三方框架的情况下,我们应该如何进行对权限的申明呢?

示例的源代码:Github

前期准备

权限

我们在动态申明权限的时候,我们需要在AndroidManifest.xml清单中进行静态的申明,我们就以常见的读写权限为例

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

为什么需要限制在SDK32的版本?

在SDK33的版本中,READ_EXTERNAL_STORAGE的权限已经被细分成READ_MEDIA_IMAGEREAD_MEDIA_VIDEOREAD_MEDIA_AUDIO

本篇将以在Android12(SDK32)的情况下为例,进行对读写权限的申明

简单封装一下ViewBinding

为方便代码的编写,我们先简单的封装一下ViewBinding

  • build.gradle(app)中添加一下以下代码以表使用ViewBinding
android {
    ...
    buildFeatures {
        viewBinding true //使用viewbinding
    }
    ...
}
  • 简单封装一下BaseFragment
package com.softdrink.emotional.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding

abstract class BaseFragment<VB : ViewBinding> : Fragment() {
    lateinit var _vb: VB
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _vb = initVB()
        return _vb.root
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initSome()
    }

    abstract fun initVB(): VB
    abstract fun initSome()
}
  • MainActivity的代码,进行对OneFragment的显示
package com.softdrink.emotional.myapplication

import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.softdrink.emotional.myapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    lateinit var _vb: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _vb = ActivityMainBinding.inflate(layoutInflater)
        setContentView(_vb.root)
        initView()
    }

    lateinit var currentFragment: Fragment
    fun initView() {
        //三种方式的对应的Fragment
        val one = OneFragment()
        val two = TwoFragment()
        val three = ThreeFragment()

        //通过transaction添加fragment
        val transaction = supportFragmentManager.beginTransaction()
        transaction.add(R.id.frameLayout, one, "one")
        transaction.add(R.id.frameLayout, two, "two")
        transaction.add(R.id.frameLayout, three, "three")

        //默认显示OneFragment
        transaction.hide(two)
        transaction.hide(three)
        transaction.show(one)
        transaction.commit()
        currentFragment = one

        //注册点击事件进行切换
        _vb.button.setOnClickListener {
            val transaction = supportFragmentManager.beginTransaction()
            if (currentFragment != one) {
                transaction.hide(currentFragment)
                transaction.show(one)
                transaction.commit()

                currentFragment = one

            }

        }

        _vb.button2.setOnClickListener {
            val transaction = supportFragmentManager.beginTransaction()
            if (currentFragment != two) {
                transaction.hide(currentFragment)
                transaction.show(two)
                transaction.commit()
                currentFragment = two
            }

        }
        _vb.button3.setOnClickListener {
            val transaction = supportFragmentManager.beginTransaction()
            if (currentFragment != three) {
                transaction.hide(currentFragment)
                transaction.show(three)
                transaction.commit()
                currentFragment = three

            }

        }
    }
}

  • MainActivity的布局文件代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    //显示Fragment
    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    //三种方式的切换
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="方式一"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button2"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="方式二"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/button3"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/button" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="方式三"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>

通过以上代码我们可以看到如下的效果

1665458383425.jpg

方式一

我们知道Fragment是不能独立显示,必须要依附在Activity才能显示,所以我们可以在Fragment进去请求权限,借助Activity来进行对权限的结果的监听,并把结果返回给Fragment

  • OneFragment 进行权限的请求
package com.softdrink.emotional.myapplication

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.softdrink.emotional.myapplication.databinding.FragmentOneBinding

class OneFragment : BaseFragment<FragmentOneBinding>() {

    companion object {
        //权限请求码
        const val permissionCode = 100
    }

    override fun initVB(): FragmentOneBinding = FragmentOneBinding.inflate(layoutInflater)

    override fun initSome() {
        _vb.btnStart.setOnClickListener {
            requestPermission()
        }
    }
    
    //申请的权限
    val allPermission = arrayOf(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )


    fun requestPermission() {
        //判断权限是否已经同意
        if (ContextCompat.checkSelfPermission(
                this.requireContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(
                this.requireContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            != PackageManager.PERMISSION_GRANTED
        ) {
            //请求权限
            requireActivity().requestPermissions(allPermission, permissionCode)

        } else {

            Toast.makeText(requireContext(), "OneFragment Permissions granted", Toast.LENGTH_SHORT).show()
        }
    }

    /**
     * 权限的结果回调,这里是给mainActivity调用
     *
     * @param isOk 返回结果
     */
    fun permissionResultCall(isOk: Boolean) {
        Toast.makeText(
            requireContext(),
            if (isOk) " OneFragment Permissions granted" else "OneFragment Permissions not granted",
            Toast.LENGTH_SHORT
        ).show()
    }

}

我们还需要在MainActivity中添加一下代码

//重写权限的结果回调
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    if (requestCode == OneFragment.permissionCode) {// 判断请求码
        //调用OneFragment的permissionResultCall
        one.permissionResultCall(grantResults[0] == PackageManager.PERMISSION_GRANTED)
    }
}

效果图:

1665455748457.jpg

1665459000051.jpg

方式二

我们在方法一中,我们借助了Activity来进行权限的请求,那么我们有什么办法不需要Activity来进行权限的请求呢?当然有,那么我们直接看代码

package com.softdrink.emotional.myapplication

import android.Manifest
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.core.content.ContextCompat
import com.softdrink.emotional.myapplication.databinding.FragmentTwoBinding

class TwoFragment : BaseFragment<FragmentTwoBinding>() {
    override fun initVB(): FragmentTwoBinding {
        return FragmentTwoBinding.inflate(layoutInflater)
    }

    override fun initSome() {
        _vb.btnTwo.setOnClickListener {
            requestPermission()
        }
    }

    val twoCode = 100
    val allPermission = arrayOf(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )

    fun requestPermission() {
        if (ContextCompat.checkSelfPermission(
                this.requireContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(
                this.requireContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            != PackageManager.PERMISSION_GRANTED
        ) {
            //请求权限 已经被遗弃了
            requestPermissions(allPermission, OneFragment.permissionCode)

        } else {

            Toast.makeText(requireContext(), "TwoFragment Permissions granted", Toast.LENGTH_SHORT)
                .show()
        }
    }

    //重写回调
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == twoCode) {

            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(
                    requireContext(),
                    "TwoFragment Permissions granted",
                    Toast.LENGTH_SHORT
                )
                    .show()
            } else {
                Toast.makeText(
                    requireContext(),
                    "TwoFragment Permissions not granted",
                    Toast.LENGTH_SHORT
                )
                    .show()
            }
        }

    }
}

我们发现方法一与方法二的区别在于:

方法一:调用的是Activity的requestPermissions
方法二:调用是Fragment的requestPermissions
这两种方式都不推荐使用,官方推荐使用方式三

方式三

该方式的是官方的推荐的一种方式,具体请看

该方式是通过一种回调的方式来进行获取

package com.softdrink.emotional.myapplication

import android.Manifest
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import com.softdrink.emotional.myapplication.databinding.FragmentThreeBinding

class ThreeFragment : BaseFragment<FragmentThreeBinding>() {
    override fun initVB(): FragmentThreeBinding {
        return FragmentThreeBinding.inflate(layoutInflater)
    }

    override fun initSome() {
        _vb.btnThree.setOnClickListener {
            requestPermission()
        }
    }


    fun requestPermission() {
        if (ContextCompat.checkSelfPermission(
                this.requireContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)

        } else {

            Toast.makeText(requireContext(), "ThreeFragment WRITE Permissions granted", Toast.LENGTH_SHORT)
                .show()
        }
    }

    //以接口的形式来进行回调的监听
    val requestPermissionLauncher =
        registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { isGranted: Boolean ->
            if (isGranted) {
                Toast.makeText(
                    requireContext(),
                    "ThreeFragment Permissions Granted",
                    Toast.LENGTH_SHORT
                )
                    .show()
            } else {
                Toast.makeText(
                    requireContext(),
                    "ThreeFragment Permissions Not Granted",
                    Toast.LENGTH_SHORT
                )
                    .show()
            }
        }

}