Skip to content

Latest commit

 

History

History
338 lines (280 loc) · 14.5 KB

3.7.md

File metadata and controls

338 lines (280 loc) · 14.5 KB

Android权限机制

安卓权限系统

我们在安装一个安卓apk文件时,会提醒我们这个安卓应用都使用了哪些权限,比如说下面就是一个典型的安装apk文件时产生的提醒。


比如说这个app可能会安装快捷方式、读取SD卡、使用网络数据等等。对于这些权限,相信用过安卓手机人都很清楚的吧。

不过在安卓6.0以前,安卓权限系统有一个巨大的弊端就是如果你安装一个软件就要默认应用使用所有的这些权限,比如说一个应用需要读写SD卡权限,也需要读取短信权限。你并不想让这个应用读取你的短信,读取SD卡还可以接受。但是如果你不接受这个权限里的一个,你只能选择不安装这个应用。这于情于理都显得不合适。

在安卓6.0以后,google推出了动态权限,也就是可以让用户授权一个app可以使用哪些权限。比如说,在安卓6.0以后如果要读写SD卡的话,APP就要先提出申请,让用户授权。


正常权限和危险权限

虽然在安卓6.0以后推出了动态权限机制,但并不是对于所有的权限都需要动态的申请。Google把安卓里的权限划分为普通权限和危险权限两类。

普通权限,比如说使用网络数据,这是一般每个app都要用到的,网络权限也没有涉及到用户的隐私问题,所以是个安全的权限。它的使用方法和安卓6.0以前一样,只需要在manifest文件里面注册一个uses-permission标签就好了。

<uses-permission android:name="android.permission.INTERNET"/>

危险权限,如读取短信权限,读取SD卡,打电话等等权限都属于危险权限。在Android6.0之前,也是直接写在manifest文件里就可以,而Android6.0之后,需要获取用户的授权才能使用。

有关权限的名称、说明、解释等等,可以在开发者文档里面找到:Manifest.permission

例如,可以看到,网络权限(android.permission.INTERNET)是一个普通权限。


而打电话权限(android.permission.CALL_PHONE)是一个危险权限。


下面我们通过网络请求和打电话请求两个例子来学习一下普通权限和危险权限。

使用权限-网络请求

首先,我们先创建一个新的工程叫做:NetRequest。然后创建一个类HttpUtils,完成网络请求的方法。

package kalen.app.example.netrequest;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

public class HttpUtils {
    static String TestGet() {
        try {
            URL url = new URL("https://www.baidu.com");
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Charset", "UTF-8");
            connection.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");

            if(connection.getResponseCode() == 200 ){
                InputStream is = connection.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                String result = "", line = null;

                while ((line = reader.readLine()) != null) {
                    result += line;
                }

                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "Error";
    }

}

这里,�使用了URLConnection对象来访问baidu.com的源代码。下面,我们在MainActivity中把信息打印出来。注意:Android中不允许在主(UI)线程里面进行网络请求(其实iOS、winform都是这样的),所以我们新开一个线程来进行访问。

package kalen.app.example.netrequest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Thread t =new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Start Http Get ....");
                String result = HttpUtils.TestGet();
                System.out.println(result);
            }
        });
        t.start();

    }

}

在这里,我们先不在manifest里面注册,同时打开DDMS里的LogCat,并运行程序,看看会提示什么错误出来。


可以看到这里异常被try catch捕获,显示出了Permission denied的字样,我们再在manifest里面加上android.permission.INTERNET的权限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kalen.app.example.netrequest">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

运行程序,查看日志,可以看到显示出了网页的源代码。


到这里,我们也算了解了安卓基本的权限机制的使用了。

另外,在App开发中,网络基本上无处不在,几乎所有的app都要和服务器进行通信。虽然本教程即讲解了App的开发,又涉及到web应用的开发。不过本教程不是一个专业App的开发教程,在安卓中,网络请求都要放在后台来执行,这就需要其他的组件来让UI组件和后台组件进行交互,相关的内容读者可以继续学习handler以及AsyncTask等等。

Android6.0权限申请

下面,我们创建一个新的工程:CallPhone。和普通权限一样,危险权限首先也要在manifest文件里注册。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kalen.app.example.callphone">

    <uses-permission android:name="android.permission.CALL_PHONE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

接下来,我们编写代码,在xml里面放一个button用来点击发起打电话事件,并通过两行代码实现打电话的功能。

package kalen.app.example.callphone;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doCallPhone();
            }
        });
    }

    void doCallPhone() {
        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:10086"));
        startActivity(intent);
    }
}

在Android6.0以前,我们只要像上面这么写便可以发起危险权限(打电话)的操作了。

下面,我们来看看Android6.0以后如何使用危险权限,这里主要涉及到四个API,分别是检查权限、申请权限、以及权限的回调、权限解释。

1、权限检查

我们下面创建一个函数mayCallPhone用来处理权限的申请。首先通过ContextCompat.checkSelfPermission检查我们要的权限是否被授权。如果已授权的话那就会返回PackageManager.PERMISSION_GRANTED,反之则返回PackageManager.PERMISSION_DENIED。如果授权过了,就可以直接执行打电话操作了,反之,则要进行申请。

void mayCallPhone() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
        // 这里编写申请权限的操作

    } else {
        doCallPhone();
    }
}

2、权限申请

如果没有权限的话,我们需要通过ActivityCompat.requestPermissions来进行申请,一次可以申请多个权限,我们需要传入一个数组。另外,还需要一个requestCode用来辨别申请那组危险权限,这里我随便定义一个变量值为101。

private static final int REQ_CALL_PHONE_CODE = 101;
void mayCallPhone() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
        // 申请权限
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQ_CALL_PHONE_CODE);

    } else {
        doCallPhone();
    }
}

3、权限回调

下面,我们在Activity里面重载onRequestPermissionsResult方法,这个方法是个回调函数,在用户允许或拒绝app请求的权限时触发。这里如果请求的是我们的打电话权限,requestCode就会为101。另外,因为我们只请求了一个权限,所以把grantResults的第一个元素取出来就是用户返回的结果。和ContextCompat.checkSelfPermission类似,如果用户同意我们就执行危险权限操作,用户不同意这里给一个提示信息。

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQ_CALL_PHONE_CODE) {
        if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "用户拒绝了使用打电话权限", Toast.LENGTH_LONG).show();
        } else {
            doCallPhone();
        }
    }
}

4、权限解释

有的时候,用户拒绝了我们的权限,我们可以继续发出申请,这个时候可以解释一下为什么要使用这个权限,然后再让用户信服。

void mayCallPhone() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CALL_PHONE)) {
            Toast.makeText(this, "请打开打电话权限,用来测试", Toast.LENGTH_LONG).show();
        }
        // 申请权限
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQ_CALL_PHONE_CODE);

    } else {
        doCallPhone();
    }
}

测试运行

到这里代码就编写完成了,我们把之前的onClick中的doCallPhone改成mayCallPhone,完整代码如下:

package kalen.app.example.callphone;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mayCallPhone();
            }
        });
    }

    private static final int REQ_CALL_PHONE_CODE = 101;
    void mayCallPhone() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.CALL_PHONE)) {
                Toast.makeText(this, "请打开打电话权限来测试", Toast.LENGTH_LONG).show();
            }
            // 申请权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQ_CALL_PHONE_CODE);

        } else {
            doCallPhone();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQ_CALL_PHONE_CODE) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "用户拒绝了使用打电话权限", Toast.LENGTH_LONG).show();
            } else {
                doCallPhone();
            }
        }
    }

    void doCallPhone() {
        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:10086"));
        startActivity(intent);
    }
}

下面,运行程序,并点击Button,可以看到这里显示出了打电话的申请。


我们第一次可以选择拒绝,然后当第二次点击button的时候,就会解释为什么要开启打电话权限。


这个时候点击同意,可以看到发起了打电话的请求,拨打了10086。


链接