美文网首页Android开发入门教程
Android开发学习 -- Day16 内容提供器

Android开发学习 -- Day16 内容提供器

作者: h080294 | 来源:发表于2018-03-28 17:13 被阅读13次

    我们在前面初步学习了Android的Activity和BroadcastReceiver组件,接下来要继续学习下一个组件内容提供器 -- Content Provider。

    一、内容提供器简介

    内容提供器(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前, 使用内容提供器是 Android实现跨程序共享数据的标准方式。

    不同于文件存储和 SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选
    择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。

    不过在正式开始学习内容提供器之前,我们需要先掌握另外一个非常重要的知识
    Android 运行时权限,因为待会的内容提供器示例中会使用到运行时权限的功能。

    二、运行时权限

    Android开发团队在Android 6.0系统中引入了运行时权限这个功能,用以加强保护用户的安全隐私。

    1、Android权限机制

    在学习广播的时候,当时为了要访问系统的网络状态,我们在AndroidManifest中添加过如下的声明:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.johnhao.listviewdemo">
        ...
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        ...
    </manifest>
    

    这是因为访问系统的网络状态涉及到了用户设备的安全性,因此必须在AndroidManifest中添加声明,否则程序就会出现大问题。再添加过相应的声明后,用户安装该app时,就会在安装界面提示用户该app申请使用了哪些权限。如果用户不认可某些权限,就可以选择不安装。

    但是市场上有很多我们常用的软件存在着滥用权限的情况,但是我们又不得不用,一瞬间有种店大欺客的感觉。Android团队也认识到这个问题,于是在6.0系统中加入了运行时权限。用户不需要在安装软件的时候一次性授权所有的申请权限,可以在使用过程中再对某一项全项进行授权。比如一款拍照软件,要访问通讯录权限,就算用户拒绝了这个权限,该应用的其他功能还是能正常使用,不会说不给通讯录权限就拍不了照了。

    Android将现有的权限分成了两类,一类是普通权限,一类是危险权限。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,这部分权限系统自动帮我们进行授权。危险权限则表示非常有可能会触及用户隐私或对设备造成安全影响,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序无法使用相应的功能。

    图中所示的为Android的危险权限,涉及9组24个权限。每一个危险权限都属于一个权限组,当用户授权了权限组中任意一个权限是,改组的其他权限也会同时被授权。

    2、在程序运行时申请权限

    简单的介绍完Android的权限机制后,下面我们用打电话的例子来体验一下。我们从上面的表中看到PHONE权限组是危险权限,因为拨打电话会设计用户的手机资费问题,因为被列入了危险权限。新建一个Activity,增加一个Button用来拨打电话:

    public class ContentProviderMakeCallActivity extends BaseActivity {
    
        private Button make_call;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_content_provider_make_call);
            setTitle("运行时权限Demo -- 拨打电话");
            make_call = findViewById(R.id.btn_content_call);
    
            make_call.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 运行时权限检测
                    if (ContextCompat.checkSelfPermission(ContentProviderMakeCallActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(ContentProviderMakeCallActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1);
                    } else {
                        call();
                    }
                }
            });
        }
    
        private void call() {
            try {
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10086"));
                startActivity(intent);
            } catch (SecurityException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        call();
                    } else {
                        Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
            }
        }
    }
    

    首先我们定义了一个call()方法,把拨打电话的逻辑写在了里面。构建一个隐式Intent,Intent的action指定为Intent.ACTION_CALL,这是系统内置的打电话动作,然后在data部分指定了协议是tel,号码为10086。为了防止应用程序的崩溃,我们把操作都放在异常捕获代码块中。

    回到onCreate()方法,我们在执行call()方法拨打电话前,需要先确认用户是不是已经给过授权。这里用的是ContextCompat.checkSelfPermission()方法,该方法接收两个参数,第一个参数是Context,第二个是具体的权限名,例如拨打电话就是Manifest.permission.CALL_PHONE。然后我们把返回值和PackageManager.PERMISSION_GRANTED做比较,相等说明用户已经授权,可以调用call()方法进行拨打电话操作;不等表示没有授权,则需要进一步调用ActivityCompat.requestPermissions()方法来向用户申请授权。requestPermissions()方法接收3个参数,第一个参数是Context,第二个参数是一个String数组,把权限名放在数组中即可,第三个参数是requestCode,只要是唯一值就行。

    调用完requestPermissions()方法后,会在界面上弹出一个授权弹窗,无论用户点击授权或拒绝,最终都会回调onRequestPermissionsResult()方法,并将结果封装在grantResults中。这里,我们只需要根据对应的requestCode,判断下grantResults结果。如果授权了,就调用call()方法拨打电话, 如果没有授权就算了。


    三、访问其他程序中的数据

    如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序都可以对这部分数据进行访问。Android系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口。我们接下来就看看,如何读取联系人信息。在设备中添加一些测试的电话号码,一会儿我们会读取这些信息。

    public class ContentProviderContactActivity extends BaseActivity {
    
        private ListView lv;
        private List<String> list = new ArrayList<>();
        private ArrayAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_content_provider_contact);
            setTitle("Contact练习");
            lv = findViewById(R.id.listview_content_provider_contacts);
            adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
            lv.setAdapter(adapter);
    
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
            } else {
                readContacts();
            }
        }
    
        private void readContacts() {
            Cursor cursor = null;
            try {
                cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
                if (cursor != null) {
                    while (cursor.moveToNext()) {
                        String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                        String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        list.add(name + "\n" + number);
                    }
                    adapter.notifyDataSetChanged();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        readContacts();
                    } else {
                        Toast.makeText(this, "You denied the permission", Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
            }
        }
    }
    

    我们准备了一个ListView用于存放联系人信息,然后使用ArrayAdapter适配器,这里因为读取联系人是危险权限,因此需要添加处理运行时权限的逻辑,在上面一节中已经掌握了。下面重点看一下readContacts()方法。

    这里我们用了ContentResolver的query()方法来查询系统的联系人数据。对于每个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类,我们可以通过Context中的getContentResolver()方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作。其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。是不是跟SQLite的操作感觉很类似,但不同的地方在于ContentResolver中的CRUD操作都不接收表名参数,而是使用了一个Uri参数代替,这个参数被称为内容URI。内容URI给Content Provider中的数据建立了唯一标识符,主要由两部分组成:authority和path。authority用于对不同的应用程序做区分,通常以包名的形式命名。path则用于对同一个应用程序中不同的表做区分。通过内容URI,可以非常清晰的表达出我们想要访问那个程序的哪张表的数据。

    这里我们使用的是ContactsContract.CommonDataKinds.Phone封装好的CONTENT_URI常量,这个常量就是使用Uri.parse()方法解析出来的结果。接着就遍历Cursor对象,将联系人的姓名和电话取出,并添加到List数据中,然后通知刷新一下ListView,一定不能忘记的事情是将Cursor关掉。

    最后,在AndroidManifest中添加读取联系人的权限声明。重新运行一下程序:


    关于Content Provider,还有些分知识没有学习,例如如何创建自己的内容提供器,大家可以自学一下。

    关注获取更多内容

    相关文章

      网友评论

        本文标题:Android开发学习 -- Day16 内容提供器

        本文链接:https://www.haomeiwen.com/subject/wdtucftx.html