美文网首页
ContentProvider基本使用

ContentProvider基本使用

作者: 者文_ | 来源:发表于2019-08-09 20:37 被阅读0次

1. ContentProvider使用基础

1.1 概述

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

使用场景

  • 想在自己的应用中访问别的应用,或者说一些ContentProvider暴露给我们的一些数据, 比如手机联系人,短信等!我们想对这些数据进行读取或者修改
  • 我们自己的应用,想把自己的一些数据暴露出来,给其他的应用进行读取或操作,我们也可以用 到ContentProvider,另外我们可以选择要暴露的数据,就避免了我们隐私数据的的泄露!

不同于文件存储和SharedPreferences存储中两种全局刻度写操作模式,内容提供器只对那一部分数据进行共享。

image
  • URI

    专业名词叫做通用资源标识符,类比为网页域名,定位资源所在的路径,例如:content://com.jay.example.providers.myprovider/word/2

  • 例子分析:

    content:协议头,这是规定,类比http,ftp等,规定以其开头

    接着是provider全限定类名

    wrod代表资源部分,其中2表示word中资源id为2的记录

    另外,URI提供了一个parse()方法将字符串转换为URI,即:Uri uri = Uri.parse("Content://~");

1.2 运行时权限

在使用ContentProvider时免不了要和数据权限打交道,这里先了解一下数据的运行时权限。

Android 6.0中加入了运行时权限功能,用户不需要在安装软件时一次性授权所有申请的权限。Android将所有权限归成两类,一类是普通权限,一类是危险权限,确切还有第三类特殊权限

Android中所有危险权限一共是9组24个权限:

image

其他的属于非危险权限,只需在AndroidManifest.xml文件中添加权限声明即可

进行运行时权限处理时使用的是权限名,一旦用户同意授权,该权限对应的权限组中所有的权限也会同时被授权。

示例:

  • Android6.0之前,若要拨打电话,修改activity_main.xml文件

    <Button
        android:id="@+id/make_call"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Make Call" />
    
  • 在MainActivity.java中构建一个隐式Intent,其action指向Intent。ACTION_CALL,系统内置打电话动作。

  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) {
                   try {
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                      startActivity(intent);
                      } catch (SecurityException e) {
                      e.printStackTrace();
                }
              });
     }
  }
  • 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, @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 denied the permission", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
            }
    }
}

代码解释

  • 判断用户是否已经授权,借助ContextCompat.checkSelfPermission()方法,第一个参数是Context,第二个参数是具体权限名。用该方法的返回值与PackageManager.PERMISSION_GRANTED做比较,相等说明用户已授权,不等未授权
  • 已授权则直接执行逻辑操作,未授权则需要调用ActivityCompat.requestPermission()方法向用户申请授权,接收三个参数,第一参数Acitivity实例,第二参数是String数组,存放权限名,第三是请求码。
  • 调用该申请方法,弹出权限对话框,最终回调到onRequestPermissionResult()方法中,授权结果封装在grantResults参数中。这里判断一下授权结果来进行逻辑操作
  • 如果检查多个权限的话,可以将要检查的权限放入数组或者集合当中,遍历检查即可

2. ContentProvider使用

内容提供器一般有两种用法:

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

2.1 读取与操作

若需访问内容提供器中共享数据,一定要借助ContentResolver类,通过ContextgetContentResolver()方法获取到该类的实例。

ContentResolver类提供了一系列的方法用于对数据进行CRUD操作,这些方法的参数为Uri,称为内容URI

内容URI给内容提供器数据建立了唯一标识符,主要有两部分组成:authoritypath

  • authority用于对不同的应用程序做区分的,避免冲突,都会采用程序包名方式进行命名,例如:com.example.app.provider。
  • path用于对同一个程序中不同的表做区分,通常添加到authority之后,例如数据库中存放了两张表,table1和table2

二者组合,内容URI就变成了:com.example.app.provider/table1com.example.app.provider/table2

最后,在字符串头部加上协议声明:

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

得到内容URI字符串,还需将其解析为Uri对象才可以作为参数传入,解析方法为:

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

示例:

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

查询完成后返回仍然是一个Cursor对象,将数据从这个对象中取出来。通过移动游标的位置来遍历Cursor所有行,然后在取出每一行中相列的数据


if (cursor != null) {
    while (cursor.moveToNext()) {
        String column1 = cursor.getString(cursor.getColumnIndex("column1"));
        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"});

实例(读取联系人)

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.notifyDataSetChanged();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    @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) {
                    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"/>

备注

  • ContactsContract.CommonDataKinds.Phone类做了封装,提供了一个CONTENT_RUI常量,这个常量就是使用Uri.parse()方法解析出来的结果。
  • 联系人对应的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
  • 手机号对应常量是:ContactsContract.CommonDataKinds.Phone.NUMBER

2.2 创建ContentProvider

image

创建ContentProvider的流程如上图所示,通过新建一个类去继承ContentProvider方式创建,该类有六个抽象方法需要重写

public class MyProvider extends ContentProvider {

     @Override
    public boolean onCreate() {
    return false;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues) {
        return null;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }
    
    @Override
    public String getType(Uri uri) {
        return null;
    }
}
  • onCreate():初始内容提供器时调用,完成数据库创建与升级等操作。只有当存在ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化
  • query():从内容提供器中查询数据
  • insert():向内容提供器添加数据。添加完成返回一个用于表示这条新纪录的URI
  • update()更新内容提供器已有的数据。返回受影响的行数
  • delete():从内容提供器删除数据。被删除的行数作为返回值返回
  • getType()根据传入的内容URI来返回相应的MIME类型

可以在内容URI后面加一个id:

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

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

内容URI格式主要有:以路径结尾表示期望访问该表中所有的数据,以id结尾表示期望访问该表中拥有相应的id数据。可以使用通配符方式匹配这两种格式的内容URI:

  • *:表示匹配任意长度的任意字符

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

    表示一个能够匹配任意表的内容URI格式

  • :表示匹配任意长度的数字

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

    表示能够匹配table1表中任意一行数据的内容URI格式

UriMatcher类可以实现匹配内容URI功能。UriMatcher类中提供了一个addURI()方法,接收三个参数:authority、path和一个自定义代码。调用UriMatchermatch()方法,可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义码。利用这个码可以判断出调用方期望访问是哪种表中的数据。

简单示例:

<provider
            android:name=".MyProvider" 
            android:authorities="com.example.databasetest_provider"
            android:enabled="true"
            android:exported="true" />

第一个参数:类名
第二个参数:通常使用包名来使用,可以区分不同程序之间的内容提供器
第三个参数:启用
第四个参数:表示允许被其他的应用程序进行访问

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 MyDatabaseHelper dbHelper;

    private static UriMatcher uriMatcher;
    //UriMatcher 可以匹配uri  通过调用他的match()方法 匹配到就会返回我们在下面添加uri时填入的第三个参数
    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 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        // 查询数据,通过解析uri来判断想要查询哪个程序的哪个表.通过UriMatchder进行匹配 如果有就返回前面addUri()中填入的code
        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);
                /**
                 * .getPathSegments()它会将内容URI权限之后的部分以 / 进行分割,并把分割后的结果放入到一个字符串列表中,
                 * 返回的列表[0]存放的就是路径,[1]存放的就是id
                 */
                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;
    }
    .......
}

关于getType()方法。用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要有3部分组成:

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

示例:content://com.example.app.provider/table1对应的MIME类型可以写成:

vnd.android.cursor.dir/vnd.content://com.example.app.provider/table1

跨程序共享实例

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);
                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;
    }

}

完成了上述操作,这个项目就拥有了跨程序共享数据的能力,下面在另一个项目上去操作这个项目上共享的数据。

<Button
    android:id="@+id/create_database"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Create database"
    />

<Button
    android:id="@+id/add_data"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Add data"
    />

<Button
    android:id="@+id/update_data"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Update data"
    />

<Button
    android:id="@+id/delete_data"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Delete data"
    />

<Button
    android:id="@+id/query_data"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Query data"
    />
public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dbHelper.getWritableDatabase();
            }
        });
        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                // 开始组装第一条数据
                values.put("name", "The Da Vinci Code");
                values.put("author", "Dan Brown");
                values.put("pages", 454);
                values.put("price", 16.96);
                db.insert("Book", null, values); // 插入第一条数据
                values.clear();
                // 开始组装第二条数据
                values.put("name", "The Lost Symbol");
                values.put("author", "Dan Brown");
                values.put("pages", 510);
                values.put("price", 19.95);
                db.insert("Book", null, values); // 插入第二条数据
            }
        });
        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                values.put("price", 10.99);
                db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" });
            }
        });
        Button deleteButton = (Button) findViewById(R.id.delete_data);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                db.delete("Book", "pages > ?", new String[] { "500" });
            }
        });
        Button queryButton = (Button) findViewById(R.id.query_data);
        queryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                // 查询Book表中所有的数据
                Cursor cursor = db.query("Book", null, null, null, null, null, null);
                if (cursor.moveToFirst()) {
                    do {
                        // 遍历Cursor对象,取出数据并打印
                        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);
                    } while (cursor.moveToNext());
                }
                cursor.close();
            }
        });
    }

}

相关文章

网友评论

      本文标题:ContentProvider基本使用

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