Android 权限说明顶部弹框实现和权限申请封装

2,007 阅读4分钟

Android 权限说明顶部弹框实现和权限申请封装

使用Snackbar绘制顶部弹框,特点要嵌入View才行,为了灵活调用,从activity获取窗口View

ViewGroup rootView = activity.findViewById(android.R.id.content);

定义customer_snackbar_top.xmlSnackbar布局,如下

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content"
    android:background="#00ffffff">

    <androidx.cardview.widget.CardView 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="wrap_content"
        android:layout_gravity="top|center_horizontal"
        android:layout_marginTop="25dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"
        android:backgroundTint="@color/white"
        android:minHeight="0dp"
        app:cardCornerRadius="8dp"
        app:cardElevation="8dp"
        app:contentPadding="12dp">

        <ImageView
            android:layout_width="18dp"
            android:layout_height="18dp"
            android:layout_marginTop="6dp"
            android:src="@drawable/icon_safety_52" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="24dp"
            android:gravity="top"
            android:orientation="vertical">

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_snack_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:includeFontPadding="false"
                android:textColor="#000000"
                android:textSize="15sp"
                android:textStyle="bold"
                tools:text="哈哈哈哈哈哈哈哈哈哈" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/tv_snack_message"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="4dp"
                android:includeFontPadding="false"
                android:textColor="#333333"
                android:textSize="12sp"
                tools:text="哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈" />
        </LinearLayout>


    </androidx.cardview.widget.CardView>
</FrameLayout>

定义snackbar弹窗工具类TopSnackbarFactory,如下

public class TopSnackbarFactory {
    public static Snackbar create(AppCompatActivity activity, String title, String message) {
        ViewGroup rootView = activity.findViewById(android.R.id.content);
        Snackbar mSnackbar =
                Snackbar.make(rootView, "Replace with your own action", Snackbar.LENGTH_INDEFINITE);
        Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) mSnackbar.getView();
        layout.setBackgroundColor(Color.TRANSPARENT);
//        layout.setElevation(0);
        layout.setPadding(0, 0, 0, 0);
        layout.findViewById(com.google.android.material.R.id.snackbar_text).setVisibility(View.GONE);
        layout.findViewById(com.google.android.material.R.id.snackbar_action).setVisibility(View.GONE);
        View view = LayoutInflater.from(activity).inflate(R.layout.customer_snackbar_top, layout);
        AppCompatTextView tvTitle = view.findViewById(R.id.tv_snack_title);
        tvTitle.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                tvTitle.setText(title);
                tvTitle.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
        AppCompatTextView tvMessage = view.findViewById(R.id.tv_snack_message);
        tvMessage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                tvMessage.setText(message);
                tvMessage.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            }
        });
        FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) view.getLayoutParams();
        param.width = FrameLayout.LayoutParams.MATCH_PARENT;
        param.gravity = Gravity.CENTER_HORIZONTAL;
        mSnackbar.setBackgroundTint(Color.TRANSPARENT);
        return mSnackbar;
    }
}

权限申请功能实现

//getActivityResultRegistry()权限库
implementation("androidx.activity:activity:1.2.5@aar")

使用getActivityResultRegistry().register申请调用权限,原因是使用registerForActivityResult()调用必须在生命周期STARTED之前,否则会报

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.");
}

代码里已经使用了getActivityResultRegistry().register避免再使用registerForActivityResult(),可能会受到key影响,结合mNextLocalRequestCode递增值,作为key值取得onActivityResult,如下

@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);
}
定义权限申请回调
public interface OnPermissionListener {

    void onGranted(String[] permissions, boolean isAllGranted);

    /**
     * @param permissions 0用dialog -1不用dialog
     * @return
     */
    int onDenied(String[] permissions);
}
检查权限是否通过
private boolean checkFilesPermission(String... permissions) {
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}
封装权限申请
private final AtomicInteger mNextLocalRequestCode = new AtomicInteger();

protected AlertDialog permAlertDialog;
private Snackbar snackPermission;
private ActivityResultLauncher<Intent> resultLaunch;
private ActivityResultLauncher<String[]> permissionLauncher;

private String[] currentPermissions;

/**
 * @param context
 * @param listener    权限回调监听
 * @param deniedTitle 顶部提示文案标题
 * @param deniedDesc  顶部提示文案描述
 * @param deniedHint  拒绝弹框描述
 * @param mPerms      权限数组
 */
protected void checkPermissionCallback(Context context, OnPermissionListener listener, String deniedTitle, String deniedDesc, String deniedHint, String... mPerms) {
    if (checkFilesPermission(mPerms) || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        Log.e("TAG", "granted Permission ");
        listener.onGranted(mPerms, true);
        if (permAlertDialog != null) {
            permAlertDialog.dismiss();
            permAlertDialog = null;
        }
        if (snackPermission != null) {
            snackPermission.dismiss();
        }
    } else {
        Log.e("TAG", "denied Permission ");
        if (snackPermission == null) {//"文件权限使用说明", ""Yi点"想使用您的文件管理,用于帮助后续文件读取写入"
            snackPermission = TopSnackbarFactory.create(this, deniedTitle, deniedDesc);
        }
        if (!snackPermission.isShown()) {
            snackPermission.show();
        }
        String key = "activity_rq#" + mNextLocalRequestCode.getAndIncrement();
        Log.e("TAG", "checkPermissionCallback ActivityResultLauncher " + key);
        permissionLauncher = getActivityResultRegistry().register(key, new ActivityResultContracts.RequestMultiplePermissions(), result -> {
            for (Map.Entry<String, Boolean> entry : result.entrySet()) {
                Log.e("TAG", "Permission=" + entry.getKey() + "," + entry.getValue());
            }
            List<String> granteds = new ArrayList<>();
            List<String> denieds = new ArrayList<>();
            for (String mPerm : mPerms) {
                Boolean granted = result.getOrDefault(mPerm, false);
                if (granted != null) {
                    if (granted) {
                        granteds.add(mPerm);
                    } else {
                        denieds.add(mPerm);
                    }
                }
            }
            if (granteds.size() > 0) {
                String str[] = new String[granteds.size()];
                listener.onGranted(granteds.toArray(str), granteds.size() == mPerms.length);
            }
            if (denieds.size() > 0) {
                String str[] = new String[denieds.size()];
                int state = listener.onDenied(denieds.toArray(str));
                if (state == 0) {
                    Log.e("TAG", "permAlertDialog " + (permAlertDialog == null));
                    if (permAlertDialog == null) {
                        permAlertDialog = new MaterialAlertDialogBuilder(context, android.R.style.Theme_Material_Dialog).setTitle("温馨提示").setCancelable(false).setMessage(deniedHint).setPositiveButton("去设置", (dialog, which) -> {
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", context.getPackageName(), null);
                            intent.setData(uri);
                            registerLaunchForResult(intent, result1 -> {
                                dismissPermissionWindow();
                            });
                            currentPermissions = mPerms;
                        }).create();
                    }
                    permAlertDialog.setCanceledOnTouchOutside(false);
                    if (!permAlertDialog.isShowing()) {
                        permAlertDialog.show();
                    }

                }

            } else {
                if (permAlertDialog != null) {
                    permAlertDialog.dismiss();
                    permAlertDialog = null;
                }
                if (snackPermission != null) {
                    snackPermission.dismiss();
                    snackPermission = null;
                }
                currentPermissions = null;
            }
            permissionLauncher.unregister();
        });
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    permissionLauncher.unregister();
                    getLifecycle().removeObserver(this);

                }

            }
        });
        permissionLauncher.launch(mPerms);
    }
}
清除隐藏弹框公共方法
public void dismissPermissionWindow() {
    if (currentPermissions == null) {
        return;
    }
    if (checkFilesPermission(currentPermissions) || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        if (permAlertDialog != null) {
            permAlertDialog.dismiss();
            permAlertDialog = null;
        }
        if (snackPermission != null) {
            snackPermission.dismiss();
            snackPermission = null;
        }
        currentPermissions = null;
    } else {
        Log.e("TAG", "permAlertDialog2 " + (permAlertDialog == null));
        if (permAlertDialog != null) {
            if (!permAlertDialog.isShowing()) {
                permAlertDialog.show();
            }
        }
    }
}
封装文件权限申请兼容13和14,关于权限判断第三方库用的是targetSdkVersion>=13判断,有问题请替换'Build.VERSION.SDK_INT'为'targetSdkVersion'
public void requestFilesPermission(OnPermissionListener listener, String deniedTitle, String deniedDesc, String deniedHint) {
    // Permission request logic
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {//   Manifest.permission.READ_MEDIA_VIDEO,
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_MEDIA_IMAGES, android.Manifest.permission.READ_MEDIA_VIDEO, android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
    } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_MEDIA_IMAGES, android.Manifest.permission.READ_MEDIA_VIDEO);
    } else {
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_EXTERNAL_STORAGE);
    }
}

public void requestFilesCameraPermission(OnPermissionListener listener, String deniedTitle, String deniedDesc, String deniedHint) {
    // Permission request logic
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {//   Manifest.permission.READ_MEDIA_VIDEO,
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_MEDIA_IMAGES, android.Manifest.permission.READ_MEDIA_VIDEO, android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, android.Manifest.permission.CAMERA);
    } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_MEDIA_IMAGES, android.Manifest.permission.READ_MEDIA_VIDEO, android.Manifest.permission.CAMERA);
    } else {
        checkPermissionCallback(this, listener, deniedTitle, deniedDesc, deniedHint, android.Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA);
    }
}
封装带回调的activity
public void registerLaunchForResult(Intent intent, ActivityResultCallback<ActivityResult> activityResultCallback) {
    String key = "activity_rq#" + mNextLocalRequestCode.getAndIncrement();
    resultLaunch = getActivityResultRegistry().register(key, new ActivityResultContracts.StartActivityForResult(), result -> {
        activityResultCallback.onActivityResult(result);
        startActivityForResultCustomer(result);
        resultLaunch.unregister();
    });
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                resultLaunch.unregister();
                getLifecycle().removeObserver(this);
            }
        }
    });
    resultLaunch.launch(intent);
}
调用方式
requestFilesPermission(new OnPermissionListener() {
    @Override
    public void onGranted(String[] permissions, boolean isAllGranted) {
        if (isAllGranted) {

        }
    }

    @Override
    public int onDenied(String[] permissions) {
        return 0;
    }
}, "照片视频权限使用说明", ""App"正在使用您的照片视频权限,用于后续基本资料提交以及照片文件缓存", "权限未获取会影响到App正常使用,请前往设置!");
效果如下
Screenshot_20231226_172509.png Screenshot_20231226_172537.png