Activity activity = SoulPermission.getInstance().getTopActivity();
if (null == activity) {
return;
}
//绿色框中的流程
//用户第一次拒绝了权限、并且没有勾选"不再提示"这个值为true,此时告诉用户为什么需要这个权限。
if (permission.shouldRationale()) {
new AlertDialog.Builder(activity)
.setTitle(“提示”)
.setMessage(rationaleMessage)
.setPositiveButton(“授予”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//用户确定以后,重新执行请求原始流程
retryRunnable.run();
}
}).create().show();
} else {
//此时请求权限会直接报未授予,需要用户手动去权限设置页,所以弹框引导用户跳转去设置页
String permissionDesc = permission.getPermissionNameDesc();
new AlertDialog.Builder(activity)
.setTitle(“提示”)
.setMessage(permissionDesc + “异常,请前往设置->权限管理,打开” + permissionDesc + “。”)
.setPositiveButton(“去设置”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//去设置页
SoulPermission.getInstance().goPermissionSettings();
}
}).create().show();
}
}
}
复制代码
然后我们在App所有打电话的入口处做一次调用:
/**
- 拨打指定电话
*/
public static void makeCall(final Context context, final String phoneNumber) {
SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.CALL_PHONE,
new CheckPermissionWithRationaleAdapter(“如果你拒绝了权限,你将无法拨打电话,请点击授予权限”,
new Runnable() {
@Override
public void run() {
//retry
makeCall(context, phoneNumber);
}
}) {
@Override
public void onPermissionOk(Permission permission) {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse(“tel:” + phoneNumber);
intent.setData(data);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
});
}
复制代码
那么这样下来,在Activity和任何业务页面的调用就只有一行代码了:
findViewById(R.id.bt_call).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UtilsWithPermission.makeCall(getActivity(), “10086”);
}
});
复制代码
其中完全拒绝以后,SoulPermission 提供了跳转到系统权限设置页的方法,我们再来看看效果:
很多时候,其实绿色部分(shouldShowRequestPermissionRationale)其实并不一定必要,反复的弹框用户可能会厌烦,大多数情况,我们这么封装就好:
public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {
@Override
public void onPermissionDenied(Permission permission) {
//SoulPermission提供栈顶Activity
Activity activity = SoulPermission.getInstance().getTopActivity();
if (null == activity) {
return;
}
String permissionDesc = permission.getPermissionNameDesc();
new AlertDialog.Builder(activity)
.setTitle(“提示”)
.setMessage(permissionDesc + “异常,请前往设置->权限管理,打开” + permissionDesc + “。”)
.setPositiveButton(“去设置”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//去设置页
SoulPermission.getInstance().goPermissionSettings();
}
}).create().show();
}
}
复制代码
我们再写一个选择联系人的方法:
/**
- 选择联系人
*/
public static void chooseContact(final Activity activity, final int requestCode) {
SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.READ_CONTACTS,
new CheckPermissionAdapter() {
@Override
public void onPermissionOk(Permission permission) {
activity.startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), requestCode);
}
});
}
复制代码
在Activity中也是一行解决问题:
findViewById(R.id.bt_choose_contact).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);
}
});
复制代码
代码细节请参考demo,我们再来看看效果:
主要功能的源码分析:
优雅的避掉onPermissionResult:
适配权限最大的痛点在于:项目业务页面繁多,如果你想实现“真运行时权限”的话就需要在业务的Activity或者Fragment中去重写权限请求回调方法,斟酌一番并且在参考了下RxPermission中对权限请求的处理,我决定用同样的方式—用一个没有界面的Fragment去完成我们权限请求的操作,下面贴上部分代码:
首先定义一个接口,用于封装权限请求的结果
public interface RequestPermissionListener {
/**
-
得到权限检查结果
-
@param permissions 封装权限的数组
*/
void onPermissionResult(Permission[] permissions);
}
复制代码
然后是我们的Fragment:
public class PermissionSupportFragment extends Fragment implements IPermissionActions {
/**
- 内部维护requestCode
*/
private static final int REQUEST_CODE = 11;
/**
- 传入的回调
*/
private RequestPermissionListener listener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//当状态发生改变,比如设备旋转时候,Fragment不会被销毁
setRetainInstance(true);
}
/**
-
外部请求的最终调用方法
-
@param permissions 权限
-
@param listener 回调
*/
@TargetApi(M)
@Override
public void requestPermissions(String[] permissions, RequestPermissionListener listener) {
requestPermissions(permissions, REQUEST_CODE);
this.listener = listener;
}
@TargetApi(M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Permission[] permissionResults = new Permission[permissions.length];
//拿到授权结果以后对结果做一些封装
if (requestCode == REQUEST_CODE) {
for (int i = 0; i < permissions.length; ++i) {
Permission permission = new Permission(permissions[i], grantResults[i], this.shouldShowRequestPermissionRationale(permissions[i]));
permissionResults[i] = permission;
}
}
if (listener != null && getActivity() != null && !getActivity().isDestroyed()) {
listener.onPermissionResult(permissionResults);
}
}
}
复制代码
其中Permission是我们的权限名称、授予结果、是否需要给用于一个解释的包装类:
public class Permission {
private static final String TAG = Permission.class.getSimpleName();
/**
- 权限名称
*/
public String permissionName;
/**
- 授予结果
*/
public int grantResult;
/**
- 是否需要给用户一个解释
*/
public boolean shouldRationale;
/**
- 权限是否已经被授予
*/
public boolean isGranted() {
return grantResult == PackageManager.PERMISSION_GRANTED;
}
//。。。
}
复制代码
至此,我们已经利用自己实现的一个没有界面的Fragment封装了运行时权限相关的请求、RequestCode的维护、以及onPermissionResult的回调、在我们真正调用的时候代码是这样的:
/**
-
@param activity 栈顶 Activity
-
@param permissionsToRequest 待请求的权限
-
@param listener 回调
*/
private void requestRuntimePermission(final Activity activity, final Permission[] permissionsToRequest, final CheckRequestPermissionsListener listener) {
new PermissionRequester(activity)
.withPermission(permissionsToRequest)
.request(new RequestPermissionListener() {
@Override
public void onPermissionResult(Permission[] permissions) {
List refusedListAfterRequest = new LinkedList<>();
for (Permission requestResult : permissions) {
if (!requestResult.isGranted()) {
refusedListAfterRequest.add(requestResult);
}
}
if (refusedListAfterRequest.size() == 0) {
listener.onAllPermissionOk(permissionsToRequest);
} else {
listener.onPermissionDenied(PermissionTools.convert(refusedListAfterRequest));
}
}
});
}
复制代码
其中PermissionRequester也就是一个简单的构建者模式,其中包含了对Activity的类型判断,根据Activity类型去确定Fragment的实现:如果是FragmentActivity的实例,则使用Support包中的Fragment,否则用默认的Fragment,这样就兼容了有些应用的项目的基类不是AppComponentActivity(FragmentActivity)的情形,当然,原则上最低支持4.0,即默认Fragment的支持版本。
class PermissionFragmentFactory {
private static final String FRAGMENT_TAG = “permission_fragment_tag”;
static IPermissionActions create(Activity activity) {
IPermissionActions action;
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
PermissionSupportFragment permissionSupportFragment = (PermissionSupportFragment) supportFragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (null == permissionSupportFragment) {
permissionSupportFragment = new PermissionSupportFragment();
supportFragmentManager.beginTransaction()
.add(permissionSupportFragment, FRAGMENT_TAG)
.commitNowAllowingStateLoss();
}
action = permissionSupportFragment;
} else {
android.app.FragmentManager fragmentManager = activity.getFragmentManager();
PermissionFragment permissionFragment = (PermissionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (null == permissionFragment) {
permissionFragment = new PermissionFragment();
activity.getFragmentManager().beginTransaction()
.add(permissionFragment, FRAGMENT_TAG)
.commitAllowingStateLoss();
}
action = permissionFragment;
}
return action;
}
}
复制代码
至此,整个请求链已经很像最外层暴露的CheckAndRequestPermission方法了,就差一个Activity了,那么参数Activity怎么来呢?我们继续想办法。
再舍去Activity:
当然是使用Application中的ActivityLifecycleCallbacks,使用它的 registerActivityLifecycleCallbacks,感知Activity声明周期变化,获取到当前应用栈顶的Activity,这样我们就不需要自己手动传入了。
public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {
WeakReference topActWeakReference;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//原则上只需要onResume,兼容如果在onCreate的时候做权限申请保证此时有Activity对象
topActWeakReference = new WeakReference<>(activity);
}
//…
@Override
public void onActivityResumed(Activity activity) {
topActWeakReference = new WeakReference<>(activity);
}
//…
}
复制代码
注册它仅仅需要一个Application:
/**
- @param context Application
*/
private void registerLifecycle(Application context) {
if (null != lifecycle) {
context.unregisterActivityLifecycleCallbacks(lifecycle);
}
lifecycle = new PermissionActivityLifecycle();
context.registerActivityLifecycleCallbacks(lifecycle);
}
复制代码
这样一来,只要调用了初始化方法registerLifecycle,我们就能提供提供栈顶Activity了
/**
-
获取栈顶Activity
-
@return 当前应用栈顶Activity
-
@throws InitException 初始化失败
-
@throws ContainerStatusException Activity状态异常
*/
private Activity getContainer() {
// may auto init failed
if (null == lifecycle || null == lifecycle.topActWeakReference) {
throw new InitException();
}
// activity status error
if (null == lifecycle.topActWeakReference.get() || lifecycle.topActWeakReference.get().isFinishing()) {
throw new ContainerStatusException();
}
return lifecycle.topActWeakReference.get();
}
复制代码
结合起来回到我们之前申请权限的方法(省略了日志打印和线程的判断,如果需要再细看源码):
private void requestPermissions(final Permissions permissions, final CheckRequestPermissionsListener listener) {
//check container status
final Activity activity;
try {
activity = getContainer();
} catch (Exception e) {
//activity status error do not request
return;
}
//…
//finally request
requestRuntimePermission(activity, permissions.getPermissions(), listener);
}
复制代码
至此,我们已经能脱离Activity和Fragment,也无需重写onPermissionResult了,只需要一个ApplicationContext初始化即可。
能否更简便一点?
最后避掉Application(免初始化):
我们可以自定义ContentProvider来完成库的初始化,我们可以参考Lifecycle组件的初始化:
//lifeCycle定义的初始化Provider
public class LifecycleRuntimeTrojanProvider extends ContentProvider {
@Override
public boolean onCreate() {
LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}
}
复制代码
和它的Manifest文件:
<provider
android:name=“android.arch.lifecycle.LifecycleRuntimeTrojanProvider”
android:authorities=“${applicationId}.lifecycle-trojan”
android:exported=“false”
android:multiprocess=“true” />
复制代码
参照它的实现给我们提供了一个很好的思路,我们可以自定义Provider去初始化一些库或者其他的内容,现在我们写一个自己的initContentProvider:
public class InitProvider extends ContentProvider {
@Override
public boolean onCreate() {
//初始化我们的库
SoulPermission.getInstance().autoInit((Application) getContext());
return true;
}
//…
}
复制代码
在库的AndroidManifest文件中声明:
<provider android:authorities=“${applicationId}.permission.provider”
android:name=“.permission.InitProvider”
android:multiprocess=“true”
android:exported=“false”/>
复制代码
至于为什么这个Context就是Application,我们可以参考ActivityThread中的对ContentProvider的初始化:
public void handleInstallProvider(ProviderInfo info) {
//即我们的应用的Application
installContentProviders(mInitialApplication, Arrays.asList(info));
}
复制代码
至此,我们权限申请流程就跟Activity、Fragment、乃至Context都没有关系了。
去除if&else、涵盖版本判断:
虽然我们完成了对运行时权限的申请流程,但是毕竟只针对6.0以上机型,如果上面流程还想一句话完成的话,那我们还得兼容老的机型,so,我们需要做在方法内做一个版本判断:
首先判断系统版本
public static boolean isOldPermissionSystem(Context context) {
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
return android.os.Build.VERSION.SDK_INT < M || targetSdkVersion < M;
}
复制代码
然后是检查权限:
6.0以上当然是走系统Api:
class RunTimePermissionChecker implements PermissionChecker {
private String permission;
private Context context;
RunTimePermissionChecker(Context context, String permission) {
this.permission = permission;
this.context = context;
}
@TargetApi(M)
@Override
public boolean check() {
int checkResult = ContextCompat.checkSelfPermission(context, permission);
return checkResult == PackageManager.PERMISSION_GRANTED;
}
}
复制代码
6.0以下、4.4以上通过AppOps反射获取(为了保证一致性,把权限名称参数在check方法中做了映射,把权限的String参数映射成checkOp的整形参数):
class AppOpsChecker implements PermissionChecker {
private Context context;
private String permission;
AppOpsChecker(Context context, String permission) {
this.context = context;
this.permission = permission;
}
/**
-
老的通過反射方式檢查權限狀態
-
结果可能不准确,如果返回false一定未授予
-
按需在里面添加
-
如果没匹配上或者异常都默认权限授予
-
@return 检查结果
*/
@Override
public boolean check() {
if (null == permission) {
return true;
}
switch (permission) {
case Manifest.permission.READ_CONTACTS:
return checkOp(4);
case Manifest.permission.WRITE_CONTACTS:
return checkOp(5);
case Manifest.permission.CALL_PHONE:
return checkOp(13);
case Manifest.permission.READ_PHONE_STATE:
return checkOp(51);
case Manifest.permission.CAMERA:
return checkOp(26);
case Manifest.permission.READ_EXTERNAL_STORAGE:
return checkOp(59);
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
return checkOp(60);
case Manifest.permission.ACCESS_FINE_LOCATION:
case Manifest.permission.ACCESS_COARSE_LOCATION:
return checkOp(2);
default:
break;
}
return true;
}
最后
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
*/
@Override
public boolean check() {
if (null == permission) {
return true;
}
switch (permission) {
case Manifest.permission.READ_CONTACTS:
return checkOp(4);
case Manifest.permission.WRITE_CONTACTS:
return checkOp(5);
case Manifest.permission.CALL_PHONE:
return checkOp(13);
case Manifest.permission.READ_PHONE_STATE:
return checkOp(51);
case Manifest.permission.CAMERA:
return checkOp(26);
case Manifest.permission.READ_EXTERNAL_STORAGE:
return checkOp(59);
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
return checkOp(60);
case Manifest.permission.ACCESS_FINE_LOCATION:
case Manifest.permission.ACCESS_COARSE_LOCATION:
return checkOp(2);
default:
break;
}
return true;
}
最后
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-mbaMOThH-1715874821461)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-Fks1bUE5-1715874821462)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!