Android四大组件之ContentProvider介绍和简单实例

Content Providers

Content provider管理android以结构化方式存放的数据。他以相对安全的方式封装数据并且提供简易的处理机制。Content provider提供不同进程间数据交互的标准化接口。

当你准备取出content provider中的数据时,你需要获得一个和当前上下文相关的ContentResolver对象作为客户端。这个对象和一个继承至ContentProvider的provider对象相关联。provider从客户端接收请求并且返回结果。

如果你不准备和其他应用共享你的数据,你不需要自定义provider。但是,你需要实现provider以满足个性化搜索数据的需求。同时,当你拷贝和粘贴复杂数据或者文件到他人应用中时,也需要provider。

      上面摘自安卓开发指南。也指明了ContentProviders的作用就是在不同的应用程序之间传递数据,但是如果学过了Android文件与IO存储。就会发现SharedPreference,文件,数据库可以用于在应用程序之间传递数据,那么为什么不用这些来传递数据呢,因为这些方法存在安全漏洞: 

      Shared Preferences存储安全风险源于:

1 开发者在创建文件时没有正确的选取合适的创建模式(MODE_PRIVATE、MODE_WORLD_READABLE以及MODE_WORLD_WRITEABLE)进行权限控制;

2  开发者过度依赖Android系统内部存储安全机制,将用户信息、密码等敏感重要的信息明文存储在Shared Preferences文件中,导致攻击者可通过root手机来查看敏感信息。

正是因为存在这些问题,所以从Androdi4.2开始就不再支持SharedPreference方式在不同组件之间传递数据,在Android Studio中调用getSharedPrefence可以看见用于在程序之间传递数据的打开模式都已不再推荐

扯这么多废话,回到正题吧。如何创建一个自己的ContentProvider呢?

1 定义一个自己的CotentProvide类,该类需要继承ContentProvider类,并实现几个抽象方法

2 和定义一个Activity一样,在AndroidManfest.xml文件中注册该ContentProvider

上面第一步自定义ContentProvider类的时候需要实现几个抽象方法,这里说明一下:ContentProvider实际上是对数据库进行操作。学过数据库都知道数据库最基本的操作就是增,删,改,查。ContentProvider也正是定义了这四个抽象方法,所以在继承该类的时候需要实现这四个抽象方法。下面分别列出这四个抽象方法

   public boolean onCreate()    自定义ContentProvider创建时调用

   重点说下query方法的参数:第一个Uri参数必须有其它随意,projection为数据库列名, selection为约束条件,selectionArgs为过滤参数,sortOrder为排序参数

   public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)    

   public String getType(@NonNull Uri uri)
   public Uri insert(@NonNull Uri uri, @Nullable ContentValues values)
   public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)

   public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)

可以发现除了onCreate方法,其余每个方法都有一个Uri参数,这里介绍下Uri

想必大家都知道URL,这里做下对比学习,

http://www.huya.com/g/lol

这里用了一个个人比较喜欢浏览的网址,有志同道合的撸友可以一起交流哈。好了,又扯偏了。回到正题:

这里URL可以分为三个部分

http://  :URL的协议部分,凡是通过HTTP协议来访问网址都需要该部分

www.huya.com : URL的域名部分,用于指定具体的网站

/g/lol :URL的网站资源部分,根据访问者的需求动态改变

URI(Universal Resource Identify)通用资源标识符

URI组成和URL类似这里举例一个:

content://com.example.mycontentprovider/test

其中:

content://  是Android的ContentProvider规定的,类似URL的协议部分固定不变。

com.example.mycontentprovider :  这就是ContentProvider的authorities,类似URL的域名部分,用于指定ContentProvider

/test: 资源部分,根据访问者的需求动态变化

如果想详细了解URI可以参考URI详解

有了URI基础后,我们需要知道ContentProvider就是根据匹配不同的URI提供不同的业务操作


Android提供了UriMatcher工具类用于匹配Uri

void addURI(String authority , String path , int code);该方法用于向UriMatcher对象注册Uri。其中authority和path组合成一个Uri,而code则代表该Uri的标志码。

int match(Uri uri)根据前面注册的Uri来判断指定Uri对应的标志码。如果没有匹配,则返回-1

举例:

private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final String AUTHORITY = "com.example.myprovider" ;
private static final int WORDS = 1 ;
private static final int WORD = 2 ;
static {

    matcher.addURI(AUTHORITY , "words" ,WORDS);
    matcher.addURI(AUTHORITY , "word/#" , WORD) ;
}
//上面创建的UriMatcher对象注册了两个Uri,其中com.example.myprovider/words对应的标识码为1,
//com.example.myprovider/word/#对应的标识码为2 ,其中#为通配符,表示任意数字。
//这意味着:
//返回标识码1
matcher.match(Uri.parse("com.example.myprovider/words")) ;
//返回标识码2 
matcher.match(Uri.parse("com.example.myprovider/word/3")) ;

此外Android还提供了ContentUris工具类,用于Uri字符串.具体提供了如下两个方法:

1,withAppendedId(uri , id) 用于为指定uri添加id

Uri uri = Uri.parse("content://com.example.myprovider/word") ;
Uri resultUri = ContentUris.withAppendedId(uri , 2);
//生成后Uri为:content://com.example.myprovider/word/2

2,parseId(uri):用于从指定uri中解析出id

Uri uri = Uri.parse("content://com.example.myprovider/word/2") ;
long wordId = ContentUris.parseId(uri); //wordId = 2

好了,现在我们假定已经自定义了一个继承自ContentProvider的类DicttProvider。那么我们要来完成创建ContentProvider的第二步:在AndroidManfest.xml中注册该ContentProvider。

我们需要在<application> </application>字段之间添加:

<provider
    android:authorities="com.example.myprovider"
    android:exported="true"
    android:name=".DictProvider" />

从上面的配置信息我们可以看出,配置ContentProvider需要配置如下属性

* name : 指定该ContentProvider实现类的类名

* authorities :指定该ContentProvider的Uri。类似URL的域名部分。注意:上面已经提到URI由协议,域名,资源三部分组成。

* exported :指定该ContentProvider是否允许其它应用调用。

使用ContentResolver操作数据

根据前面讲解,ContentProvider可以说是提供了数据库操作的接口可以让其它应用程序调用。那么其它程序就是通过ContentResolver来操作数据。Context提供了如下方法来获取ContentResolver对象:

getContentResolver();获取该应用默认的ContentResolver对象

举例: ContentResolver cr = getContentResolver();

ContentResolver 类也提供了与ContentProvider类相对应的四个方法:
public Uri insert(Uri uri, ContentValues values)    该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs)   该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)   该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)   该方法用于从ContentProvider中获取数据。

我们可以发现:ContentResolver和ContentProvider提供的insert, delete , query , update方法一样,而且都有一个参数Uri,那么我们是否可以猜想ContentResolver和ContentProvider是通过Uri进行的数据交互。其实事实情况也是这样:

假设A应用通过ContentResolver执行增删改查操作,这些操作都需要指定Uri参数,Android系统就根据该Uri找到对应的ContentProvider(该ContentProvider属于B应用),ContentProvider负责实现增删改查方法,完成对底层数据的各种操作。

ContentResolver Uri ContentProvider三者的关系为:



好了:基础知识就补充到这里了。下面上demo:

自定义ContentProvider


一:ContentProvider子类程序实现:

   OpenHelperDb类:数据库表单创建类

package com.example.xukefeng.contentprovidertest;

import android.content.ContentProvider;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * Created by initializing on 2018/4/16.
 */

public class OpenHelperDb extends SQLiteOpenHelper {
    private final String BASE_NAME = "create table dict(_id integer primary key autoincrement" +
            ", word varchar(255) , detail varchar(255))" ;

    public OpenHelperDb(Context context , String baseName , int version)
    {
        super(context , baseName , null , version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建数据库表
        db.execSQL(BASE_NAME);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

MainActivity类:初始化数据库表单数据,并在Activity退出时关闭数据库资源。注意在MainActivity中用了setContentView(R.layout.activity_main);该字段用的默认的layout布局界面,没有做任何修改

package com.example.xukefeng.contentprovidertest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private OpenHelperDb helperDb;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  //默认layout界面

        helperDb = new OpenHelperDb(this , "test.db3" , 1) ;
        //插入数据的操作
        helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" ,
                new String[]{"Android" , "安卓"});

        helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" ,
                new String[]{"CSDN" , "中国程序员大本营(China Software Developper Network)"});

        helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" ,
                new String[]{"github" , "社交编程及代码托管网站"});

        Toast.makeText(this,"插入数据成功",Toast.LENGTH_SHORT).show();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (helperDb != null)
        {
            helperDb.close();
        }
    }
}

DictProvider类:继承自ContentPrivider类,实现增删改查方法:

package com.example.xukefeng.contentprovidertest;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Created by initializing on 2018/4/16.
 */

public class DictProvider extends ContentProvider {

    private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int WORDS = 1 ;
    private static final String AUTHORITY = "com.example.myprovider" ;
    private static final int WORD = 2 ;
    private static final String _ID = "_id" ;

    private OpenHelperDb helperDb ;
    static {
        matcher.addURI(AUTHORITY , "words" ,WORDS);
        matcher.addURI(AUTHORITY , "word/#" , WORD) ;
    }


    @Override
    public boolean onCreate() {

        helperDb = new OpenHelperDb(getContext() , "test.db3" , 1) ;
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteDatabase db = helperDb.getReadableDatabase() ;
        switch ( matcher.match(uri))
        {
            case WORDS :
                return db.query("dict",null,null,null,null,null,null) ;
            case WORD :
                return db.query("dict",null,"_id = ?" ,selectionArgs ,null , null , null) ;
            default:
                throw new IllegalArgumentException("Unrecognized Uri !");
        }
    }

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

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        SQLiteDatabase db = helperDb.getReadableDatabase() ;
        switch (matcher.match(uri)){
            case WORDS :
                long rowId = db.insert("dict" ,null, values) ;
                if (rowId > 0)
                {
                    Uri wordUri = ContentUris.withAppendedId(uri , rowId) ;
                    //通知数据改变
                    getContext().getContentResolver().notifyChange(wordUri , null);
                    return wordUri ;
                }
                break ;
            default:
                throw new IllegalArgumentException("Unrecognized Uri !");
        }
        return null;
    }

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

        SQLiteDatabase db = helperDb.getReadableDatabase() ;
        //记录所删除的记录数目
        int num = 0 ;
        switch (matcher.match(uri))
        {
            case WORDS :
                num = db.delete("dict" , null , null) ;
                break ;
            case WORD :
                //解析处所要删除的记录Id
                long id = ContentUris.parseId(uri) ;
                String whereClause = _ID + "=" + id ;
                num = db.delete("dict" , whereClause ,selectionArgs) ;
                break ;

            default:
                throw new IllegalArgumentException("Unrecognized Uri !");
        }
        //通知数据改变
        getContext().getContentResolver().notifyChange(uri , null);
        return num;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        SQLiteDatabase db = helperDb.getReadableDatabase() ;
        //记录需要修改的记录数目
        int num = 0 ;
        switch (matcher.match(uri))
        {
            case WORDS :
                //db.update()
                num = db.update("dict" , values ,selection ,selectionArgs) ;
                break;
            default:
                throw new IllegalArgumentException("Unrecognized Uri !");
        }
        //通知数据改变
        getContext().getContentResolver().notifyChange(uri , null);
        return num;
    }
}

AndroidManfest.xml文件主要配置activity和contentprovider信息

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:authorities="com.example.myprovider"
            android:exported="true"
            android:name=".DictProvider" />
    </application>

</manifest>

二:其它程序调用ContentProvider子类程序暴露的数据库操作接口

MainActivity:调用getContentResolver并且实现相关业务

package com.example.xukefeng.contentresolvertest;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends AppCompatActivity {

    private ListView mainActListView ;
    private ContentResolver resolver ;
    private static final String WORD = "word" ;
    private static final String DETAIL = "detail" ;
    private static final String AUTHORITY = "com.example.myprovider" ;
    private static final Uri ALL_URI = Uri.parse("content://" + AUTHORITY + "/words") ;
    private static final Uri SINGLE_URI = Uri.parse("content://" + AUTHORITY + "/word") ;
    private static final String _ID = "_id" ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainActListView = (ListView) findViewById(R.id.mainActListView) ;
        //获取系统的ContentResolver对象
        resolver = getContentResolver() ;
        init() ;
    }

    private void init()
    {
        Cursor cursor = resolver.query(ALL_URI , null , null ,null ,null) ;
        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this , R.layout.dict_adapt_listview,
                cursor , new String[]{WORD , DETAIL} ,new int[] {R.id.DictWordTetView , R.id.DictDetailTetView}) ;
        mainActListView.setAdapter(adapter);
        System.out.println("init") ;
    }

    public void insertBtnClickLis(View view)
    {
        ContentValues values = new ContentValues() ;
        values.put(WORD , "CSDN");
        values.put(DETAIL , "中国程序员大本营(China Software Developper Network)" );
        resolver.insert(ALL_URI , values ) ;
        init();
    }

    public void deleteBtnClickLis(View view)
    {
        resolver.delete(ALL_URI , null , null) ;
        init();
    }

    public void updateBtnClickLis(View view)
    {
        ContentValues values = new ContentValues() ;
        values.put(WORD , "hello world!");
        values.put(DETAIL , "世界 你好!" );
         resolver.update(ALL_URI , values , new String("word = ?") , new String[]{"CSDN"} ) ;
         init();

    }


}

activity_main.xml布局文件:主界面布局,按钮和按钮点击事件监听

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.xukefeng.contentresolvertest.MainActivity">

  <LinearLayout
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <LinearLayout
          android:orientation="horizontal"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="insert"
              android:onClick="insertBtnClickLis"

              />
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="delete"
              android:onClick="deleteBtnClickLis"

              />
          <Button
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="update"
              android:onClick="updateBtnClickLis"
              />
      </LinearLayout>
      <ListView
          android:id="@+id/mainActListView"
          android:layout_width="match_parent"
          android:layout_height="wrap_content">

      </ListView>

  </LinearLayout>

</android.support.constraint.ConstraintLayout>

dict_adapt_listview.xml: SimpleCursorAdapt指定的布局文件。用于显示单词列表

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/DictWordTetView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="25dp"
        android:layout_margin="5dp"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="词意:"
        android:textSize="20dp"
        android:layout_margin="5dp"
        />
    <TextView
        android:id="@+id/DictDetailTetView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20dp"
        />

</LinearLayout>

AndroidManfest.xml:配置文件

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值