Android开发(6):ContentProvider内容提供者

ContentProvider内容提供者

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

运行时权限

运行时权限机制

Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。准确地讲,其实还有第三类特殊权限,不过这种权限使用得很少,因此不在本书的讨论范围之内。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了,比如在BroadcastTest项目中申请的两个权限就是普通权限。危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。

查看Android系统中完整的权限列表:Manifest.permission | Android Developers (google.cn)

程序运行时申请权限

Android6.0之前

Android6.0之前,不需要动态申请权限就可以使用危险权限

Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

但需要在AndroidManifest.xml中申请静态权限

<uses-permission android:name="android.permission.CALL_PHONE" />
Android6.0以后,动态申请权限
public class MainActivity extends AppCompatActivity {
    @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 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();
                }
            }
        });
    }

    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) {
        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:
                break;
        }
    }
}
  1. 先判断用户是不是已经给过授权

    借助的是ContextCompat.checkSelfPermission()方法。

    • checkSelfPermission() 方法:第一个参数是Context;第二个参数是具体的权限名
    • 方法的返回值和PackageManager.PERMISSION_GRANTED 做比较,相等就说明用户已经授权,不等就表示用户没有授权
  2. 如果已经授权,则调用相关执行逻辑

  3. 如果没有授权,则需要调用ActivityCompat.requestPermissions() 方法来向用户申请授权

    • requestPermissions() 方法
      • 第一个参数要求是Activity的实例
      • 第二个参数是一个String 数组,存放要申
        请的权限名
      • 第三个参数是请求码(唯一值)这里传入了1
  4. 最终都会回调到onRequestPermissionsResult() 方法中,而授权的结果则会封装在rantResults 参数当中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就调用call() 方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并且弹出一条失败提示。

访问其他程序中数据

  • 内容提供器的用法:
    • 使用现有的内容提供器来读取和操作相应程序中的数据
    • 创建自己的内容提供器给我们程序的数据提供外部访问接口
ContentResolver的基本用法

对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver() 方法获取到该类的实例。

ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,其中insert() 方法用于添加数据,update() 方法用于更新数据,delete() 方法用于删除数据,query() 方法用于查询数据。

  1. 不同于SQLiteDatabaseContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri 参数代替,这个参数被称为内容URI

    • 内容URI给内容提供器中的数据建立了唯一标识符

      • 主要由两部分组成:authority和path

        • authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。一般为[PackageName].provider

        • path则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:table1和table2,这时就可以将path分别命名为/table1和/table2,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。

        • 内容URI标准格式写法

          content://com.example.app.provider/table1
          content://com.example.app.provider/table2
          
  2. 得到内容URI字符串之后,我们还需要将它解析成Uri 对象才可以作为参数传入

    需要调用Uri.parse()方法

    Uri uri = Uri.parse("content://com.example.app.provider/table1");
    
  3. 接下来可以使用ContentResolver来查询其他程序 数据库中表的数据了

    Cursor cursor = getContentResolver().query(
        uri,
        projection,
        selection,
        selectionArgs,
        sortOrder);
    
    ContentResolver.query()方法参数对应SQL部分描述
    urifrom table_name指定查询某个应用程序下的某一张表
    projectionselect column1, column2指定查询的列名
    selectionwhere column = value指定where 的约束条件
    selectionArgs为where 中的占位符提供具体的值
    sortOrderorder by column1, column2指定查询结果的排序方式
  • insert(uri, contentValues);
  • update(uri, contentValues, “column1 = ? and column2 = ?”, new String[] {“text”, “1”});
  • delete(uri, “column2 = ?”, new String[] { “1” });
读取系统联系人
public class MainActivity extends AppCompatActivity {
    ArrayAdapter<String> adapter;
    List<String> contactsList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView contactsView = (ListView) findViewById(R.id.contacts_view);
        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 }, 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 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
                adapter.notifyDataSetChanged();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if (cursor != null) {
                cursor.close();
            }
        }
    }

    // 接收权限申请返回结果
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] 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_
                        SHORT).show();
                }
                break;
            default:
        }
    }
}

同时还需在AndroidManifest.xml中添加权限

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

创建自己的内容提供器

创建内容提供器的步骤
  1. 新建一个类去继承ContentProvider 的方式来创建一个自己的内容提供器。该类有6个抽象方法

    1. onCreate():初始化内容提供器的时候调用。。通常会在这里完成对数据库的创建和升级等操作,返回true 表示内容提供器初始化成功,返回false则表示失败。

    2. query():从内容提供器中查询数据。使用uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。

    3. insert():向内容提供器中添加一条数据。使用uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。

    4. update():更新内容提供器中已有的数据。使用uri 参数来确定更新哪一张表中的数据,新数据保存在values 参数中,selection 和selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

    5. delete():从内容提供器中删除数据。使用uri 参数来确定删除哪一张表中的数可以看到,几乎每一个方法都会带有Uri 这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。

    6. getType():根据传入的内容URI来返回相应的MIME类型。

      一个内容URI所对应的MIME字符串主要由3部分组成

      • 必须以vnd 开头。
      • 如果内容URI以路径结尾,则后接android.cursor.dir/ ;如果内容URI以id结尾,则后接android.cursor.item/
      • 最后接上vnd.<authority>.<path>
      public class MyProvider extends ContentProvider {
          ...
      
          @Override
          public String getType(Uri uri) {
              switch (uriMatcher.match(uri)) {
              case TABLE1_DIR:
                  return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
              case TABLE1_ITEM:
                  return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
              case TABLE2_DIR:
                  return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
              case TABLE2_ITEM:
                  return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
              default:
                  break;
              }
              return null;
          }
      }
      
  2. URI参数

    • 标准URI写法

      content://[Package Name]/[Table Name]/[ID]
      
    • 通配符

      • *:表示匹配任意长度的任意字符
        • 如匹配任意表:content://[Package Name]/*
      • #:表示匹配任意长度的数字
        • 如匹配table表任意一行内容:content://[Package Name]/table/#
  3. UriMatcher类:

    可以轻松地实现匹配内容URI的功能

    • addURI() 方法: 接收3个参数,分别为authoritypath 和一个自定义代码传进去
    public class MyProvider extends ContentProvider {
        public static final int TABLE1_DIR = 0;
        public static final int TABLE1_ITEM = 1;
        public static final int TABLE2_DIR = 2;
        public static final int TABLE2_ITEM = 3;
        private static UriMatcher uriMatcher;
        static {
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
            uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
            uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM);
            uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_DIR);
            uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
        }
        ...
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[]
            selectionArgs, String sortOrder) {
            switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                // 查询table1表中的所有数据
                break;
            case TABLE1_ITEM:
                // 查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                // 查询table2表中的所有数据
                break;
            case TABLE2_ITEM:
                // 查询table2表中的单条数据
                break;
            default:
                break;
            }
            ...
        }
        ...
    }
    
实现跨程序数据共享
public class DatabaseProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static UriMatcher uriMatcher;
    private MyDatabaseHelper dbHelper;
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }
    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;
    }
    @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;
            case CATEGORY_DIR:
                cursor = db.query("Category", projection, selection, selectionArgs,
                    null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category", projection, "id = ?", new String[]
                    { categoryId }, null, null, sortOrder);
                break;
            default:
                break;

        }
        return cursor;
    }
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 添加数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +
                   newBookId);
                /**
                 * Uri uri = Uri.parse("content://org.providers.prods/word");
                 * Uri resultUri = ContentUris.withAppendedId(uri,5);
                 * // 返回resultUri为:content://org.providers.prods/word/5
                 * Long id = ContentUris.parseId(resultUri); 
                 * // 返回5
                 */
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" +
                    newCategoryId);
                break;
            default:

                break;
        }
        return uriReturn;
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[]
        selectionArgs) {
        // 更新数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updatedRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[]
                    { bookId });
                break;
            case CATEGORY_DIR:
                updatedRows = db.update("Category", values, selection,
                    selectionArgs);

                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category", values, "id = ?", new String[]
                    { categoryId });
                break;
            default:
                break;
        }
        return updatedRows;
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 删除数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                deletedRows = db.delete("Book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);

                deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
                break;
            case CATEGORY_DIR:
                deletedRows = db.delete("Category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Category", "id = ?", new String[]
                    { categoryId });
                break;
            default:
                break;
        }
        return deletedRows;
    }
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case BOOK_ITEM:

                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        return null;
    }
}

其中还需要在AndroidManifest.xml中注册provider

<provider
    android:name=".DatabaseProvider"
    android:authorities="com.example.databasetest.provider"
    android:enabled="true"
    android:exported="true">
</provider>
  • android:name 属性指定了DatabaseProvider的类名
  • android:authorities 属性指定了DatabaseProviderauthority
  • enabledexported 属性则是根据我们刚才勾选的状态自动生成的,这里表示允许DatabaseProvider被其他应用程序进行访问;Enabled 属性表示是否启用

另写一程序来调用该数据库

public class MainActivity extends AppCompatActivity {
    private String newId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 添加数据
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 22.85);
                Uri newUri = getContentResolver().insert(uri, values);

                newId = newUri.getPathSegments().get(1);
            }
        });
        Button queryData = (Button) findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 查询数据
                Uri uri = Uri.parse("content://com.example.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);
                        Log.d("MainActivity", "book author is " + author);

                        Log.d("MainActivity", "book pages is " + pages);
                        Log.d("MainActivity", "book price is " + price);
                    }
                    cursor.close();
                }
            }
        });
        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 更新数据
                Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });
        Button deleteData = (Button) findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // 删除数据
                Uri uri = Uri.parse("content://com.example.databasetest. provider/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forgo7ten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值