【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider
数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑。它只是负责为应用提供数据访问的接口。Android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)。如果把第三方应用比作一个黑盒子的话,ContentProvider就像是从里面延伸出来的管道,从这个管道,应用可以把一些数据共享出来,我们也可以往里面输送数据。但是里面怎么处理数据我们看不到,也管不着。并且这个管道是有规范标准的,不是它规定的数据你塞不进这个管道。
一、ContentProvider的特征
- 我们为什么使用ContentProvider?像上几篇写的博客中就有好几种方法可以跨应用来读取数据,但ContentProvider的特点不仅仅如此。首先它作为Android中四大组件之一,(我们都知道组件的信息会被android统一管理),提供数据的跨进程无缝隙访问,并会在进程中提供本地缓存。跨进程调用是需要时间和资源消耗的,而通过缓存可以有效的提高效率。再则ContentProvider规定了数据访问结构,严谨不容易发生错误。然后,应用调用接口进行操作时,是一个同步的过程,也就是说,所有对数据源组件对象中的数据操作都是在消息队列中串行执行的,我们开发者就不需要考虑复杂的并发情形。最后,数据源组件中数据存储的方式没有任何的限制,可以通过数据库、文件等任意方式实现。
- 通过什么方式找到想要的ContentProvider?它是通过URI进行定位。URI,就是全局统一定位标志,通过一个结构化的字符串,唯一标识数据源的地址信息,而每个数据源组件都有一个唯一的URI标识。
ContentProvider的scheme已经由Android所规定, scheme为:content://
主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作person表中id为10的记录,可以构建这样的路径:/person/10
要操作person表中id为10的记录的name字段, person/10/name
要操作person表中的所有记录,可以构建这样的路径:/person
要操作xxx表中的记录,可以构建这样的路径:/xxx
当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")
二、ContentProvider的实例
我们还是通过一个实例来了解它吧。利用ContentProvider来对第三方的数据库进行操作。
- 首先我们建一个DBHelper的类继承SQLiteOpenHelper
package com.example.database; import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory; public class DBHelper extends SQLiteOpenHelper{
private static final int VERSION=1;
/**
* 在SQLiteOpenHelper的子类当中,必须有该构造函数
* @param context 上下文对象
* @param name 数据库名称
* @param factory
* @param version 当前数据库的版本,值必须是整数并且是递增的状态
*/
public DBHelper(Context context,String name,CursorFactory factory,int version){
super(context,name,factory,version);
}
public DBHelper(Context context, String name, int version){
this(context,name,null,version);
} public DBHelper(Context context, String name){
this(context,name,VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 数据库首次构造时,会调用该函数,可以在这里构造表、索引,等等
System.out.println("create a database");
//execSQL用于执行SQL语句
db.execSQL("create table notebook(_id integer primary key autoincrement,title varchar(20),content text,time long)"); }
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 如果给定的当前数据库版本高于已有数据库版本,调用该函数
System.out.println("upgrade a database");
} }这一步没什么好解释的,不懂的可以看一看我写的上一篇关于数据库操作的博文。
- 接下来我们就要新建一个MyProvider的类继承ContentProvider
package com.example.database; 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; public class MyProvider extends ContentProvider { private DBHelper dh = null;// 数据库管理对象
private SQLiteDatabase db;//获取其中的数据库
//UriMatcher:Creates the root node of the URI tree.
//按照官方解释,UriMatcher是一颗Uri的树,然后利用addURI()方法往里面添加枝干,通过match()函数来查找枝干。
private static final UriMatcher MATCHER = new UriMatcher(
UriMatcher.NO_MATCH);
//设定匹配码
private static final int NOTEBOOK = 1;
private static final int NOTEBOOKS = 2;
static {
//添加枝干,并给它们加上唯一的匹配码,以方便查找
//如果match()方法匹配content://com.example.database/notebook路径,返回匹配码为1
MATCHER.addURI("com.example.database", "notebook", NOTEBOOKS);
//如果match()方法匹配content://com.example.database/notebook/#路径,返回匹配码为2
//其中#号为通配符。
MATCHER.addURI("com.example.database", "notebook/#", NOTEBOOK);
} @Override
public boolean onCreate() {
// 创建ContentProvider对象时会调用这个函数
dh = new DBHelper(this.getContext(),"note.db");// 数据库操作对象
db = dh.getReadableDatabase();
return false;
} /**
* 查询,返回Cursor
**/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//通过match函数,获取匹配码
switch (MATCHER.match(uri)) {
case NOTEBOOKS:
//返回数据库操作的结果
return db.query("notebook", projection, selection, selectionArgs,
null, null, sortOrder);
case NOTEBOOK:
//因为添加 了id,所以要把id加到where条件中
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
return db.query("notebook", projection, where, selectionArgs, null,
null, sortOrder);
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
} //获取Uri的类型
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch (MATCHER.match(uri)) {
case NOTEBOOKS:
return "com.example.Database.all/notebook"; case NOTEBOOK:
return "com.example.Database.item/notebook"; default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
} //插入数据
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
switch (MATCHER.match(uri)) {
case NOTEBOOKS:
//调用数据库的插入功能
// 特别说一下第二个参数是当title字段为空时,将自动插入一个NULL。
long rowid = db.insert("notebook", "title", values);
Uri insertUri = ContentUris.withAppendedId(uri, rowid);// 得到代表新增记录的Uri
this.getContext().getContentResolver().notifyChange(uri, null);
return insertUri; default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
} //删除数据
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
int count;
switch (MATCHER.match(uri)) {
case NOTEBOOKS:
count = db.delete("notebook", selection, selectionArgs);
return count; case NOTEBOOK:
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = db.delete("notebook", where, selectionArgs);
return count; default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
} //更新数据
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
int count = 0;
switch (MATCHER.match(uri)) {
case NOTEBOOKS:
count = db.update("notebook", values, selection, selectionArgs);
return count;
case NOTEBOOK:
long id = ContentUris.parseId(uri);
String where = "_id=" + id;
if (selection != null && !"".equals(selection)) {
where = selection + " and " + where;
}
count = db.update("notebook", values, where, selectionArgs);
return count;
default:
throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
}
} }因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris 。掌握它们的使用,会便于我们的开发工作。
看上去这个类很像我上次写的DBManager类吧。其实这可以算是一个很简单的数据操作类,关键地方就在于它放在了ContentProvider这个“容器”上,让第三方应用也能访问到己方的数据。所以想要吃透这个组件,只要透彻理解什么是Uri,怎么操作Uri就八九不离十了。 - 最后,不要忘记在配置文件中为ContentProvider注册,因为这也是一个组件,所以无法避免了~
<provider android:name=".MyProvider" android:authorities="com.example.database" />
前面的是你的类名,后面则是关键地方,它是要写在Uri中的,所以不要弄错了。
到此,一个可以供其他应用访问的工程就建好了,接下来我们来写个测试工程来检验效果吧。
三、调用ContentProvider
在使用其他应用为你提供的ContentProvider时,你必须要知道的有两点:(1)它的authorities值,在我这里的是“com.example.database”;(2)数据文件的结构,比如我这里要使用的是数据库中的booknote表,它里面有着(_id,title,content,time)这些字段。只有知道了这些你才能操作ContentProvider。
- 好的,我们先新建一个工程,设置一下布局文件,效果如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:orientation="horizontal" > <LinearLayout
android:layout_width="120dp"
android:layout_height="match_parent"
android:orientation="vertical" > <TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="45dp"
android:text="Title:"
android:textAlignment="center"
android:textSize="25sp" /> <TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="45dp"
android:text="Content:"
android:textAlignment="center"
android:textSize="25sp" />
</LinearLayout> <LinearLayout
android:layout_width="200dp"
android:layout_height="match_parent"
android:orientation="vertical" > <EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25sp" > <requestFocus />
</EditText> <EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25sp" />
</LinearLayout>
</LinearLayout> <ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="196dp" >
</ListView> <LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_weight="0.20" > <Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取全部信息" /> <Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="插入一条信息" /> <Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除一条信息" /> <Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="更新一条信息" /> </LinearLayout> </LinearLayout>activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:layout_width="80dip"
android:layout_height="wrap_content"
android:text="_id"
android:id="@+id/id"
/>
<TextView
android:layout_width="80dip"
android:layout_height="wrap_content"
android:text="title"
android:id="@+id/title"
/> <TextView
android:layout_width="100dip"
android:layout_height="wrap_content"
android:text="content"
android:id="@+id/content"
/> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="time"
android:id="@+id/time"
/> </LinearLayout>item.xml
- 接下来在MainActivity添加代码
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; public class MainActivity extends Activity implements OnClickListener { private ListView listView;
private SimpleCursorAdapter adapter;
private Button button_query, button_insert, button_delete, button_update;
private EditText editText_title, editText_content;
private int CurItem; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); editText_title = (EditText) this.findViewById(R.id.editText1);
editText_content = (EditText) this.findViewById(R.id.editText2); listView = (ListView) this.findViewById(R.id.listView1);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
ListView lView = (ListView) parent;
Cursor data = (Cursor) lView.getItemAtPosition(position);
int _id = data.getInt(data.getColumnIndex("_id"));
Toast.makeText(MainActivity.this, _id + "", Toast.LENGTH_SHORT)
.show();
CurItem = _id;
editText_title.setText(data.getString(data.getColumnIndex("title")));
editText_content.setText(data.getString(data.getColumnIndex("content")));
}
});
button_query = (Button) this.findViewById(R.id.button1);
button_query.setOnClickListener(this);
button_insert = (Button) this.findViewById(R.id.button2);
button_insert.setOnClickListener(this);
button_delete = (Button) this.findViewById(R.id.button3);
button_delete.setOnClickListener(this);
button_update = (Button) this.findViewById(R.id.button4);
button_update.setOnClickListener(this); } @Override
public void onClick(View v) {
//ContentResolver它是ContentProvider提供的一个接口,它能够调用ContentProvider里面的所有方法。
ContentResolver contentResolver;
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:
contentResolver = getContentResolver();
//Uri.parse()能将字符串转换成Uri格式。
Uri selectUri = Uri.parse("content://com.example.database/notebook");
Cursor cursor = contentResolver.query(selectUri, null, null, null,
null);
adapter = new SimpleCursorAdapter(this, R.layout.item, cursor,
new String[] { "_id", "title", "content", "time" },
new int[] { R.id.id, R.id.title, R.id.content, R.id.time },
1);
listView.setAdapter(adapter);
break;
case R.id.button2:
contentResolver = getContentResolver();
Uri insertUri = Uri
.parse("content://com.example.database/notebook");
ContentValues values = new ContentValues();
values.put("title", editText_title.getText().toString());
values.put("content", editText_content.getText().toString());
values.put("time", System.currentTimeMillis());
Uri uri = contentResolver.insert(insertUri, values);
Toast.makeText(this, uri.toString() + "添加完成", Toast.LENGTH_SHORT)
.show();
break;
case R.id.button3:
contentResolver = getContentResolver();
Uri deleteUri = Uri
.parse("content://com.example.database/notebook/"+CurItem);
int d = contentResolver.delete(deleteUri, null,null);
Toast.makeText(this, CurItem+"删除完成", Toast.LENGTH_SHORT)
.show();
break;
case R.id.button4:
contentResolver = getContentResolver();
Uri updateUri = Uri
.parse("content://com.example.database/notebook/"+CurItem);
ContentValues updatevalues = new ContentValues();
updatevalues.put("title", editText_title.getText().toString());
updatevalues.put("content", editText_content.getText().toString());
updatevalues.put("time", System.currentTimeMillis());
int u = contentResolver.update(updateUri, updatevalues,null,null);
Toast.makeText(this, CurItem+"更新完成", Toast.LENGTH_SHORT)
.show();
break;
}
}
}两个应用之间的流程图大概就是这样了(手挫,不要嫌弃~)
- 最后,将两个应用安装好,打开实践一下。那么我们看看运行结果吧
正常运行。那么今天就到此结束,收工了~
四、结束语
理论上来说,数据源组件并没有所谓的生命周期,因为数据源组件的状态并不作为判定进程优先级的依据。所以系统回收进程资源时,并不会将数据源组件的销毁事件告诉开发者。但构造ContentProvider组件时还是会调用onCreate()函数。所以,不要在数据源组件中部署延迟写入等写优化策略,当被系统默默回收时,一些未持久化的数据会丢失。一旦数据源组件被构造出来,就会保持长期运行的状态至其所在的进程被系统回收。所以,也不要在数据源组件中缓存过多的数据,以免占用内存空间。
到此,Android的四大组件已经介绍完毕,他们各有各的的特色和用法,同时也是我们开发时候不可缺少的工具。希望通过这些介绍能让大家体会到Android设计的巧妙和特征,学会正确的使用Android应用的框架。那么接下来可能会带来的是Android的Intent机制,尽请期待~
参考文章:(1)ContentProvider和Uri详解 http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html
(2) ContentProvider浅析 http://bbs.51cto.com/thread-1024108-1.html
(3) 从头学Android之ContentProvider http://blog.csdn.net/worker90/article/details/7016430
下载:demo下载
========================================
作者:cpacm
出处:(http://www.cpacm.net/2015/03/22/Android开发日记(七)——Android四大组件之ContentProvider/)【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider的更多相关文章
- 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行
前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...
- 【Android开发日记】之入门篇(十二)——Android组件间的数据传输
组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...
- 【Android开发日记】之入门篇(七)——Android数据存储(上)
在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也 ...
- 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件
好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...
- 【Android开发日记】之入门篇(十一)——Android的Intent机制
继续我们的Android之路吧.今天我要介绍的是Android的Intent. 对于基于组件的应用开发而言,不仅需要构造和寻找符合需求的组件,更重要的是要将组件有机的连接起来,互联互通交换信息,才能够 ...
- 【Android开发日记】之入门篇(一)——开发环境的搭建
写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的 ...
- 【Android开发日记】之入门篇(八)——Android数据存储(下)
废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基 ...
- 【Android开发日记】之入门篇(五)——Android四大组件之Service
这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的 ...
- 【Android开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver
广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都 ...
随机推荐
- BZOJ2142 礼物 【扩展Lucas】
题目 一年一度的圣诞节快要来到了.每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物.不同的人物在小E 心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多.小E从商店中购买了n件礼物, ...
- NetApp存储方案及巡检命令
一.MCC概述 Clustered Metro Cluster(简称MCC)是Netapp Data Ontap提供的存储双活解决方案,当初的方案是把1个FAS/ V系列双控在数据中心之间拉远形成异地 ...
- mimikazhi Kerberos Modules
Kerberos Modules 1. .#####. mimikatz 2.0 alpha (x64) release "Kiwi en C" (Oct 9201500 ...
- linux中man 2与man 3区别
1.Standard commands (标准命令)2.System calls (系统调用)3.Library functions (库函数)4.Special devices (设备说明)5.Fi ...
- Android 65535 问题与 MultiDex分包
Android Multidex 遇到的问题 http://blog.csdn.net/wangbaochu/article/details/51178881 Android 使用android-su ...
- 《剑指offer》— JavaScript(20)包含min函数的栈
包含min函数的栈 题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. 实现代码 var stack = []; function push(node) { stack. ...
- 使用EntitysCodeGenerate
http://bbs.csdn.net/topics/360256700 public DataSet xxx(DateTime start, DateTime end, string type) ...
- Java入门:绘制简单图形
在上一节,我们学习了如何使用swing和awt工具创建一个空的窗口,本节学习如何绘制简单图形. 基本绘图介绍 Java中绘制基本图形,可以使用Java类库中的Graphics类,此类位于java.aw ...
- nginx启用stream日志配置文件
主配置文件/etc/nginx/nginx.conf增加内容: stream { log_format proxy '$remote_addr [$time_local] ' '$protocol $ ...
- 使用Githubdesktop管理Eclipse项目
使用Githubdesktop管理Eclipse项目 觉得有用的话,欢迎一起讨论相互学习~[Follow] 方案 使用Eclipse创建项目,使用githubdesktop进行管理 项目右键, Tea ...