第二行代码学习笔记——第七章:跨程序共享数据——探究内容提供器

本章要点

实现跨程序共享数据:内容提供器技术(更加安全可靠)。

共享数据给其他应用程序是因为一些程序让其他程序进行二次开发的基础性数据。比如:联系人,短信,媒体等程序都实现了跨程序共享数据的功能(内容提供器)。

7.1 内容提供者简介

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

内容提供器是选择性数据进行共享(保证程序中的隐私数据不会泄露)。


7.2 运行时权限

Android开放团队在Android6.0系统中引用了运行时权限这个功能,为了更好的保护用户的安全和隐私。

7.2.1 Android权限机制详解

Android6.0系统加入了运行时权限,是为了用户不需要在安装软件的时候一次性授权所有申请的权限,而是在软件使用的过程中再对某项权限进行授权。

并不是所有的权限都需要在运行时授权,Android将所有的权限分为了两类:

  • 普通权限
    指不会直接威胁到用户的安全和隐私,系统会自动帮我们进行授权。
  • 危险权限
    指可能会触及用户隐私,或者对设备安全性造成影响的权限。用户必须手动点击才可以,否则程序就无法使用相应的功能。

Android所有的危险权限,一共是9组24个权限,如图:
permission

注意:属于这张表中的权限,就需要进行运行时权限的处理。表格中每个权限都属于一个权限组,用户一旦同意授权,那么该权限对应的权限组中所有的其他权限也会同时被授权。

Android系统中所有的权限列表,官网:
https://developer.android.google.cn/reference/android/Manifest.permission.html

7.2.2 在程序运行时申请权限

新建RuntimePermissionTest项目。使用方法,我们就来使用CALL_PHONE这个权限来打电话。

修改activity_main.xml文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_make_call"
        android:text="Make Call"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

修改MainActivity中的代码:

public class MainActivity extends AppCompatActivity {

    private Button btn_make_call;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_make_call= (Button) findViewById(R.id.btn_make_call);

        btn_make_call.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中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hjw.runtimepermissiontest">

    <uses-permission android:name="android.permission.CALL_PHONE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
    </application>

</manifest>

这样我们就实现了打电话的功能,并且低于6.0以下的系统都可以正常运行;6.0以上的手机上运行,点击Make Call就没有任何效果,观察logcat日志,如图:
e

错误信息提醒我们“Permission Denial”,可以看出权限被禁止导致的,因为6.0及以上的系统正在使用危险权限,都必须进行运行时权限处理。

修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

    private Button btn_make_call;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_make_call= (Button) findViewById(R.id.btn_make_call);

        btn_make_call.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) {
        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:
                break;
        }
    }
}

第一步,判断用户是否给程序授权过了,方法是:ContextCompat.checkSelfPermission()方法。接收两个参数:第一个参数Context,第二个参数是权限的名称,比如打电话就是Manifest.permission.CALL_PHONE。
使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较,相等就是用户已经授权,不等就是没有授权。

没有授权,则需要调用ActivityCompat.requestPermissions()方法来向用户进行申请授权,这个方法接收3个参数,第一个参数是Activity的实例,第二个参数是String数组(存放申请的权限),第三个参数是请求码。

调用完requestPermissions()方法,系统就会弹出一个对话框,用户可以选择同意或拒绝我们的权限申请,都会回调onRequestPermissionsResult()方法。授权的结果封装在grantResults参数当中,判断授权结果,同意就直接调用call()拨打电话,拒绝,就是放弃操作。

运行程序,点击Make Call按钮,如图:
callyn

选择拒绝,如图:
calln

选择接收,就会拨打电话,如图:
cally

用户可以随时关闭危险权限,进入 设置—>应用程序—>RuntimePermission—>权限,如图:
close

在这里对任何授予的权限进行关闭。


7.3 访问其他程序中的数据

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

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

Android系统中自带的电话簿,短信,媒体库等程序都提供了外部访问接口,第三方应用程序可以充分的利用这部分数据来实现更号的功能。

7.3.1 ContentResolver的基本用法

通过ContentResolver类访问内容提供器中的数据,通过Context中的getContentReslover()方法获取到该类的实例。ContentResolver提供了一些列方法对数据进行CRUD操作。

ContentResolver中的增删改查方法不接受表名参数,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器建立了唯一标识符,它主要由两部分组成:authority和path。authority是用于对不同的应用程序做区分的,一般为了避免冲突,采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序的authority就可以命名为com.example.app.provider。path则是对于同一应用程序中不同的表做区分的,通常都会添加在authority后面。比如某个程序的数据库里存在两张表:table1和table2,这时就可以将path分别命名为/table1和/table2。组合authority和path(URI)。
URI最标准的格式写法如下:

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

将内容URI字符串解析成Uri对象传入。代码如下:

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

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

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

参数详解:
cs

查询完返回Cursor对象,逐个读取出数据:通过游标的位置遍历所有Cursor的行,代码如下:

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

向table1表添加数据,代码如下:

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

将待添加的数据存放到ContentsValues中,然后调用ContentResolver的insert()方法,传入Uri,ContentValues。

更新新添加的数据,将column1清空,使用ContentResolver的update()方法,代码如下:

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

使用selection和selectionArgs对更新数据进行约束。

删除数据:调用ContentResolver的delete()方法,代码如下:

getContentResolver().delete(uri,"column2 = ?",new String[]{"1"});
7.3.2 读取联系人信息

向模拟器添加联系人信息,为了方便我们读取。这里我们添加了两个联系人:
tjlxr

准备好我们要读取的数据之后,接下来我们新建ContactsTest项目。读取联系人展示在ListView上,修改main_activity.xml文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/lv_contacts"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>

</LinearLayout>

修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

    private ArrayAdapter<String> adapter;
    private List<String> contactsList=new ArrayList<>();

    private ListView lv_contacts;

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

        lv_contacts= (ListView) findViewById(R.id.lv_contacts);
        adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactsList);
        lv_contacts.setAdapter(adapter);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.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) {
        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_SHORT).show();
                   }
                break;
            default:
        }
    }
}

readContacts()方法中,使用getContentResolver().query()方法查询系统读取联系人,ContactsContract.CommonDataKinds.Phone.CONTENT_URI做好了封装,提供CONTENT_URI常量,使用Uri.parse()方法解析出来。返回Cursor对象,对cursor进行遍历,读取联系人姓名和手机号,姓名对应的常量是:ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME。手机号对应的常量是:ContactsContract.CommonDataKinds.Phone.NUMBER。

声明读取联系人权限,在AndroidManifest.xml中修改代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hjw.contactstest">

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

运行程序,首先弹出了申请访问联系人权限的对话框,如图:
contactsyn

我们点击允许,如下:
contacty

访问到了我们添加的联系人信息,实现跨程序访问数据功能。


7.4 创建自己的内容提供者

提供外部访问接口的应用程序实现跨程序访问数据(保证数据的安全性,防止隐私数据泄漏)

7.4.1 创建内容提供者的步骤

新建类继承自ContentProvider,创建一个自己的内容提供器,重写这个类的6个方法。新建MyProvider继承自ContentProvider,代码如下:

public class MyProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}
  1. onCreate()
    初始化内容提供器的时候调用。通常在这里完成对数据库的创建和升级操作,返回true表示内容提供器初始化成功,返回flase则表示失败。注意:只有当存在ContentResolver尝试访问我们的程序中的数据时,内容提供器才会被初始化。
  2. query()
    从内容提供器中查询数据。uri指定那张表,projection确定查询哪写列,selection和selectionArgs约束查询哪些行,sortOrder对结果进行排序。返回Cursor对象。
  3. insert()
    向内容提供器添加数据。uri指添加到的表,新数据保存在values当中。
  4. update()
    更新内容提供器中已有的数据。uri指更新哪张表中的数据,新数据保存在values中,selection和selectionArgs约束更细哪些行。
  5. delete()
    从内容提供器中删除数据。uri表示删除哪张表中的数据,selection和selectionArgs约束删除哪些行。
  6. getType()
    根据传入的内容URI来返回相应的MIME类型。

内容URI的格式主要由两种,以路径结尾表示期望访问该表中的所以数据,以id结尾表示期望访问该表中同有相应id的数据。使用通配符来分别匹配者两中内容URI,规则如下:

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

因此,一个匹配任意表的内容URI:

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

匹配table1中的任意一行数据的内容URI:

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

使用UriMatcher实现匹配内容URI的功能。UriMatcher提供了体格addURI()方法,接收authority,path和一个自定义代码传进入。当调用UriMatcher的match()方法时,就可以将Uri对象传入,返回值是某个能匹配这个Uri对象所对应的自定义代码。就可以判断调用方期望访问额是哪张表中的数据。修改MyProvider中的代码,如下:

public class MyProvider extends ContentProvider {

    private static final int TABLE1_DIR=0;
    private static final int TABLE1_ITEM=1;
    private static final int TABLE2_DIR=3;
    private static final int TABLE2_ITEM=4;

    private static UriMatcher uriMatcher;
    static {
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.hjw.contactstest.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.hjw.contactstest.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.hjw.contactstest.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.hjw.contactstest.provider","table2/#",TABLE2_ITEM);
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
       switch (uriMatcher.match(uri)){
           case TABLE1_DIR:
               //查询表1中的所有数据
               break;
           case TABLE1_ITEM:
               //查询表1中的单个数据
               break;
           case TABLE2_DIR:
               //查询表2中的所有数据
               break;
           case TABLE2_ITEM:
               //查询表2中的单个数据
               break;
           default:
               break;
       }
        return null;
    }
  ...
}

insert(),update(),delete()实现,都会携带Uri这个参数,同样调用UriMatcher的match()方法判断期望访问的是哪张表,在对该表中的数据做相应的操作。

getType()方法是所有内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串只要有3部分组成。格式规定如下:

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

所以对于content://com.example.hjw.contactstest.provider/table1这个内容URI,对应的MIME类型:

vnd.android.cursor.dir/vnd.com.example.hjw.contactstest.provider.table1

对于content://com.example.hjw.contactstest.provider/table1/1这个内容URI,对应的MIME类型:

vnd.android.cursor.item/vnd.com.example.hjw.contactstest.provider.table1

完善MyProvider中的代码,实现getType()方法中的逻辑,如下:

public class MyProvider extends ContentProvider {
    ...
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/com.example.hjw.contactstest.provider.table1";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/com.example.hjw.contactstest.provider.table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/com.example.hjw.contactstest.provider.table2";
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/com.example.hjw.contactstest.provider.table2";
        }
        return null;
    }
...
}

一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以访问使用ContentReslover来访问我们程序中的数据。
所有的URCD操作都一定匹配到相应的内容URI格式才能进行的,不可能直接向UriMatcher中添加隐私数据的URI。(解决隐私数据不会泄漏的问题)

7.4.2 实现程序共享数据

打开上一章DatabaseTest项目,通过内容提供器来给它加入外部访问接口。去掉MyDatabaseHelper中的Toast,因为跨程序访问不能直接使用Toast。

创建内容提供器,右击包—>New—>Other—>Content Provider,如图:
cp

这里我们命名为DatabaseProvider,authoritiy为com.example.hjw.databasetest.provider,Exported属性表示是否允许外部程序访问我们的内容提供器,Enable是否启用这个内容提供器。将其都勾选,点击Finish完成创建。

修改DatabaseProvider中的代码如下:

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

    public DatabaseProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deleteRows =0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                deleteRows = db.delete("Book",selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String boolId = uri.getPathSegments().get(1);
                deleteRows=db.delete("Book","id = ?",new String[]{boolId});
                break;
            case CATEGORY_DIR:
                deleteRows=db.delete("Category",selection,selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categotyId = uri.getPathSegments().get(1);
                deleteRows=db.delete("Category","id = ?",new String[]{categotyId});
                break;
            default:
                break;
        }

        return deleteRows;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.hjw.databasetest.provider.Book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.hjw.databasetest.provider.Book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.hjw.databasetest.provider.Category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.hjw.databasetest.provider.Category";
        }
        return null;
    }

    @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 newCagetoryId = db.insert("Cagetory", null, values);
                uriReturn=Uri.parse("content://"+AUTHORITY+"/category/"+newCagetoryId);
                break;
            default:
        }
        return uriReturn;
    }

    @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.getWritableDatabase();
        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 int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updateRows =0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                updateRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String boolId = uri.getPathSegments().get(1);
                updateRows=db.update("Book",values,"id = ?",new String[]{boolId});
                break;
            case CATEGORY_DIR:
                updateRows=db.update("Category",values,selection,selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categotyId = uri.getPathSegments().get(1);
                updateRows=db.update("Category",values,"id = ?",new String[]{categotyId});
                break;
            default:
                break;
        }

        return updateRows;
    }
}

内容提供器,一定要在AndroidManifest.xml中注册,我们在创建内容提供器的时候就已经自动注册完了,我们打开AndroidManifest.xml中看一看:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hjw.databasetest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        <provider
            android:name=".DatabaseProvider"
            android:authorities="com.example.hjw.databasetest.provider"
            android:enabled="true"
            android:exported="true"></provider>
    </application>

</manifest>

现在这个DatabaseTest项目就已经拥有了跨程序共享数据的功能了。
先在模拟器卸载DatabaseTest,再重新安装。再关掉这个项目,并新建ProviderTest,通过Provider访问DatabaseTest中的数据。

修改activity_main.xml文件,代码如下:

public class MainActivity extends AppCompatActivity {

    private Button btn_add_data,btn_query_data,btn_update_data,btn_delete_data;

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_add_data= (Button) findViewById(R.id.btn_add_data);
        btn_query_data= (Button) findViewById(R.id.btn_query_data);
        btn_update_data= (Button) findViewById(R.id.btn_update_data);
        btn_delete_data= (Button) findViewById(R.id.btn_delete_data);


        btn_add_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //添加数据
                Uri uri=Uri.parse("content://com.example.hjw.databasetest.provider/book");
                ContentValues values=new ContentValues();
                values.put("name","第一行代码");
                values.put("author","郭霖");
                values.put("price",54.5);
                values.put("pages",545);
                Uri newUri=getContentResolver().insert(uri,values);
                newId=newUri.getPathSegments().get(1);
            }
        });

        btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //查询数据
                Uri uri=Uri.parse("content://com.example.hjw.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", "author name is "+author);
                        Log.d("MainActivity", "pages name is "+pages);
                        Log.d("MainActivity", "price name is "+price);
                    }
                    cursor.close();
                }
            }
        });

        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //修改数据
                Uri uri=Uri.parse("content://com.example.hjw.databasetest.provider/book"+newId);
                ContentValues values=new ContentValues();
                values.put("price",48.9);
                getContentResolver().update(uri,values,null,null);
            }
        });

        btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //删除数据
                Uri uri=Uri.parse("content://com.example.hjw.databasetest.provider/book"+newId);
                getContentResolver().delete(uri,null,null);
            }
        });
    }
}

运行程序,点击Add to book按钮,再 Query from book按钮:
addq

点击Update Book按钮,再 Query from book按钮:
pupdate
点击Delete from book按钮,再 Query from book按钮,就查询不到数据了。


7.5 Git时间——版本控制工具进阶

7.5.1 忽略文件
7.5.2 查看修改内容
7.5.3 撤销未提交的修改
7.5.4 查看提交记录

7.6 小结与点评

了解了Android权限机制,学会使用Android6.0系统以上运行时权限;学习了内容提供器的相关内容,实现跨程序数据共享的功能。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值