Android之运行时相机权限和联系人权限获取(最后又源码可以下载下来看)


知识点:

1、Android M 及以上系统的动态权限申请;

2、知识名词记录

{

CameraPreview:自定义相机预览类

ViewAnimator:配合framelayout使用,在两个view之间切换时,会有切换动画

ContactsContract:

CursorLoader:

ContentProviderOperation:插入联系人用

读取联系人列表;

}(这里我决定,每篇文章中遇到的“新名词”我都会记录在这里,作为自己下一步需要了解的知识;大家有兴趣的话,也可以循着这些新的专业名词,一步一步的走向Android更深的“泥潭”)


如果你有持续关注Android官方最新的SDK版本的话,你就会知道,Android官方对权限的管理变得原来越严格了。在5.0之后,权限不再是全部都在manifest文件里头申请就OK了,你APP要的权限,当用到的时候,系统才会赋予给你。


当然,你可以这样做,但是要把你的targetSdkVersion 变为22以下,这样是可以暂时避免动态申请权限引起的“麻烦事”,但是这是一个不可逆的趋势,我们必须要顺从它,而不是抗拒它,况且紧靠几个人几个应用,那是螳臂当车。


权限分为:普通权限,危险权限和系统权限;

普通权限:这些权限对于用户隐私和设备操作不会造成太多危险,主要有:

ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS


危险权限,主要是包含产生费用或者是读取用户隐私的权限,包含:

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS


上图:

android申请相机权限 允许本次使用 权限能保持多久 手机相机权限_android


下面我这里利用请求相机和联系人权限来演示一下在Android M 系统上的权限请求;我在这里做了两个fragment页面,分别是相机和联系人相关的动作。

这里我就只是贴一个主要的页面代码就好了,我这里只是要一个触发相机和联系人的动作就好了,代码里头都有说明。

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentTransaction;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ViewAnimator;

import com.example.android.common.logger.Log;
import com.example.android.common.logger.LogFragment;
import com.example.android.common.logger.LogWrapper;
import com.example.android.common.logger.MessageOnlyLogFilter;
import com.example.android.system.runtimepermissions.camera.CameraPreviewFragment;
import com.example.android.system.runtimepermissions.contacts.ContactsFragment;

import common.activities.SampleActivityBase;

public class MainActivity extends SampleActivityBase
        implements ActivityCompat.OnRequestPermissionsResultCallback {

    public static final String TAG = "MainActivity";

    /* 相机请求码 */
    private static final int REQUEST_CAMERA = 0;

    /* 联系人请求码 */
    private static final int REQUEST_CONTACTS = 1;

    /* 请求读取联系人权限 */
    private static String[] PERMISSIONS_CONTACT = {Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS};

    // 标志log fragment是否显示
    private boolean mLogShown;

    /* 主页的布局,依靠动态加载进来 */
    private View mLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLayout = findViewById(R.id.sample_main_layout);

        if (savedInstanceState == null) {
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            RuntimePermissionsFragment fragment = new RuntimePermissionsFragment();
            transaction.replace(R.id.sample_content_fragment, fragment);
            transaction.commit();
        }
        initializeLogging();
    }

    /**
     * 点击显示联系人按钮相应
     * <p>
     * 回调已经被定义好了
     */
    public void showCamera(View view) {
        Log.i(TAG, "检查权限是否被受理!");
        // 检查是否想要的权限申请是否弹框。如果是第一次申请,用户不通过,
        // 那么第二次申请的话,就要给用户说明为什么需要申请这个权限
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            // 权限未被授予
            requestCameraPermission();
        } else {
            Log.i(TAG, "相机权限已经被受理,开始预览相机!");
            showCameraPreview();
        }
    }

    /**
     * 申请相机权限
     */
    private void requestCameraPermission() {
        Log.i(TAG, "相机权限未被授予,需要申请!");
        // 相机权限未被授予,需要申请!
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            // 如果访问了,但是没有被授予权限,则需要告诉用户,使用此权限的好处
            Log.i(TAG, "申请权限说明!");
            Snackbar.make(mLayout, R.string.permission_camera_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            // 这里重新申请权限
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[]{Manifest.permission.CAMERA},
                                    REQUEST_CAMERA);
                        }
                    })
                    .show();
        } else {
            // 第一次申请,就直接申请
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                    REQUEST_CAMERA);
        }
    }

    public void showContacts(View v) {
        // 判断权限是否拥有
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED
                || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "读写联系人权限未被授予,需要申请!");
            // 读写联系人权限未被授予,需要申请!
            requestContactsPermissions();
        } else {
            // 权限已经被授予,显示细节页面!
            Log.i(TAG, "权限已经被授予,显示细节页面!");
            showContactDetails();
        }
    }

    /**
     * 申请联系人读取权限
     */
    private void requestContactsPermissions() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.READ_CONTACTS)
                || ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.WRITE_CONTACTS)) {
            // 如果是第二次申请,需要向用户说明为何使用此权限,会带出一个不再询问的复选框!
            Log.i(TAG, "如果是第二次申请,需要向用户说明为何使用此权限,会带出一个不再询问的复选框!");

            Snackbar.make(mLayout, R.string.permission_contacts_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ActivityCompat
                                    .requestPermissions(MainActivity.this, PERMISSIONS_CONTACT,
                                            REQUEST_CONTACTS);
                        }
                    })
                    .show();
        } else {
            // 第一次申请此权限,直接申请
            ActivityCompat.requestPermissions(this, PERMISSIONS_CONTACT, REQUEST_CONTACTS);
        }
    }

    /**
     * 显示相机预览界面
     */
    private void showCameraPreview() {
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
                .addToBackStack("contacts")
                .commit();
    }

    /**
     * 显示联系人页面
     */
    private void showContactDetails() {
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.sample_content_fragment, ContactsFragment.newInstance())
                .addToBackStack("contacts")
                .commit();
    }


    /**
     * 申请权限的回调,
     *
     * @param requestCode  requestCode
     * @param permissions  permissions
     * @param grantResults grantResults 多个权限一起返回
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CAMERA) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Snackbar.make(mLayout, R.string.permision_available_camera,
                        Snackbar.LENGTH_SHORT).show();
            } else {
                Snackbar.make(mLayout, R.string.permissions_not_granted,
                        Snackbar.LENGTH_SHORT).show();
            }
        } else if (requestCode == REQUEST_CONTACTS) {
            // 这里有个多权限的检查,需要检查每一个权限是否都被授权了
            if (PermissionUtil.verifyPermissions(grantResults)) {
                // true,所有权限已经被授予
                Snackbar.make(mLayout, R.string.permision_available_contacts,
                        Snackbar.LENGTH_SHORT)
                        .show();
            } else {
                // false,并不是所有权限都被授予
                Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT).show();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
        logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
        logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_toggle_log:
                mLogShown = !mLogShown;
                ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
                if (mLogShown) {
                    output.setDisplayedChild(1);
                } else {
                    output.setDisplayedChild(0);
                }
                supportInvalidateOptionsMenu();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * 初始化log日志
     */
    @Override
    public void initializeLogging() {
        LogWrapper logWrapper = new LogWrapper();
        Log.setLogNode(logWrapper);

        MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
        logWrapper.setNext(msgFilter);

        LogFragment logFragment = (LogFragment) getSupportFragmentManager()
                .findFragmentById(R.id.log_fragment);
        msgFilter.setNext(logFragment.getLogView());
    }

    public void onBackClick(View view) {
        // 因为我们对fragment入栈处理,按返回键的时候,出栈处理
        getSupportFragmentManager().popBackStack();
    }

}



第一,我们申请权限的方法:


ActivityCompat.requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode){}

第二,是申请的结果回调方法:

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults){}


以上就是主要的两个方法了。

当然,还要注意一下,是否是第一次申请权限?需不需要想用户说明我这个权限是用来干嘛的?如果用户不授权,我的APP该如何来操作防止崩溃?用户随时可以取消对你APP的授权,那时我们又该如何来做?

问题好多好多,我们要走的路还很长。但是我这里有几个方法,适合大家去咀嚼咀嚼。

第一个:

ActivityCompat.shouldShowRequestPermissionRationale(this,

                Manifest.permission.READ_CONTACTS)

这个方法呢,说的是,当你第一次去请求权限的时候,如果用户拒绝了,然后第二次再去申请此权限,那么这个方法会返回一个true的结果,告诉你,上一次用户不同意给你这个权限,然后这次你就需要向用户说明为什么你需要这个权限。


我们看到的效果图,是有相机预览和查看联系人的页面的,这里我就不一一把代码贴出来,我会在最后面把工程代码放到GitHub上面,给大家下载。里面有更加详细的说明。

代码下载:点击打开链接

如有任何问题,请及时与我联系,谢谢!