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>






阅读更多
个人分类: android android
上一篇Java throws和throw的使用和区别
下一篇Android 自定义音乐播放器实现
想对作者说点什么? 我来说一句

ContentProvider单元测试

2013年11月02日 76KB 下载

内容提供者示例DEMO

2017年11月17日 209KB 下载

android 四大组件图解 ppt形式展示

2012年08月16日 1.27MB 下载

ContentResolver

2015年04月22日 1.45MB 下载

ContentProvider

2015年04月21日 1.44MB 下载

Android四大组件学习实例

2016年03月22日 46B 下载

Android四大组件ContentProvider

2016年08月31日 10.06MB 下载

没有更多推荐了,返回首页

关闭
关闭