Content Provider组件简析。。。待续

Android深入四大组件(五)Content Provider的启动过程 Content Provider做为四大组件之一,通常情况下并没有其他的组件使用频繁,但这不能作为我们不去深入学习它的理由。关于Content Provider一篇文章是写不完的,这一篇文章先来介绍它的启动过程。 阅读详情

一 Content Provider组件概述

在Android系统中,Content Provider作为应用程序四大组件之一,它起到在应用程序之间共享数据的作用,同时,它还是标准的数据访问接口。

在Android系统中,每一个应用程序被安装时,都会分配到一个不同的Linux用户ID。这样,Android系统就可以基于Linux用户ID来保护每一个应用程序的数据不会被其他应用程序破坏。即每一个应用程序只可以访问自己创建的数据。Android系统是基于Linux内核开发的,而在Linux系统中,每一个文件除了文件本身的数据之外,还具有一定的属性,其中最重要的就是文件权限了。所谓的文件权限,就是指对文件的读写和执行权限,此外,Linux还是一个多用户的操作系统,因此,Linux将系统中的每一个文件都与一个用户以及用户组关联起来,基于不同的用户赋予不同的用户权限。只有当一个用户对某个文件拥有相应的权限时,才能执行相应的操作,例如,只有当一个用户对某个文件拥有读权限时,这个用户才可以调用read系统调用来读取这个文件的内容。Android系统继承了Linux系统管理文件的方法,为每一个应用程序分配一个独立的用户ID和用户组ID,而由这个应用程序创建出来的数据文件就赋予相应的用户以及用户组读写的权限,其余用户则无权对该文件进行读写。

例如

adb shell

//进入到Android系统日历应用程序数据目录
cd data/data/com.android.providers.calendar/databases 

m282a:/data/data/com.android.providers.calendar/databases # ls -l
ls -l
total 296
-rw-rw---- 1 u0_a2 u0_a2 122880 2019-04-09 16:17 calendar.db

在前面的十字符-rw-rw----中,最前面的符号-表示这是一个普通文件,接下来的三个字符rw-表示这个文件的所有者对这个文件可读可写不可执行,再接下来的三个字符rw-表示这个文件的所有者所在的用户组的用户对这个文件可读可写不可执行,而最后的三个字符—表示其它的用户对这个文件不可读写也不可执行,因为这是一个数据文件,所认所有用户都不可以执行它是正确的。

在接下来的两个u0_a2字符串表示这个文件的所有者和这个所有者所在的用户组的名称均为u0_a2,这是应用程序在安装的时候系统分配的,在不同的系统上,这个字符串可能是不一样,不过它所表示的意义是一样的。这意味着只有用户ID为u0_a2或者用户组ID为u0_a2的那些进程才可以对这个calendar.db文件进行读写操作。我们通过执行终端上执行ps命令来查看一下哪个进程的用户ID为u0_a2:

u0_a2     1745  211   983520 60844 SyS_epoll_ a645a7a4 S com.android.providers.calendar

这里我们看到,正是这个日历应用程序com.android.providers.calendar进程的用户ID为u0_a2。这样,就验证了我们前面的分析了:只有这个日历应用程序com.android.providers.calendar才可以对这个calendar.db文件进行读写操作。这个日历应用程序com.android.providers.calendar其实是由一个Content Provider组件来实现的。

Android系统对应用程序的数据文件作如此严格的保护会不会有点过头了呢?如果一个应用程序想要读写另外一个应用程序的数据文件时,应该怎么办呢?举一个典型的应用场景,我们在开发自己的应用程序时,有时候会希望读取通讯录里面某个联系人的手机号码或者电子邮件,以便可以对这个联系人打电话或者发送电子邮件,这时候就需要读取通讯录里面的联系人数据文件了。这时候就需要使用Content Ptovider组件来共享通讯录中的联系人信息了。

现在在互联网里面,都流行平台的概念,各大公司都打着开放平台的口号,来吸引第三方来为自己的平台做应用,例如,国外最流行的Fackbook开放平台和Google+开放平台等,国内的有腾讯的Q+开放平台,还有新浪微博开放平台、360的开放平台等。这些开放平台的核心就是要开放用户数据给第三方来使使用,就像前面我们说的Android系统的通讯录,它需要把自己联系人数据开放出来给其它应用程序使用。但是,这些数据都是各个平台自己的核心数据和核心竞争力,它们需要有保护地进行开放。Android系统中的Content Provider应用程序组件正是结合上面分析的这种文件权限机制来秉承这种有保护地开放自己
的数据给其它应用程序使用的理念。

由于Content组件可以在不同的应用程序之间进行数据共享,因此,他在软件平台建设中是非常有用的。一个软件品台无非就是由数据和业务组成的,一般来说,数据是统一的,而业务则是百花齐放的。由于不同的业务均使用到了统一的数据,因此,从垂直的方向看,一个软件平台至少要由数据层,数据访问层和业务层构成。而从水平方向看,业务层应该由一系列相对独立地方模块组成,以便实现可以适应越来越复杂的业务环境。在Android系统中,数据层可以使用数据库,文件或者网络来实现,业务层可以使用一系列应用来实现,而数据访问层可以使用 Content Ptovider组件实现,整个软件平台架构如下图所示。
在这里插入图片描述
在这个架构中, 数据层采用数据库、文件或者网络来保存数据,数据访问层使用Content Provider来实现,而业务层就通过一些APP来实现。为了降低各个功能模块间耦合性,我们可以把业务层的各个APP和数据访问层中的Content Provider,放在不同的应用程序进程中来实现,而数据库中的数据统一由Content Provider来管理,即Content Provider拥有对这些文件直接进行读写的权限,同时,它又根据需要来有保护地把这些数据开放出来给上层的APP来使用。

现在关键的问题是,Content Provider组件是如何将它里面的数据共享给业务层中的Android应用程序访问的呢?我们注意到,Content Provider组件是运行在一个独立的应用程序中的,即它本身也是一个Android应用程序,因此,业务层中的Android应用程序是肯定不可以直接访问它的数据的。从前面的学习可以知道,不同的应用程序进程之间可以使用Binder进程间通信机制来通信。因此,Content Provider组件就可以通过Binder进程间通信机制将它里面的数据传递给业务层中的Android应用程序。然而,事情没有那么简单,因为Content Provider组件一次传递给业务层中的Android应用程序的数据量可能是非常大的。在这种情况下,如果直接使用Binder进程间通信机制来传递数据,那么数据传输效率就会成为问题。我们可以知道,不同的应用程序进程可以通过匿名共享内存来传输大数据,因此无论多大的数据,对于匿名共享内存来说,需要在进程间传递的仅仅是一个文件描述符而已。把需要在进程间传输的数据都写到共享内存中去,然后只能通过Binder进程间通信机制来传输一个共享内存的打开文件描述符给对方就好了,这样,结合Binder进程间通信机制以及匿名共享内存机制,Content Provider组件就可以高效地将它里面的数据传递给业务层中的Android应用程序访问了。

二 SQLite数据库概述

在继续介绍这个应用程序的实现之前,我们先介绍一下这个应用程序用来保存文章信息的数据库的设计。我们知道,在Android系统中,内置了一款轻型的数据库SQLite。SQLite是专门为嵌入式产品而设计的,它具有占用资源低的特点,而且是开源的,非常适合在Android平台中使用,关于SQLite的更多信息可以访问官方网站http://www.sqlite.org。

ArticlesProvider应用程序就是使用SQLite来作为数据库保存文章信息的,数据库文件命名为Articles.db,它里面只有一张表ArticlesTable,表的结构如下所示:
在这里插入图片描述

它由四个字段表示,如下

第一个字段: _id字段 --- 表示文章的ID,类型为自动递增的integer,它作为表的key值;
第二个字段:_title字段 --- 表示文章的题目,类型为text;
第三个字段:_abstract字段 --- 表示文章的摘要,类型为text;
第四个字段:_url字段 --- 表示文章的URL,类型为text;

三 ArticlesProvider应用程序的实现

应用程序ArticlesProvider目录结构如下:

-----AndroidManifest.xml 
-----Android.mk 
-----src 
	-----com/android/articles 
		-----Articles.java 
		-----ArticlesProvider.java 
-----res 
	-----values 
		-----strings.xml 
	-----drawable 
		-----icon.png

Articles.java

package com.android.articles;
 
import android.net.Uri;
 
public class Articles {
    /*
    ID ,TITLE ,ABSTRACT ,URL 四个常量前面已经解释过了,他是我们用来保存文章信息的数据表的四个列名。
    */
    public static final String ID = "_id";
    public static final String TITLE = "_title";
    public static final String ABSTRACT = "_abstract";
    public static final String URL = "_url";
 
    /*
    DEFAULT_SORT_ORDER 常量是调用ContentProvider接口的query函数来查询数据时调用的,他表示对查询结果按照 _id列的值
    从小到大排列;
    */
    public static final String DEFAULT_SORT_ORDER = "_id asc";

    /*
	METHOD_GET_ITEM_COUNT 常量和 KEY_ITEM_COUNT 常量两个常量是调用ContentProvider接口的一个未公开函数call来查询数
	据时用的,使用call函数时,传入参数 METHOD_GET_ITEM_COUNT 表示我们要调用我们自己定义的 ContentProvider子
	类中的getItemCount函数来获取数据库中的文章信息条目的数量,结果放在一个Bundle中以KEY_ITEM_COUNT为关键字的域中。
	*/
    public static final String METHOD_GET_ITEM_COUNT = "METHOD_GET_ITEM_COUNT";
    public static final String KEY_ITEM_COUNT = "KEY_ITEM_COUNT";

    /* URI B组件 */
    public static final String AUTHORITY = "com.android.articles";

    /*
    ITEM、ITEM_ID和POS_ID三个常量分别定了三个URI匹配规则的匹配码
    如果URI的形式为content://com.android.providers.articles/item,则匹配规则返回的匹配码为ITEM
    如果URI的形式为content://com.android.providers.articles/item/#,#表示一个数字,匹配规则返回的匹配码为ITEM_ID
    如果URI的形式为#也是表示任意一个数字,则匹配规则返回的匹配码为ITEM_POS
	*/
    public static final int ITEM = 1;
    public static final int ITEM_ID = 2;
    public static final int ITEM_POS = 3;

    /*
    每一个URI所访问的资源都是一种特定的数据类型,这些数据类型使用多功能Internet邮件扩展协议(MIME)来描述,
    MIME定义形式为 "[type]/[subtype]",其中,[type]用来描述数据类型的大类,而[subtype]用来描述数据类型的子类
    
    Content Provider组件规定,他所返回的资源的数据类型统一划分为如下两个大类:
    "vnd.android.cursor.dir"  描述数据集合
    "vnd.android.cursor.item" ,描述一个个体数据

	Content Provider组件所返回的资源的数据类型的子类则是可以自定义的,它的定义形式一般约定为
	"vnd.[company name].[resource type]"。

	此处两种MIME类型,大类不同,子类一致
    */
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.android.article";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.android.article";

    /* 定义两个URI  通用资源标志符 通过它用来唯一标志某个资源位置
	 CONTENT_URI 表示的URI表示是通过ID来访问文章信息的
	 CONTENT_POS_URI 表示的URI表示是通过位置来访问文章信息的
	*/
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item");
    public static final Uri CONTENT_POS_URI = Uri.parse("content://" + AUTHORITY + "/pos");
}

URI的全称是Universal Resource Identifier,即通用资源标志符,通过它用来唯一标志某个资源在网络中的位置,它的结构和我们常见的HTTP形式URL是一样的,其实我们可以把常见的HTTP形式的URL看成是URI结构的一个实例,URI是在更高一个层次上的抽象。
在Android系统中,它也定义了自己的用来定义某个特定的Content Provider的URI结构,它通常由四个组件来组成,如下所示:
在这里插入图片描述
A组件称为Scheme它固定为content://,表示它后面的路径所表示的资源是由Content Provider来提供的。

B组件称为Authority,它唯一地标识了一个特定的Content Provider,因此,这部分内容一般使用Content Provider所在的package来命名,使得它是唯一的。

C组件称为资源路径,它表示所请求的资源的类型,这部分内容是可选的。如果我们自己所实现的Content Provider只提供一种类型的资源访问,那么这部分内部就可以忽略;如果我们自己实现的Content Provider同时提供了多种类型的资源访问,那么这部分内容就不可以忽略了。例如,我们有两种电脑资源可以提供给用户访问,一种是笔记本电脑,一种是平板电脑,我们就把分别它们定义为notebook和pad;如果我们想进一步按照系统类型来进一步细分这两种电脑资源,对笔记本电脑来说,一种是安装了windows系统的,一种是安装了linux系统的,我们就分别把它们定义为notebook/windows和notebook/linux;对平板电脑来说,一种是安装了ios系统的,一种是安装了android系统的,我们就分别把它们定义为pad/ios和pad/android。

D组件称为资源ID,它表示所请求的是一个特定的资源,它通常是一个数字,对应前面我们所介绍的数据库表中的_id字段的内容,它唯一地标志了某一种资源下的一个特定的实例。继续以前面的电脑资源为例,如果我们请求的是编号为123的装了android系统的平板电脑,我们就把它定义为pad/android/123。当忽略这部分内容时,它有可能是表示请求某一种资源下的所有实例,取决于我们的URI匹配规则,后面我们将会进一步解释如何设置URI匹配规则。

回到上面的Articles.java源文件中,我们定义了两个URI,分别用COTENT_URI和CONTENT_POS_URI两个常量来表示,它们的Authority组件均指定为com.android.articles。其中,COTENT_URI常量表示的URI表示是通过ID来访问文章信息的,而CONTENT_POS_URI常量表示的URI表示是通过位置来访问文章信息的。

例如,content://com.android.articles/item表示访问所有的文章信息条目;
content://com.android.articles/item/123表示只访问ID值为123的文章信息条目;
content://com.android.articles/pos/1表示访问数据库表中的第1条文章信息条目,这条文章信息条目的ID值不一定为1。通过常量CONTENT_POS_URI来访问文章信息条目时,必须要指定位置,这也是我们设置的URI匹配规则来指定的,后面我们将会看到。

每一个URI所访问的资源都是一种特定的数据类型,这些数据类型使用多功能Internet邮件扩展协议(MIME)来描述。MIME定义形式为 “[type]/[subtype]”,其中,[type]用来描述数据类型的大类,而[subtype]用来描述数据类型的子类。例如, "text/html"描述的数据类型的大类是文本(text),而子类是超文本标记语言(html)。

Content Provider组件规定,他所返回的资源的数据类型统一划分为"vnd.android.cursor.dir"和 “vnd.android.cursor.item” 两个大类,其中,前者用来描述 一个数据集合,后者用来描述一个个体数据。Content Provider组件所返回的资源的数据类型的子类则是可以自定义的,它的定义形式一般约定为
“vnd.[company name].[resource type]”。其中[company name] 表示一个公司的名称,[resource type]表示一个资源类型,在我们的例子中,CONTENT_TYPE和COTENT_ITEM_TYPE两个常量分别定义了两种MIME类型,它们的大类别分别为vnd.android.cursor.dir和vnd.android.cursor.item,而具体类别均为vdn.com.android.article,其中com.android就是表示公司名了,而article表示资源的类型为文章。这两个MIME类型常量主要是在实现ContentProvider的getType函数时用到的,后面我们将会看到。

ArticlesProvider.java

import java.util.HashMap;
 
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
 
public class ArticlesProvider extends ContentProvider {
    private static final String LOG_TAG = "com.android.providers.articles.ArticlesProvider";

	/*
	DB_NAME : SQLite数据库名称
	DB_VERSION  : SQLite数据库版本号
	DB_TABLE : 描述这个SQLite数据库中的一个数据库表,ArticlesProvider组件内部的文章信息就保存在里面
	*/
	private static final String DB_NAME = "Articles.db";
    private static final String DB_TABLE = "ArticlesTable";
    private static final int DB_VERSION = 1;

	/*
	定义一条SQL语句 DB_CREATE ,他是用来创建数据库表 DB_TABLE 的
	*/
    private static final String DB_CREATE = "create table " + DB_TABLE +
                            " (" + Articles.ID + " integer primary key autoincrement, " +
                            Articles.TITLE + " text not null, " +
                            Articles.ABSTRACT + " text not null, " +
                            Articles.URL + " text not null);";

	/*
	创建和初始化一个 URI 匹配器 uriMatcher,用来识别一个 URI 类别。匹配器uriMatcher 中有三个匹配规则:
	uriMatcher.addURI(Articles.AUTHORITY, "item", Articles.ITEM)  对应 URI匹配码 Articles.ITEM
    uriMatcher.addURI(Articles.AUTHORITY, "item/#", Articles.ITEM_ID) 对应 URI匹配码 Articles.ITEM_ID
    uriMatcher.addURI(Articles.AUTHORITY, "pos/#", Articles.ITEM_POS) 对应 URI匹配码 Articles.ITEM_POS
	*/
    private static final UriMatcher uriMatcher;
    static {
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
            uriMatcher.addURI(Articles.AUTHORITY, "item", Articles.ITEM);
            uriMatcher.addURI(Articles.AUTHORITY, "item/#", Articles.ITEM_ID);
            uriMatcher.addURI(Articles.AUTHORITY, "pos/#", Articles.ITEM_POS);
    }

	/*
	创建和初始化了一个字段映射表 articleProjectionMap,用来为数据库表ArticlesTable的字段名称设置别名,为数据库表
	ArticlesTable的字段名称设置别名之后,ArticlesProvider组件的访问者就不必知道它的真实字段名称了。此处我们将数据
	库表ArticlesTable的字段别名和真实名称设置为一样的。在实际应用中,可以根据需要来设置不一样的名称。
	*/
    private static final HashMap<String, String> articleProjectionMap;
    static {
            articleProjectionMap = new HashMap<String, String>();
            articleProjectionMap.put(Articles.ID, Articles.ID);
            articleProjectionMap.put(Articles.TITLE, Articles.TITLE);
            articleProjectionMap.put(Articles.ABSTRACT, Articles.ABSTRACT);
            articleProjectionMap.put(Articles.URL, Articles.URL);
    }

    private DBHelper dbHelper = null;
    private ContentResolver resolver = null;

	/*
	ArticlesProvider组件的成员函数 onCreate()在ArticlesProvider组件启动时候调用,主要用来创建一个DBHelper对象和一个ContentResolver
	对象,并且分别保存在成员变量dbHelper 和 resolver 中,dbHelper 用来辅助打开一个数据库,resolver 用来通知其他应用程
	序,ArticlesProvider组件中的数据发生了更新。
	*/
    @Override
    public boolean onCreate() {
            Context context = getContext();
            resolver = context.getContentResolver();
            dbHelper = new DBHelper(context, DB_NAME, null, DB_VERSION);

            Log.i(LOG_TAG, "Articles Provider Create");

            return true;
    }

	/*
	ArticlesProvider组件的成员函数getType()用来获取一个URI所要访问的资源的数据类型。只要三类URI可以访
	问ArticlesProvider组件,它们分别是:
	1
	"content://com.android.articles/item" 
		访问的资源的数据类型为:CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.android.article";
	2
	"content://com.android.articles/item/#"  
		访问的资源的数据类型为: CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.android.article";
    3
    "content://com.android.articles/pos/#" 
    	访问的资源的数据类型为: CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.android.article";
	*/
    @Override
    public String getType(Uri uri) {
            switch (uriMatcher.match(uri)) {
            case Articles.ITEM:
                    return Articles.CONTENT_TYPE;
            case Articles.ITEM_ID:
            case Articles.ITEM_POS:
                    return Articles.CONTENT_ITEM_TYPE;
            default:
                    throw new IllegalArgumentException("Error Uri: " + uri);
            }
    }

	
	/*
	ArticlesProvider组件的成员函数 insert() 用来向ArticlesProvider组件插入一个新的文章条目,参数 values 用来描述一个要
	插入的文章条目的信息。并且通过 DBHelper 对象dbHelper打开一个SQL数据库。db.insert()函数将参数 values 所描述的
	文章条目写入到这个SQL数据库表中。最后用  ContentResolver 对象方法  resolver.notifyChange() 通知其他应用程
	序,ArticlesProvider组件的文章条目已经发生变化。
	*/ 
    @Override
    public Uri insert(Uri uri, ContentValues values) {
            if(uriMatcher.match(uri) != Articles.ITEM) {
                    throw new IllegalArgumentException("Error Uri: " + uri);
            }

            SQLiteDatabase db = dbHelper.getWritableDatabase();

            long id = db.insert(DB_TABLE, Articles.ID, values);
            if(id < 0) {
                    throw new SQLiteException("Unable to insert " + values + " for " + uri);
            }

            Uri newUri = ContentUris.withAppendedId(uri, id);
            resolver.notifyChange(newUri, null);

            return newUri;
    }

	/*
	ArticlesProvider组件的成员函数 update()用来更新ArticlesProvider组件中的文章条目,
	参数selection和selectionArgs用来指定要更新的文章条目,参数values用来描述更新后的文章条目。
	首先通过DBHelper 对象dbHelper打开一个SQL数据库,然后switch判断要更新的是一个文章条目,还是一组文章条目,
	*/
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            int count = 0;

            switch(uriMatcher.match(uri)) {
            case Articles.ITEM: {
                    count = db.update(DB_TABLE, values, selection, selectionArgs);
                    break;
            }
            case Articles.ITEM_ID: {
					//从参数 uri 中获取它的文章条目的ID值
                    String id = uri.getPathSegments().get(1);
                    count = db.update(DB_TABLE, values, Articles.ID + "=" + id
                                    + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                    break;
            }
            default:
                    throw new IllegalArgumentException("Error Uri: " + uri);
            }
			
			//通知其他应用程序 ArticlesProvider组件的文章条目已经发生变化
            resolver.notifyChange(uri, null);

            return count;
    }

	/*
	ArticlesProvider组件的成员函数 delete()用来删除组件中的文章条目,参数selection和selectionArgs用来指定要删除的文
	章条目。
	*/	
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {

			/*
			首先通过DBHelper 对象dbHelper打开一个SQL数据库
			*/
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            int count = 0;
			
			/*
				判断要删除的是一组文章条目 还是 一个文章条目
			*/
            switch(uriMatcher.match(uri)) {
            case Articles.ITEM: {
                    count = db.delete(DB_TABLE, selection, selectionArgs);
                    break;
            }
            case Articles.ITEM_ID: {

					//从参数 uri 中获取它的文章条目的ID值
                    String id = uri.getPathSegments().get(1);
                    count = db.delete(DB_TABLE, Articles.ID + "=" + id
                                    + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs);
                    break;
            }
            default:
                    throw new IllegalArgumentException("Error Uri: " + uri);
            }

			//通知其他应用程序 ArticlesProvider组件的文章条目已经发生变化
            resolver.notifyChange(uri, null);

            return count;
    }

	/*
	ArticlesProvider组件的成员函数 query() 用来获取 ArticlesProvider组件中的文章条目,参数selection和selectionArgs用来
	指定要获取的文章条目,参数projection用来指定要获取的文章条目的字段的的别名。ArticlesProvider组件将最终获得的文
	章条目保存你一个Cursor 对象中。
	*/
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            Log.i(LOG_TAG, "ArticlesProvider.query: " + uri);

			/*
			首先通过DBHelper 对象dbHelper打开一个SQL数据库
			*/
            SQLiteDatabase db = dbHelper.getReadableDatabase();

			/*
			创建一个SQLiteQueryBuilder对象sqlBuilder ,用来准备一条SQL查询语句,以便可以从前面打开的SQL数据库中获得调用者指定的文章条目。
			*/
            SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
            String limit = null;

			/*
				判断要删除的是一组文章条目 还是 一个文章条目,如果获取的是一个文章条目,那么还需要进一步判断是按照ID值还是位置值来获得这个文章条目
			*/
            switch (uriMatcher.match(uri)) {
            case Articles.ITEM: {
                    sqlBuilder.setTables(DB_TABLE);
                    sqlBuilder.setProjectionMap(articleProjectionMap);
                    break;
            }
            case Articles.ITEM_ID: {
					//如果按照ID值来获取文章条目,从参数 uri 中获取它的文章条目的ID值
                    String id = uri.getPathSegments().get(1);
                    sqlBuilder.setTables(DB_TABLE);
                    sqlBuilder.setProjectionMap(articleProjectionMap);
                    sqlBuilder.appendWhere(Articles.ID + "=" + id);
                    break;
            }

			/*
			如果是按照位置值获得文章条目,就从参数 uri 中获取它的文章条目的位置值
			*/
            case Articles.ITEM_POS: {
                    String pos = uri.getPathSegments().get(1);
                    sqlBuilder.setTables(DB_TABLE);
                    sqlBuilder.setProjectionMap(articleProjectionMap);
                    limit = pos + ", 1";
                    break;
            }
            default:
                    throw new IllegalArgumentException("Error Uri: " + uri);
            }

			/*
			通过 SQLiteQueryBuilder 对象sqlBuilder 来从前面打开的SQLite数据库中获得指定的文章条目。并且保存在一个
			cursor 对象中。在将这个cursor 对象返回给调用者之前,将 resolver对象设置到他里面去,以便保存在它里面的文章
			条目发生变化时,可以通过这个resolver对象通知其他应用程序,ArticlesProvider组件的文章条目发生变化了。
			*/
            Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Articles.DEFAULT_SORT_ORDER : sortOrder, limit);
            cursor.setNotificationUri(resolver, uri);

            return cursor;
    }

	/*
	成员函数 call()是一个未公开的数据查询接口。如果一个应用程序想通过这个未公开的接口来获取一个 Content Provider组件的数据,那么它必
	须在Android源代码环境中进行编译。
	函数中只提供一个数据获取操作 Articles.METHOD_GET_ITEM_COUNT,用来获取ArticlesProvider组件中的文章条目总数。
	*/
    @Override
    public Bundle call(String method, String request, Bundle args) {
            Log.i(LOG_TAG, "ArticlesProvider.call: " + method);

            if(method.equals(Articles.METHOD_GET_ITEM_COUNT)) {
                    return getItemCount();
            }

            throw new IllegalArgumentException("Error method call: " + method);
    }

    private Bundle getItemCount() {
            Log.i(LOG_TAG, "ArticlesProvider.getItemCount");

            SQLiteDatabase db = dbHelper.getReadableDatabase();
            Cursor cursor = db.rawQuery("select count(*) from " + DB_TABLE, null);

            int count = 0;
            if (cursor.moveToFirst()) {
                    count = cursor.getInt(0);
            }

            Bundle bundle = new Bundle();
            bundle.putInt(Articles.KEY_ITEM_COUNT, count);

            cursor.close();
            db.close();

            return bundle;
    }

	/*
	实现了一个用来打开SQLite数据库的辅助类DBHelper
	*/
    private static class DBHelper extends SQLiteOpenHelper {
            public DBHelper(Context context, String name, CursorFactory factory, int version) {
                    super(context, name, factory, version);
            }

            @Override
            public void onCreate(SQLiteDatabase db) {
                    db.execSQL(DB_CREATE);
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                    db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
                    onCreate(db);
            }
    }
}

我们在实现自己的Content Provider时,必须继承于ContentProvider类,并且实现以下六个函数:

    -- onCreate(),用来执行一些初始化的工作。

    -- query(Uri, String[], String, String[], String),用来返回数据给调用者。

    -- insert(Uri, ContentValues),用来插入新的数据。

    -- update(Uri, ContentValues, String, String[]),用来更新已有的数据。

    -- delete(Uri, String, String[]),用来删除数据。

    -- getType(Uri),用来返回数据的MIME类型。

成员函数 call()是一个未公开的数据查询接口。如果一个应用程序想通过这个未公开的接口来获取一个 Content Provider组件的数据,那么它必须在Android源代码环境中进行编译。一般来说一个应用程序想要获取Content Provider组件中的数据,那么只能调用它的标准数据查询接口,即调用成员函数query()就可以了。既然如此,为什么还要提供非标准的数据查询接口呢? Content Provider组件的成员函数 query()和call()最大的区别在于,query()将要获取的数据保存在一个Cursor对象中传输给调用者,call()将要获取的数据保存在一个Bundle对象中传输给调用者。保存在Cursor对象中的数据是通过匿名共享内存来传输的,而保存在Bundle对象中的数据是直接使用Binder进程间通信机制来传输的。当传输数据量很大时,使用匿名共享内存是有好处的,但是当传输数据量比较小时,使用匿名共享内存就得不偿失了,因为匿名共享内存不是免费的午餐,它的创建过程和映射过程都是需要开销的。因此 Content Provider组件就提供了这个未公开的成员函数接口call()来方便调用者可以快速的获取一些量比较小的数据。

辅助类DBHelper继承自 SQLiteOpenHelper类,由于打开数据库涉及到IO操作,因此,我们应该避免在
ArticlesProvider组件启动时区打开它内部的数据库,即避免在它的成员函数onCreate中执行打开数据的操作。否则,ArticlesProvider组件可能会由于启动超时而被系统中止,那么ArticlesProvider组件应该在什么时候执行打开数据库操作呢?最好的时机莫过于第一次使用这个数据可的时候了。

SQLiteOpenHelper类就是这样一个类,当他的成员函数 getReadableDatabase或者getWritableDatabase第一次被调用时,他才会打开与它关联的一个SQLite数据库。我们一般不会直接使用SQLiteOpenHelper类来辅助打开一个SQLite数据库,而是以它为父类实现一个子类,并且在子类中重写它的成员函数onCreate() 和 onUpgrade() 。

四 Article应用程序

应用程序Article由两个Activity组件 MainActivity和ArticleActivity组成,他们用来浏览、添加、修改、删除ArticlesProvider组件中的文章信息。

-----AndroidManifest.xml 
-----Android.mk 
-----src 
	-----com/android/articles 
		-----Article.java 
		-----MainActivity.java 
		-----ArticleActivity.java
		-----ArticlesAdapter.java
-----libs
	-----ArticlesProvider.jar
-----res 
	-----layout 
		-----main.xml
		-----article.xml
		-----item.xml 
	-----values
		-----strings.xml
	-----drawable 
		-----icon.png
		-----border.xml

Article.java

package com.android.article;
 
public class Article {
        private int id;
        private String title;
        private String abs;
        private String url;
 
    public Article(int id, String title, String abs, String url) {
            this.id = id;
            this.title = title;
            this.abs = abs;
            this.url = url;
    }

    public void setId(int id) {
            this.id = id;
    }

    public int getId() {
            return this.id;
    }

    public void setTitle(String title) {
            this.title = title;
    }

    public String getTitle() {
            return this.title;
    }

    public void setAbstract(String abs) {
            this.abs = abs;
    }

    public String getAbstract() {
            return this.abs;
    }

    public void setUrl(String url) {
            this.url = url;
    }

    public String getUrl() {
            return this.url;
    }
}

Article.java定义了一个Article类,用来封装一个文章条目信息,它里面包含四个变量,id、title、abs、url 分别用来描述文章条目的ID、标题、摘要和URL。

ArticlesAdapter.java

package shy.luo.article;
 
import java.util.LinkedList;
 
import shy.luo.providers.articles.Articles;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.IContentProvider;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
 
 
public class ArticlesAdapter {
        private static final String LOG_TAG = "shy.luo.article.ArticlesAdapter";

    private ContentResolver resolver = null;

	/*
	构造函数中调用参数 context的成员函数getContentResolver()来获得一个ContentResolver对象,并且保存在resolver 中
	*/
    public ArticlesAdapter(Context context) {
            resolver = context.getContentResolver();
    }

	/*
	ArticlesAdapter 类的成员函数insertArticle()用来向 ArticlesProvider组件中插入一个新的文章条目
	参数 article 描述要插入的文章条目
	*/
    public long insertArticle(Article article) {

			/* 将参数article内容写入到一个 ContentValues 对象 values  中。*/
            ContentValues values = new ContentValues();
            values.put(Articles.TITLE, article.getTitle());
            values.put(Articles.ABSTRACT, article.getAbstract());
            values.put(Articles.URL, article.getUrl());

			/*
			通过ContentResolver 对象 resolver 将 ContentValues 对象 values 发送给ArticlesProvider组件,以便
			ArticlesProvider组件可以向内部数据库插入一个新的文章条目。
			*/
            Uri uri = resolver.insert(Articles.CONTENT_URI, values);

			/* 获得插入成功后的文章条目ID*/
            String itemId = uri.getPathSegments().get(1);

            return Integer.valueOf(itemId).longValue();
    }

	/*
	用来更新 ArticlesProvider组件中的文章条目
	参数article 描述要更新的文章条目
	*/
    public boolean updateArticle(Article article) {
    		
    		/* 
    			首先构造一个URI,用来指定要更新的文章条目的ID值
    			ContentUris是content URI的一个辅助类。它有两个方法很有用,具体如下所示:
    			public static Uri withAppendedId(Uri contentUri, long id)
    				这个方法负责把id和contentUri连接成一个新的Uri,如此时ID=100,则连接后为:
    				content://com.android.articles/item/100
    				
    			public static long parseId(Uri contentUri)
    				这个方法负责把content URI 后边的id解析出来
    		 */
            Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, article.getId());

			/* 将参数article内容写入到一个 ContentValues 对象 values  中。*/
            ContentValues values = new ContentValues();
            values.put(Articles.TITLE, article.getTitle());
            values.put(Articles.ABSTRACT, article.getAbstract());
            values.put(Articles.URL, article.getUrl());

			/*
			通过ContentResolver 对象 resolver 将 ContentValues 对象 values 发送给ArticlesProvider组件,以便
			ArticlesProvider组件可以向内部数据库更新指定的文章条目。
			*/
            int count = resolver.update(uri, values, null, null);

            return count > 0;
    }

	/*用来删除 ArticlesProvider组件中的文章条目*/
    public boolean removeArticle(int id) {
    		/*首先构造一个URI,用来指定要更新的文章条目的ID值*/
            Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, id);

		    /*
			通过ContentResolver 对象 resolver 将 ContentValues 对象 values 发送给ArticlesProvider组件,以便
			ArticlesProvider组件可以向内部数据库删除指定的文章条目。
			*/
            int count = resolver.delete(uri, null, null);

            return count > 0;
    }

	/* 用来获取ArticlesProvider组件中所有的文章条目 */
    public LinkedList<Article> getAllArticles() {
            LinkedList<Article> articles = new LinkedList<Article>();

            String[] projection = new String[] {
                    Articles.ID,
                    Articles.TITLE,
                    Articles.ABSTRACT,
                    Articles.URL
            };
			
			/* ArticlesProvider组件返回的文章条目保存在一个Cursor 对象中 */
            Cursor cursor = resolver.query(Articles.CONTENT_URI, projection, null, null, Articles.DEFAULT_SORT_ORDER);
			/* 遍历cursor 对象内容,将里面的每一个文章条目都封装成 article 对象,保存在LinkedList列表中*/
            if (cursor.moveToFirst()) {
                    do {
                            int id = cursor.getInt(0);
                            String title = cursor.getString(1);
                            String abs = cursor.getString(2);
                            String url = cursor.getString(3);

                            Article article = new Article(id, title, abs, url);
                            articles.add(article);
                    } while(cursor.moveToNext());
            }
			
			/* 将LinkedList列表返还给调用者 */
            return articles;
    }

	/* 用来获取ArticlesProvider组件中所有的文章条目总数,是通过调用ArticlesProvider组件的成员函数call()实现的,总数保存
	在Bundle对象中 */
    public int getArticleCount() {
            int count = 0;

            try {
                    IContentProvider provider = resolver.acquireProvider(Articles.CONTENT_URI);
                    Bundle bundle = provider.call(Articles.METHOD_GET_ITEM_COUNT, null, null);
                    count = bundle.getInt(Articles.KEY_ITEM_COUNT, 0);
            } catch(RemoteException e) {
                    e.printStackTrace();
            }

            return count;
    }

	/* 根据文章的ID值从ArticlesProvider组件中获取指定的文章条目,即通过URI=CONTENT_URI */
    public Article getArticleById(int id) {
            Uri uri = ContentUris.withAppendedId(Articles.CONTENT_URI, id);

            String[] projection = new String[] {
                            Articles.ID,
                Articles.TITLE,
                Articles.ABSTRACT,
                Articles.URL
            };

            Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);

            Log.i(LOG_TAG, "cursor.moveToFirst");

            if (!cursor.moveToFirst()) {
                    return null;
            }

            String title = cursor.getString(1);
            String abs = cursor.getString(2);
            String url = cursor.getString(3);

            return new Article(id, title, abs, url);
    }

	/* 根据文章的位置值从ArticlesProvider组件中获取指定的文章条目,即通过URI=CONTENT_POS_URI */
    public Article getArticleByPos(int pos) {
            Uri uri = ContentUris.withAppendedId(Articles.CONTENT_POS_URI, pos);

            String[] projection = new String[] {
                            Articles.ID,
                Articles.TITLE,
                Articles.ABSTRACT,
                Articles.URL
            };

            Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);
            if (!cursor.moveToFirst()) {
                    return null;
            }

            int id = cursor.getInt(0);
            String title = cursor.getString(1);
            String abs = cursor.getString(2);
            String url = cursor.getString(3);

            return new Article(id, title, abs, url);
    }
}

MainActivity.java

package com.android.article;
 
import shy.luo.providers.articles.Articles;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
 
public class MainActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener {
        private final static String LOG_TAG = "shy.luo.article.MainActivity";

    private final static int ADD_ARTICAL_ACTIVITY = 1;
    private final static int EDIT_ARTICAL_ACTIVITY = 2;

    private ArticlesAdapter aa = null;
    private ArticleAdapter adapter = null;
    private ArticleObserver observer = null;

    private ListView articleList = null;
    private Button addButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
			
			/* 创建ArticlesAdapter对象 aa  用来辅助访问ArticlesProvider组件中的文章信息 */
            aa = new ArticlesAdapter(this);

            articleList = (ListView)findViewById(R.id.listview_article);
            
            /* 创建ArticlesAdapter对象adapter  用来作为界面上一个文章列表控件的数据源 */
			adapter = new ArticleAdapter(this);
            articleList.setAdapter(adapter);
            articleList.setOnItemClickListener(this);
			
			/* 创建和注册  ArticleObserver对象observer  用来监控ArticlesProvider组件中的文章信息变化*/
            observer = new ArticleObserver(new Handler());
            getContentResolver().registerContentObserver(Articles.CONTENT_URI, true, observer);

            addButton = (Button)findViewById(R.id.button_add);
            addButton.setOnClickListener(this);

            Log.i(LOG_TAG, "MainActivity Created");
    }

	/* 用于注销前面所注册的 ArticleObserver对象observer ,表示不需要监控ArticlesProvider组件中的文章信息变化了*/
    @Override
    public void onDestroy() {
            super.onDestroy();
            getContentResolver().unregisterContentObserver(observer);
    }

    @Override
    public void onClick(View v) {
            if(v.equals(addButton)) {
                    Intent intent = new Intent(this, ArticleActivity.class);
                    startActivityForResult(intent, ADD_ARTICAL_ACTIVITY);
            }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
            Intent intent = new Intent(this, ArticleActivity.class);

            Article article = aa.getArticleByPos(pos);
            intent.putExtra(Articles.ID, article.getId());
            intent.putExtra(Articles.TITLE, article.getTitle());
            intent.putExtra(Articles.ABSTRACT, article.getAbstract());
            intent.putExtra(Articles.URL, article.getUrl());

            startActivityForResult(intent, EDIT_ARTICAL_ACTIVITY);
    }

    @Override
    public void onActivityResult(int requestCode,int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);

            switch(requestCode) {
            case ADD_ARTICAL_ACTIVITY: {
                    if(resultCode == Activity.RESULT_OK) {
                            String title = data.getStringExtra(Articles.TITLE);
                            String abs = data.getStringExtra(Articles.ABSTRACT);
                            String url = data.getStringExtra(Articles.URL);

                            Article article = new Article(-1, title, abs, url);
                            aa.insertArticle(article);
                    }

                    break;
            }

            case EDIT_ARTICAL_ACTIVITY: {
                    if(resultCode == Activity.RESULT_OK) {
                            int action = data.getIntExtra(ArticleActivity.EDIT_ARTICLE_ACTION, -1);
                            if(action == ArticleActivity.MODIFY_ARTICLE) {
                                    int id = data.getIntExtra(Articles.ID, -1);
                                    String title = data.getStringExtra(Articles.TITLE);
                                    String abs = data.getStringExtra(Articles.ABSTRACT);
                                    String url = data.getStringExtra(Articles.URL);

                                    Article article = new Article(id, title, abs, url);
                                    aa.updateArticle(article);
                            } else if(action == ArticleActivity.DELETE_ARTICLE)     {
                                    int id = data.getIntExtra(Articles.ID, -1);

                                    aa.removeArticle(id);
                            }

                    }

                    break;
            }
            }
    }

    private class ArticleObserver extends ContentObserver {
            public ArticleObserver(Handler handler) {
                    super(handler);
            }

            @Override
            public void onChange (boolean selfChange) {
                    adapter.notifyDataSetChanged();
            }
    }

    private class ArticleAdapter extends BaseAdapter {
            private LayoutInflater inflater;

            public ArticleAdapter(Context context){
                      inflater = LayoutInflater.from(context);
            }

            @Override
            public int getCount() {
                    return aa.getArticleCount();
            }

            @Override
            public Object getItem(int pos) {
                    return aa.getArticleByPos(pos);
            }

            @Override
            public long getItemId(int pos) {
                    return aa.getArticleByPos(pos).getId();
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                    Article article = (Article)getItem(position);

                    if (convertView == null) {
                            convertView = inflater.inflate(R.layout.item, null);
                    }

                    TextView titleView = (TextView)convertView.findViewById(R.id.textview_article_title);
                    titleView.setText("Title: " + article.getTitle());

                    TextView abstractView = (TextView)convertView.findViewById(R.id.textview_article_abstract);
                    abstractView.setText("Abstract: " + article.getAbstract());

                    TextView urlView = (TextView)convertView.findViewById(R.id.textview_article_url);
                    urlView.setText("URL: " + article.getUrl());

                    return convertView;
            }
    }
}

在应用程序的主界面中,我们使用一个ListView来显示文章信息条目,这个ListView的数据源由ArticleAdapter类来提供,而ArticleAdapter类又是通过ArticlesAdapter类来获得ArticlesProvider中的文章信息的。在MainActivity的onCreate函数,我们还通过应用程序上下文的ContentResolver接口来注册了一个ArticleObserver对象来监控ArticlesProvider中的文章信息。一旦ArticlesProvider中的文章信息发生变化,就会通过ArticleAdapter类来实时更新ListView中的文章信息。

ArticleActivity.java

package shy.luo.article;
 
import shy.luo.providers.articles.Articles;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
 
public class ArticleActivity extends Activity implements View.OnClickListener {
        private final static String LOG_TAG = "shy.luo.article.ArticleActivity";

    public final static String EDIT_ARTICLE_ACTION = "EDIT_ARTICLE_ACTION";
    public final static int MODIFY_ARTICLE = 1;
    public final static int DELETE_ARTICLE = 2;

    private int articleId = -1;

    private EditText titleEdit = null;
    private EditText abstractEdit = null;
    private EditText urlEdit = null;

    private Button addButton = null;
    private Button modifyButton = null;
    private Button deleteButton = null;
    private Button cancelButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.article);

            titleEdit = (EditText)findViewById(R.id.edit_article_title);
            abstractEdit = (EditText)findViewById(R.id.edit_article_abstract);
            urlEdit = (EditText)findViewById(R.id.edit_article_url);

            addButton = (Button)findViewById(R.id.button_add_article);
            addButton.setOnClickListener(this);

            modifyButton = (Button)findViewById(R.id.button_modify);
            modifyButton.setOnClickListener(this);

            deleteButton = (Button)findViewById(R.id.button_delete);
            deleteButton.setOnClickListener(this);

            cancelButton = (Button)findViewById(R.id.button_cancel);
            cancelButton.setOnClickListener(this);

            Intent intent = getIntent();
            articleId = intent.getIntExtra(Articles.ID, -1);

            if(articleId != -1) {
                    String title = intent.getStringExtra(Articles.TITLE);
                    titleEdit.setText(title);

                    String abs = intent.getStringExtra(Articles.ABSTRACT);
                    abstractEdit.setText(abs);

                    String url = intent.getStringExtra(Articles.URL);
                    urlEdit.setText(url);

                    addButton.setVisibility(View.GONE);
            } else {

                    modifyButton.setVisibility(View.GONE);
                    deleteButton.setVisibility(View.GONE);
            }

            Log.i(LOG_TAG, "ArticleActivity Created");
    }

    @Override
    public void onClick(View v) {
            if(v.equals(addButton)) {
                    String title = titleEdit.getText().toString();
                    String abs = abstractEdit.getText().toString();
                    String url = urlEdit.getText().toString();

                    Intent result = new Intent();
                    result.putExtra(Articles.TITLE, title);
                    result.putExtra(Articles.ABSTRACT, abs);
                    result.putExtra(Articles.URL, url);

                    setResult(Activity.RESULT_OK, result);
                    finish();
            } else if(v.equals(modifyButton)){
                    String title = titleEdit.getText().toString();
                    String abs = abstractEdit.getText().toString();
                    String url = urlEdit.getText().toString();

                    Intent result = new Intent();
                    result.putExtra(Articles.ID, articleId);
                    result.putExtra(Articles.TITLE, title);
                    result.putExtra(Articles.ABSTRACT, abs);
                    result.putExtra(Articles.URL, url);
                    result.putExtra(EDIT_ARTICLE_ACTION, MODIFY_ARTICLE);

                    setResult(Activity.RESULT_OK, result);
                    finish();
            } else if(v.equals(deleteButton)) {
                    Intent result = new Intent();
                    result.putExtra(Articles.ID, articleId);
                    result.putExtra(EDIT_ARTICLE_ACTION, DELETE_ARTICLE);

                    setResult(Activity.RESULT_OK, result);
                    finish();
            } else if(v.equals(cancelButton)) {
                    setResult(Activity.RESULT_CANCELED, null);
                    finish();

            }
    }
}

在ArticleActivity窗口中,我们可以执行新增、更新和删除文章信息的操作。如果启动ArticleActivity时,没有把文章ID传进来,就说明要执行操作是新增文章信息;如果启动ArticleActivity时,把文章ID和其它信自都传进来了,就说明要执行的操作是更新或者删除文章,根据用户在界面点击的是更新按钮还是删除按钮来确定。

Android 四大组件之Content Provider,外部应用访问不到,原因就是如此简单 如果你遇到了Content Provider 外部应用不能访问的情况,content provider 可以帮助应用管理对自身存储或由其他应用存储的数据的访问,并提供与其他应用共享数据的方法。它们封装数据,并提供用于定义数据安全性的机制。content provider 是将一个进程中的数据与另一个进程中运行的代码连接的标准接口。实现 content provider 有诸多优势。最重要的是,您可以配置 content provider,让其他应用安全地访问和修改您的应用数据。是Android四大组件之一。 阅读详情

相关推荐

Android四大组件之Content Provider

Android四大组件之Content Provider 作者:白璐 日期:2020/2/24 文章目录Android四大组件之Content Provider概述内容提供器(Content Provider)Content Provider简介ContentProvider的实现统一资源标识符(URI)MIME数据类型ContentProvider类ContentResolver类Conte...

白露未晞的专栏 465

四大组件之ContentProvider

1 基础知识 1.1 作用:为不同的软件之间数据共享,提供统一的接口 1.2 形式:采用URL形式表示外界需要访问的“数据库” 1.3 ContentProvider:内容提供者;ContentResolver 内容解析者 2 实战演练参考:http://blog.youkuaiyun.com/dmk877/article/details/50387741ContentProvider:项目A;

enjoy_blogs 355

【Android】四大组件(Activity、Service、Broadcast Receiver、Content Provider)、结构目录

当 Activity 的启动模式为 singleTask 的时候,每次启动该 Activity 的时候系统会首先在返回栈中检查是否存在该 Activity 的实例,如果发现已经存在则直接使用该实例。假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存!standard 模式是 Activity 的默认启动模式,在不进行显示指定的情况下,所有 Activity 都会自动使用这种启动模式。

xzzteach的博客 2111

ContentProvider组件的实现原理

内容摘自罗升阳的Android系统源代码情景分析。     作为Android应用程序的四大组件之一,Content Provider除了可以用来作为数据访问接口之外,还可以在不同的应用程序之间进行数据共享。     Android系统中,每一个应用程序只可以访问自己创建的数据。然而,有时候我们需要在不同的应用程序之间进行数据共享,例如很多程序都需要访问通讯录中的联系人信息来实现自己的业务功能

Garment1991的专栏 2183

前端开发中常见的 Provider 是什么?

Provider 是前端开发中常见的一种模式,用于在组件层次结构中共享数据和状态。它通过提供上下文并将数据提供给后代组件,避免了通过 props 传递数据的繁琐过程。Provider 模式适用于全局状态管理、主题和样式管理以及共享认证和用户信息等场景。在代码中实现 Provider 通常依赖于框架或库的支持。以 React 框架为例,可以使用上下文(Context)来创建 Provider。通过创建上下文对象并使用 Provider 组件提供数据,可以实现数据的共享和状态管理。

IOWEL的博客 772

[车联网安全自学篇] Android安全之Content Provider攻防

"Content Provider 攻防"按照Google为Android设计的安全模型,应用的数据属于私有数据,故默认情况下,应用无法访问其他应用的私有数据。但当应用需要与其他应用共享数据时,Content Provider就扮演着应用间数据共享桥梁的角色。

橙留香Park的博客 5639

React中Provider组件详解

使用场景 1.A(顶级组件)-》B组件(子组件)-》C组件(孙子组件) 2.A(顶级组件)-》B组件(子组件)、C组件(孙子组件)…很多组件(每个组件都需要传props) A组件传递数据给C组件实现方式有很多种 1.通过props层层传递到C组件 2.通过全局对象 使用Provider可以解决数据层层传递和每个组件都要传props的问题; 三个嵌套组件:A B C 场景:组件 C 想要取组件A 的属性,一般做法通过A–B–C一层一层传递下来 缺点:使用麻烦,容易出错,中途出错,后续都错 class A ex

zai329979994639的博客 1万+

Android Content Provider 示例项目

Content Provider作为Android系统中用于数据共享的核心组件,它允许应用在遵循Android安全模型的基础上,对外开放其私有数据或者使用其他应用共享的数据。Content Provider通过一系列标准的接口提供数据的查询、插入、更新和删除操作,这使得不同应用之间可以便捷地共享数据,而无需直接访问其他应用的数据存储。理解Content Provider的工作机制对于构建模块化、数据驱动的Android应用至关重要。

weixin_35871529的博客 904

android四大组件之三-Provider实现原理分析

本篇主要会讲以下内容: 1.ContentProvider的一些基本概念和流程 2.ContentProvider中具体实现和注册流程。 3.使用者拿到ContentProvider的binder引用后执行具体的操作,如quert/insert等等。 4.关于ContentProvider的一些扩展性的问题。

分享+记录 6962

基于Codex Skill的抖音爆款分析与带货视频自动化生成实战

这次我们来看一个基于 Codex 的自动化内容创作项目。核心是利用 Codex 平台的能力,通过自定义的 Skill(技能)来拆解抖音爆款博主的视频模式,并自动生成自己的带货视频。对于内容创作者、电商运营或对短视频自动化感兴趣的技术开发者来说,这是一个将大语言模型(LLM)能力与具体业务场景深度结合的实战案例。 项目的核心价值在于流程自动化。它试图解决一个常见痛点:分析爆款视频的脚本结构、话术、节奏需要大量人工,而模仿创作又耗时费力。通过 Codex 的 Skill 机制,可以将“分析”和“生成”这两个环节

weixin_30420305的博客 383

使用FSL对DTI数据进行预处理

在fsl官网我们可以看到是同时使用了AP和PA图像处理,采用的是最小二乘法将2个图像合并为1个图像。–inindex此处需要处理的是PA图像,在acq_param.txt中我们是把这个放在第一行的,此处为1,如果处理的是AP图像,则此处为2。为了校正磁场引起的畸变,对DTI的B0像分别采集了A>P和P>A的反向编码的两种B0像。即如果AP和PA只有1个B0像时,那么这里就是两行,如果各有3个B0像则这里应该是6行。假设转换后的图像为sub01_AP.nii.gz和sub01_PA.nii.gz。

weixin_52073820的博客 4839

Android四大组件基础学习之Content Provider

Android四大组件基础学习之Content Provider

Y3036359510的博客 1175

嵌入式工程师必学(9):USB 3.0协议理解

USB3.0和USB2.0相比,在线缆上从4根线(Vbus、GND、D+、D-)变为了8根线(Vbus、GND、D+、D-、SSRX+\SSRX-\SSTX+\SSTX-),可见增加的4根线都是和超速相关的,新增加的超速有两类,分别为:超速Gen1(5.0Gbps),超速+Gen2(10Gbps),还兼容USB2.0的3类速率:low-speed(1.5Mbps),full-speed(12Mbps),andhigh-speed(480Mbps)。从设计可以看出,USB3....

码农17年的博客 1万+

3-6安卓中的Content Provider组件

​继承与重写方法​需继承ContentProvider类并实现以下核心方法:java@Overridepublic boolean onCreate() { /* 初始化数据库等操作/ }@Override查询逻辑 */ }// 重写insert、update、delete、getType方法getType():返回MIME类型(如vnd.android.cursor.dir/user表示多行数据)。​注册Provider​。

weixin_45530192的博客 1438

SD卡烧写Linux kernel——SD卡分区,并烧写uboot,kernel,DTB及filesystem

Linux Ubuntu环境下对SD卡分区(烧写uboot,kernel,及filesystem) 介绍dd命令的使用,SD卡分区、格式化、复制文件等 fdisk命令

weixin_42408707的博客 5056

【SOUND】Linux音频子系统ASoC的驱动框架总分析

ALSA系统在芯片(ASoC)层的总体项目目标是为嵌入式芯片处理器(例如PXA2XX,AU1X00,IMX等)和便携式音频编解码器提供更好的ALSA支持。 在进入ASoC子系统之前,内核对SoC音频有一些支持,但是它有一些局限性:ASoC层旨在解决这些问题并提供以下内容特征 :为了实现这一切,ASoC基本上将嵌入式音频系统分为多个可重复使用的组件驱动程序:另外,这里提下OSS(Open Sound System)这个基本被弃用的音频框架。 可以参考Linux OSS架构声卡驱动框架 2.3 ASoC重要

wang 恒 的博客 1781

Android四大组件——Content Provider

例如,用户只需知道有一个insert方法可以插入数据,并且向该方法传递待插入的数据即可,其并不关心数据是如何插入的,也不关心数据的具体组织形式。但是,程序不能直接操作Content Provider,而是借助名为Content Resolver的类,如下图所示。Content Provider 使用基于数据库模型的简单表格来提供需要共享的数据,在该表格中,每一行表示一条记录,而每一列代表特定类型和含义的数据,并且其中每一条数据记录都包含一个名为“_ID”的字段类标识每条数据。但是该过程是主动地读取数据。

2301_79188932的博客 1079

Far planner 代码系列(33) 关于real_world_contour和contour_graph

我们知道far planner前部分的流程是这样子的: 对于特定的地图,如下: 我们要从surround_obs_cloud里抽取生成realworld_contour_,如下图所示: 在这时候,每个node的z坐标都是以机器人的高度定的。如图所示: 先去吃饭啦~待会回来在写 ...

博客 762

Android Content Provider

使用 Content Provider | Android使用 Content Provider如果你要公开你的数据,你可以创建或者使用一个Content Provider。它是一个能使所以应用程度都能存储和检索数据的对象。它是唯一在包和包之间分享数据的方法;因为不存在那种供所有的包来共享的一般存储空间。Android自带了一些Content Provider,它们用于一些一般的数

dywe_ddm的专栏 2302

【K8S系列】在 Kubernetes 中使用 Prometheus 进行监控的详细指南

Prometheus 是一个开源的监控和报警工具,广泛用于 Kubernetes 环境中。本文将深入探讨如何通过 Kubernetes 中的注解配置 Prometheus 的抓取设置,以高效监控服务,并确保系统的可靠性和可维护性。

颜淡慕潇 2万+

(第14天)Veritas NetBackup(NBU) 配合 RMAN 脚本备份 Oracle 数据库

NBU(Veritas Netbackup)是企业中比较常用的备份软件,通常用于备份企业使用中的系统,数据库,文件等等,通常是搭配 Oracle RMAN 来进行备份 Oracle 数据库。

LuciferLiu_DBA 1642

content provider 的使用

android有一个独特之处就是,数据库只能被它的创建者所使用,其他的应用是不能访问到的,所以如果你想实现不同应用之间的数据共享,就不得不用content provider了。    在Android中,content provider是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取以及操作数据。并且,android自身也提供了几个现成的content provider:Contact

jiasudu1的专栏 691

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

content provider write

<br />reference: http://jlins.javaeye.com/blog/561434<br /> 如果需要创建你自己的cotent provider,也就是定制自己特有的,你需要做的其实很简单,那就是从抽象类ContentProvider派生出你自己的类,并且实现里面所有的抽象方法,包括:  <br /> <br /><br /><br /><br />query(Uri, String[], String, String[], String) which returns data t

fh400的专栏 731

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

(译)从Content Provider中获取数据

原文:https://developer.android.google.cn/guide/topics/providers/content-provider-basics.htmlContent Provider基础Content Provider管理对中央数据仓库的访问,它是Android应用的一部分,通常会提供自己的UI来使用数据。然而,Content Provider主要是为了给其他应用使用而

swt369的博客 2977

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Content Providers--怎样去访问一个基本的content provider

一个content provider 管理着怎样去访问存储在应用仓库中的数据。一个content provider是一个应用程序的一部分,它常常用来使应用程序的界面工作在它提供的数据基础上。然而,content providers 主要是用来给其它的应用程序使用的,使用一个provider客户端对象来访问使用它。总之,providers和provider 客户端对象为应用中的数据提供一个一致的

Tibib的专栏 1143

Content Provider与SQLite结合使用

虽然推荐使用数据库保存结构化的复杂数据,但是数据共享是一个挑战,因为数据库只允许创建它的应用访问。在Android中,推荐使用content provider方法在不同包之间共享数据。content provider可以被视为一个数据仓库。它如何存储数据与应用如何使用它无关。本例将讲解Content Provider与SQLite结合使用。

Notzuonotdied的博客 2490
上一篇: 串口,com口,ttl,max232
下一篇: Linux摄像头驱动第一篇之虚拟摄像头驱动vivi.c
Ma浩然
博客等级 码龄9年 1万+粉丝 462原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

Ma浩然

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

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

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

打赏作者

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

抵扣说明:

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

余额充值