初学Android:内容提供器小结

来源:互联网 发布:php log日志类 编辑:程序博客网 时间:2024/04/19 09:12

第四篇博客

学习Android的第一本书是郭霖大神的《第一行代码Android》第二版,根据自己对这本书的理解,在总结上会有一些对这本书内容上的借鉴。

  • 本着总结学习成果的方面去考虑,希望自己能够有所进步
  • 之后写的“书中”均指的是《第一行代码Android》第二版

目录

  • 第四篇博客
  • 目录
  • 运行时权限
    • 运行时权限声明
  • 访问其他程序中的数据
    • ContentResolver
    • 创建自己的内容提供器


运行时权限

为了防止“店大欺客”,Android开发团队在Android6.0系统中引用了运行时权限功能,更好的保护了用户的安全和隐私。

而运行时权限则是在运行时若要使用某方面的权限时,才询问用户是否提供该权限(危险权限才需要用户确认是否提供)。

9组24个权限如下:

权限组名 权限名 CALENDAR READ_CALENDAR WIRTE_CALENDAR CAMERA CAMERA CONTACTS READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS LOCATION ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION MICROPHONE RECORD_AUDIO PHONE READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS SENSORS BODY_SENSORS CAMERA SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS STORAGE READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE

权限的声明需要在AndroidManifest.xml文件中声明。

运行时权限声明

在Android6.0之前,无运行时权限功能,因此对于权限的处理只需要在AndroidManifest.xml文件中声明权限,再在实现代码中构建隐式Intent,在Intent的action指定对应的权限,在对异常进行处理即可。

而在Android6.0及其之后的版本,系统在使用危险权限时都必须进行运行时权限处理。

书中示例代码如下(书中实现一个打电话的功能,需要CALL_PHONE权限):

//在onCreate方法中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();}

在这之中,使用ContextCompat.checkSelfPermission()方法判断用户是否已经给该程序授权。
checkSelfPermission()方法接收两个参数,第一个是Context,第二个参数是具体的权限名,之后方法的返回值与Package.PERMISSION_GRANTED比较,相等说明已经授权,不等表示没有授权。

在Activity中:

private void call() {    try {        Intent intent = new Intent(Intent.ACTION_CALL);        intent.setData(Uri.parse("tel:10086"));        startActivity(intent);    } catch (SecurityException e) {        e.printStackTrace();    }}@Overridepublic void onRequestPermissionResult(int requestCode, String[] permissions, int[] 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:    }}

若无授权则需要调用ActivityCompat.requestPermissions()方法向用户申请授权,requestPermissions()方法接收三个参数,第一个是Activity的实例,第二个是一个String数组,将申请的权限名放在数组中,第三个参数是请求码(唯一值)。

调用完requestPermissions()方法后,系统弹出权限申请的对话框,供用户选择是否同意权限申请,最终会回调至onRequestPermissionsResult()方法中,授权的结果则会封装在grantResults参数中,即重写onRequestPermissionsResult()方法只需要判断最终的授权结果即可。

访问其他程序中的数据

内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。

ContentResolver

对弈每一个应用程序来说,如果想要访问内容提供器中的共享数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver()方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作。
不同于SQLiteDatabase,ContentResolver中的增删改查都是不接受表名参数,而是使用一个Uri参数代替,参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,他主要由两部分组成:authority和path。authority是用于对不同的应用程序做起飞,一般为了避免冲突,使用程序包名的方式来命名。path则是用于对同一个应用程序中不同的表做区分的,通常都会添加到authority的后面。格式:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
得到内容URI字符串之后,需要解析成Uri对象才可以作为参数传入。

解析方式代码如下:

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

书中使用Uri对象来查询table1表的数据,代码如下:

Cursor cursor = getContentResolver().query(    uri,    projection,    selection,    selectionArgs,    sortOrder    );

显然这些参数可以根据名称意思并且通过与数据库的query()方法的参数类比可以得知。

而且Cursor对象的读取数据也与数据库的方法相同。

查询数据
书中示例代码:

if (cursor != null) {    while(cursor.moveToNext()) {        String column1 = cursor.getString(cursor.getColumnIndex("column!"));        int column2 = cursor.getInt(cursor.getColumnIndex("column2"));    }    cursor.close();}

添加数据
书中示例代码:

 ContentValues values = new ContentValues(); values.put("column1", "text"); values.put("column2", 1); getContentResolver().insert(uri, values);

更新数据
书中示例代码:

ContentValues values = new ContentValues();values.put("column1", "");getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});

删除数据
书中示例代码:

getContentResolver().delete(uri, "column2 = ?", new String[] {"1"});

创建自己的内容提供器

创建自己的内容提供器就是新建一个类去继承ContentProvider,其中ContentProvider类有6个抽象方法,使用子类继承时,需要将这6个方法全部重写。

  1. onCreate()
    初始化内容提供器的时候调用。通常会在此完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false则表示失败。只有当存在ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化

  2. query()
    uri参数确定查询的表,projection参数用于确定查询那些列,selection和selectionArgs参数用于约束查询那些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回

  3. insert()
    uri参数确定添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新记录的URI
  4. update()
    uri参数确定更新哪张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束查询那些行,受影响的行数将作为返回值返回
  5. delete()
    uri参数确定删除哪张表中的数据,selection和selectionArgs参数用于约束查询那些行,被删除的行数将作为返回值返回
  6. getType()
    根据传入的内容URI来返回相应的MIME类型

标准的URI写法:

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

查询com.example.app这个应用的table1表中的数据,若:

content://com.example.app.provider/table1/1

表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据。

URi格式只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问表中拥有相应id的数据。我们可以使用通配符的方式来分配匹配这两种格式的内容URI,规则如下:

    • : 表示匹配任意长度的任意字符—–content://com.example.app.provider/*
  • # : 表示匹配任意长度的数字—–content://com.example.app.provider/table1/#

接着,可以借助UriMatcher这个类就可以轻松的实现匹配内容URI的功能。UriMatcher提供了一个addURI()方法,接收3个参数,可以分别将authority、path和一个自定义代码传入。这样当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据。

例如:

UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);

其中自定义代码就是用来辨识如何获取数据,获取那些数据。

除此之外,对于getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要有三部分组成,Android对着三个部分做了如下格式规定。

  • 必须以vnd开头。
  • 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/。
  • 最后街上vnd.< authority>.< path>。

所以,例如对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以为:
代码例:

return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";

对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以为:
代码例:

return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";

所有的CRUD操作都一定要匹配到相应的内容URI格式才能进行,而我们当然不可能像UriMatcher中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,因此安全问题也不存在。

对于重写的访问单条数据时有一个细节,利用书中代码进行总结。
书中代码如下:

@Override    public Cursor query(Uri uri, String[] projection, String selection,                        String[] selectionArgs, String sortOrder) {        //查询数据        SQLiteDatabase db = dbHelper.getReadableDatabase();        Cursor cursor = null;        switch (uriMatcher.match(uri)) {            case BOOK_DIR:                cursor = db.query("Book", projection, selection, selectionArgs, null,                        null, sortOrder);                break;            case BOOK_ITEM:                String bookId = uri.getPathSegments().get(1);                cursor = db.query("Book", projection, "id = ?", new String[] {bookId},                        null, null, sortOrder);                break;            ...            default:                break;        }        return cursor;    }

这里调用了Uri对象的getPathSegments()方法,它会将内容URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第一个位置存放的就是id。得到id之后,通过selection和selectionArgs参数进行约束。

创建好该程序自己的内容提供器之后,就可以在其他程序中获取该程序中的内容。

书中对应query()方法的代码如下:

queryData.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Uri uri = Uri.parse("content://com.example.hasee.databasetest.provider/book");                Cursor cursor = getContentResolver().query(uri, null, null, null, null);                if(cursor != null) {                    while (cursor.moveToNext()) {//获取数据                        String name = cursor.getString(cursor.getColumnIndex("name"));                        String author = cursor.getString(cursor.getColumnIndex("author"));                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));                        double price = cursor.getDouble(cursor.getColumnIndex("price"));                        //数据逻辑处理                        Log.d("MainActivity", "book name is " + name);                       ...                    }                    cursor.close();                }            }        });

在这里使用的Uri.parse()方法解析内容URI成Uri对象,便是前面提到过的内容。解析之后使用ContentResolver的query()方法查询数据。

0 0
原创粉丝点击