「android」 内容提供器

Posted by Dawn-K's Blog on January 17, 2021

内容提供器

权限

以拨号(之前是打开拨号界面, 此处是指直接拨号)为例分析权限申请和使用的过程.

还是基于Intent, 和之前一样, 给一个按钮增加点击的监听器, 重载onClick, 写入如下内容. 为防止权限被拒绝时程序崩溃, 所以此处需要捕获异常.

1
2
3
4
5
6
7
8
try {
    // DIAL 是 打开拨号界面, CALL 是直接拨号,必须申请权限
    Intent intent = new Intent(Intent.ACTION_CALL);
    intent.setData(Uri.parse("tel:10086"));
    startActivity(intent);
} catch (SecurityException e) {
    e.printStackTrace();
}

然后修改 AndroidManifest 文件, 加入如下内容

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

但是以上功能仅能在 安卓6.0以下才能成功. 更高的版本还需要运行时权限申请

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.example.dvtester;
// Import已被删掉
public class MainActivity extends AppCompatActivity {

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

    void testCall() {
        Button makeCall = (Button) findViewById(R.id.make_call);
        makeCall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 确定是否已经获得了这个权限,如果相等就是获得了,否则就是没获得
                if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    // 向用户申请权限,第一个参数是获取权限的活动,第二个是要申请权限的列表,第三个是请求码(好像是为了标志唯一的请求)
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
                } else {
                    // 之前就有权限,可以直接打了
                    call();
                }
            }
        });
    }

    void call() {
        // 已经有权限了,可以用intent来拨号了
        try {
            // DIAL 是 打开拨号界面, CALL 是直接拨号,必须申请权限
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

    // 无论成功与否,发出申请(requestPermission)后这个函数都会回调
    // 第一个参数是请求码,与申请时的相同,第二个是当时请求的权限数组 , 第三个是申请的结果数组,也就是用于和PackageManager.PERMISSION_GRANTED比较
    @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, "You deied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }
}

通过上面的代码, 点击按钮后, 会弹出权限申请的窗口, 如果拒绝, 那么下次再点就仍会弹出询问窗口. 如果接受, 则永久接受, 除非到设置里进行拒绝.

内容提供器

URI

URI , 给内容提供器的数据建立了唯一的标识符. 分为两部分, authority和path, 标准写法就是 content://authority+path

例如 content://com.example.app.provider/table1

1
Uri uri = Uri.parse("content://com.example.app.provider/table1");

查询

getContentResolver() 就是访问内容提供器的方法,

而查询方法如下, 使用 getContentResolver().query()方法

1
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);

此方法接受五个参数, 第一个是 URI , 第二个是查询的列名, 第三个是约束条件, 第四个是约束条件中的占位符的具体值, 第五个是查询结果的排序方式.

最后用完cursor记得关闭( cursor.close() )

ArrayAdapter

ArrayAdapter是数组适配器, 这个地方目前还是不太清楚. 搜到的资料说是为了MVC结构而引入的, 让数据和视图分离. 适配器负责存取数据, 然后和View绑定, 数据变化不会影响视图.

检索联系人

这个和上面的拨号权限思路相仿. 先判断有没有权限, 有就直接读取, 没有就尝试申请.

读取界面

首先先在模拟器中新建两个联系人. 在xml中新增一个 ListView

1
2
3
4
<ListView
    android:id="@+id/contacts_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

读取联系人

在活动中增加如下代码, 这就是获取权限之后要执行的代码段.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 成员变量,分别是适配器和联系人数组,详情去看MVC架构
// 此处的适配器充当控制器的一部分,用以连接视图和数据
// 将constactsList的数据和视图上的ListView同步更改
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
private void readContects() {
    Cursor cursor = null;
    try {
        // 使用内容提供器的查询功能
        // 此处的URI不用自己解析了,安卓已经内置好了,直接调用就可.
        cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        if (cursor != null) {
            // 循环遍历查询到的所有元组
            while (cursor.moveToNext()) {
                // 看似复杂,实际上就是查询列的名字,这个名字也是安卓封装好的常量
                String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                // 这个是类中的成员变量
                contactsList.add(displayName + "\n" + number);
            }
            adapter.notifyDataSetChanged();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}

申请权限

然后申请权限, 这个和之前基本一样, 此处将返回码设置为2, 以作区分.

1
2
3
4
5
6
7
8
9
10
11
void testReadContacts() {
    ListView contactsView = (ListView) findViewById(R.id.contacts_view);
    // 下行这个 simple_list_item_1 是自带的
    adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
    contactsView.setAdapter(adapter);
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 2);
    } else {
        readContects();
    }
}

然后在之前的申请权限的回调函数里面继续添加此次回调的逻辑, 如果申请成功, 就读取联系人.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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, "You deied the permission", Toast.LENGTH_SHORT).show();
            }
            break;
        case 2:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 刚才的权限申请成功,可以读取了
                readContects();
            } else {
                Toast.makeText(this, "You deied the permission", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}