Android运行时权限
CharleyLitt
8年前
<h2>1. Android权限机制</h2> <p>我们知道,为了要使开发出来的APP能够从网络上获取数据,就要申请接入网络的权限,只要在AndroidManifest.xml中添加这样一句权限声明即可:</p> <pre> <code class="language-java"><manifest package="com.example.jaydragon.logindemo" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> ... </manifest></code></pre> <p>其实权限机制的设计思路非常简单,就是用户如果认可你所申请的权限,那么就会安装你的程序,如果不认可,那么就拒绝安装。</p> <p>但是理想是美好的,现实却很残酷,因为很多我们离不开的常用软件普遍存在着滥用权限的情况,不管用不用得到,反正先申请了再说。</p> <p>Android开发团队当然也意识到了这个问题,于是在Android6.0系统加入了运行时权限功能。 也就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件使用的过程中再对某一项权限申请进行授权。 比如说一款相机应用在运行时申请了地理位置定位权限,就算我拒绝了这个权限,但是我仍然可以继续使用这个软件的其他功能,而不是像之前那样直接无法安装它。</p> <p>当然,并不是所有权限都需要在运行时申请。Android现在将权限归成了两类,一类是普通权限,一类是危险权限。对于普通权限,系统会自动帮我们进行授权,而危险权限就需要用户动手点击授权才可以,否则程序就无法使用相应的功能。</p> <p>但是Android中一共有上百种权限,我们怎么从中区分哪些是普通权限,哪些是危险权限呢?不用慌,因为危险权限就那么几个,我们记住(或熟悉)它们,剩下的旧都是普通权限了。</p> <p>下表列出了Android中所有的危险权限,一共9组24个危险权限。</p> <table> <thead> <tr> <th>权限名称</th> <th>权限名</th> </tr> </thead> <tbody> <tr> <td>CALENDAR</td> <td>READ_CALENDAR 、WRITE_CALENDAR</td> </tr> <tr> <td>CAMERA</td> <td>CAMERA</td> </tr> <tr> <td>CONTACTS</td> <td>READ_CONTACTS 、WRITE_CONTACTS、 GET_ACCOUNTS</td> </tr> <tr> <td>LOCATION</td> <td>ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION</td> </tr> <tr> <td>MICROPHONE</td> <td>RECORD_AUDIO</td> </tr> <tr> <td>PHONE</td> <td>READ_PHONE_STATE、CALL_PHONE、READ_CALL_LOG、WRITE_CALL_LOG、ADD_VOICEMAIL、USE_SIP、PROCESS_OUTGOING_CALLS</td> </tr> <tr> <td>SENSOPS</td> <td>BODY_SENSORS</td> </tr> <tr> <td>SMS</td> <td>SEND_SMS、RECEIVE_SMS、READ_SMS、RECEIVE_WAP_PUSH、RECEIVE_SMS</td> </tr> <tr> <td>STORAGE</td> <td>READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE</td> </tr> </tbody> </table> <p>这里的权限你可能有的都没见过,不过没关系,你不需要了解表格中的每个权限的作用,只要把它当成一个参照表来查看就行了。每当要使用权限时,可以先到这张表来查看一下,如果是这张表里的权限,那么就需要进行运行时权限处理,如果不在这张表里,那么只需要在AndroidManifest.xml文件中添加一下权限声明就可以了。</p> <p>注意,表格中的每个权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限所队形的权限组中所有其他权限也会同时被授权。</p> <p>好了,关于Android权限机制的内容就这么多。 <strong>那么到底如何在程序运行的时候申请权限呢?</strong></p> <h2>2. 在程序运行时申请权限</h2> <p>这里我们就使用CALL_PHONE这个权限来展示一下如何在程序运行的时候进行运行时权限的处理。</p> <p>CALL_PHONE这个权限是编写打电话功能的时候需要声明的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限。这里我就直接定位在Android6.0系统及以上了,Android6.0系统以下的处理方法就是在AndroidManifest.xml中声明如下权限就OK了:</p> <pre> <code class="language-java"><uses-permisson android:name="android.permission.CALL_PHONE"/></code></pre> <p>然后,具体的处理方法,就放在onClick()方法中就行了。</p> <p>那么,在Android6.0系统及以上系统到底怎样处理危险权限呢?</p> <p>睁大眼睛,Show Time!</p> <p>首先这里有一个makeCall按钮,我们点击这个按钮就会触发申请CALL_PHONE权限了。直接上代码,然后具体分析一下:</p> <pre> <code class="language-java">@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button makeCall = (Button) findViewById(R.id.make_call); makeCall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1); }else{ call(); } } }); } public void call(){ try { Intent intent=new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); } catch (Exception e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case 1: if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){ call(); }else{ Toast.makeText(this, "权限被您残忍地拒绝了", Toast.LENGTH_SHORT).show(); } break; default: } }</code></pre> <p>上面的代码将运行时权限的完整流程都覆盖了,下面我们来分析一下。</p> <p>说白了,运行时权限的核心就是在程序运行过程中由用户授权去执行某些危险操作,程序是不可以擅自做主去执行这些危险操作的。因此,第一步就是要先判断用户是不是已经给应用程序授过该权限了,借助的是ComtextCompat.checkSelfPermission()方法。该方法接受两个参数,第一个参数是Context,这个没什么好说的,第二个参数是具体的权限名,比如打电话的权限名就是Manifest.permission.CALL_PHONE,然后我们使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较,相等就说明用户已经授权,不等就表示用户没有授权。</p> <p>如果已经授权的话,直接去执行拨打电话的逻辑操作就可以了,这里,我们把拨打电话的逻辑封装到了call()方法当中。在call()方法中,我们构建了一个隐式Intent,Intent的action指定为 <strong>Intent.ACTION_CALL</strong> ,这是一个系统内置的打电话的动作,然后在data部分指定了协议是 <strong>tel</strong> ,号码是10086。如果没有授权的话,则需要调用ActivityCompat.requestPermissions()方法来向用户申请授权,requestPermissions()方法接受3个参数,第一个参数要求是Activity的实例,第二个参数是一个String数组,我们把要申请的权限名放在数组中即可,第三个参数是请求码,只要是唯一值就可以了,这里传入了1。</p> <p>调用完了requestPermissions()方法之后,系统就会弹出一个权限申请的对话框,然后用户选择同意或拒绝我们的权限申请,不论是哪种结果,最终都会回调到onRequestPermissionsResult()方法中,而授权的结果则会封装到grantResults参数当中。这里,我们只要判断一下最后的授权结果,如果用户同意的话就调用call()方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并弹出一条失败提示。</p> <p>现在运行一下程序,并点击Make Call按钮,效果如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0cc3300cad5b78ed451287ef27f3403b.png"></p> <p style="text-align:center">申请电话权限对话框.png</p> <p>由于用户还没授权过拨打电话的权限,因此第一次会弹出这样一个申请权限的对话框,用户可以选择同意或拒绝,加入这里点击了DENY,效果如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/889b058b8438e0d4d72fc889bebc6126.png"></p> <p style="text-align:center">用户拒绝了权限申请.png</p> <p>由于用户没有同意授权,我们只能弹出一个操作失败的提示。下面我们再次点击Make Call按钮,仍然会弹出申请权限的对话框,这次点击ALLOW,结果如图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1eae76c1f0fad78a27901bcd1232353b.png"></p> <p style="text-align:center">拨打电话界面.png</p> <p>可以看到,这次我们就成功进入到了拨打电话的界面了,以后再次点击Make Call按钮,就可以直接拨打电话了,因为用户已经授权过了。</p> <p>好了,到这里,我们就已经将运行时权限搞得差不多了。可是你发现没有,有时我们的程序会一次性申请多个危险权限,这时候我们怎么处理呢?来来来,跟着我继续套路一下。</p> <p>假如,这次我们要申请以下4个危险权限:ACCESS_COARSE_LOCATION、ACCESS_FINE_LOCATION、READ_PHONE_STATE、WRITE_EXTERNAL_STORAGE。</p> <p>这里由于ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION属于同一个权限组,因此两者只要申请其一就可以了。那么到底怎么才能在运行时一次性申请3个权限呢?我先将代码放出来,然后再来分析一下。</p> <pre> <code class="language-java">@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... List<String> permissionList=new ArrayList<>(); if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){ permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION); } if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE)!=PackageManager.PERMISSION_GRANTED){ permissionList.add(Manifest.permission.READ_PHONE_STATE); } if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){ permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (!permissionList.isEmpty()){ String[] permissions=permissionList.toArray(new String[permissionList.size()]); ActivityCompat.requestPermissions(MainActivity.this,permissions,1); }else{ requestLocation(); } } private void requestLocation(){ initLocation(); mLocationClient.start(); } private void initLocation(){ LocationClientOption option=new LocationClientOption(); option.setScanSpan(5000); option.setIsNeedAddress(true); mLocationClient.setLocOption(option); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case 1: if (grantResults.length>0){ for (int result:grantResults) { if (result!=PackageManager.PERMISSION_GRANTED){ Toast.makeText(this, "必须同意所有权限才能使用本程序", Toast.LENGTH_SHORT).show(); finish(); return; } } requestLocation(); }else{ Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show(); finish(); } break; default: } }</code></pre> <p>首先创建了一个List集合,然后一次判断这3个权限有没有被授权,如果没被授权就添加到List集合中,最后将List集合转换成数组,在调用ActivityCompat.requestPermission()方法一次性申请。除此之外,onRequestPermissionsResult()方法中对权限结果的逻辑处理也和之前有所不同,这次我们通过一个循环将申请的每个权限都进行了判断,如果有任何一个权限被拒绝了,那么就直接调用finish()方法关闭当前程序。</p> <p>以上代码是关于百度地图定位的,以后会另起一篇记录一下,如何调用百度地图SDK的。</p> <p>怎么样,是不是很精髓,可惜我这是拿来主义,并不是现在的我可以独立想出来的。</p> <p> </p> <p>来自:http://www.jianshu.com/p/d529c51deb33</p> <p> </p>