建立一个项目,在项目中利用SQLiteOpenHelper建立一个名称为account的数据库,并在数据库中建立一张名为info的表。android
public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context) { super(context, "account.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))"); db.execSQL("insert into info('name','money') values ('张三','2000')"); db.execSQL("insert into info ('name','money') values ('李四','5000')"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){ } }
在MainActivity中须要调用如下代码才能建立数据库:数据库
private MyOpenHelper myOpenHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myOpenHelper = new MyOpenHelper(this); myOpenHelper.getReadableDatabase(); }
运行程序,咱们利用DDMS中的FileExplorer查看咱们的数据库文件:
api
从上图能够看到建立了一个account.db的数据库,查看文件权限能够看到,对其余用户没有权限。安全
SQLiteDatabase有一个静态的方法,能够直接加载某个路径下的数据库文件。咱们再建立一个项目工程,在这个工程中用这个静态方法访问account.db。服务器
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SQLiteDatabase db = SQLiteDatabase.openDatabase("/data/data/com.itheima.transation/databases/account.db", null, SQLiteDatabase.OPEN_READWRITE); Cursor cursor = db.query("info", null, null, null, null, null, null); if (cursor!=null && cursor.getCount()>0){ while (cursor.moveToNext()) { String name=cursor.getString(cursor.getColumnIndex("name")); String money=cursor.getString(cursor.getColumnIndex("money")); System.out.println("name"+name + "money"+money); } } }
运行结果:
ide
以上结果说明咱们使用SQLiteDatabase.openDatabase()方法打开数据库须要有权限才可以访问。咱们改变account.db的访问权限,使其余用户也能访问该文件:工具
再次运行程序,访问account.db数据库,这时候就可以访问到数据库中的数据了。查看日志输出以下:布局
这种方式虽然可以访问到其余应用程序的数据库,可是这种方式须要手动改变其余应用程序数据库的访问权限,而且这是一种很是不安全的操做,若是改变应用数据库的访问权限,其余程序很容易修改数据库的内容。那么如何才能访问其余应用程序的数据库呢?Google给咱们提供了Android中另外一个组件ContentProvider内容提供者来解决这个问题。学习
ContentProvider(内容提供者)是Android中的四大组件之一,在通常的开发中,可能使用的比较少。this
ContentProvider为不一样的软件之间数据共享,提供统了一套接口。也就是说,若是咱们想让其余的应用使用咱们本身程序内的数据,就可使用ContentProvider定义一组对外开放的接口,从而使得其余的应用可使用我们应用的文件、数据库内存储的信息。
固然,本身开发的应用须要给其余应用共享信息的需求可能比较少见,可是在Android系统中,不少系统自带应用,好比联系人信息,图片库,音频库等应用,为了对其余应用暴露数据,因此就使用了ContentProvider机制。因此,学习ContentProvider的基本使用,在遇到获取联系人信息,图片库,音频库等需求的时候,才能更好的开发。
内容提供者定义了一组对外开放的接口,使其余应用能够访问本身的应用的数据库内容,下图是外部应用访问系统联系人应用数据库的原理图:
从上图能够看出,普通外部应用不能够直接访问私有的数据库,只能经过内容提供者访问私有数据库,内容提供者处于应用内部,在内容提供者中定义了一些访问路径匹配等操做,当外部应用经过路径访问私有数据库时,内容提供者根据路径匹配出具体的操做,将私有数据返回给外部应用。
(a)建立一个类继承ContentProvider,实现其中的方法
public class AccountProvider extends ContentProvider { //当内容提供者建立的时候调用 @Override public boolean onCreate() { return true; } //用于查询数据库数据,返回值是Cursor即结果的数据集 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { } //返回MIME类型的字符串,若是返回null,说明没有数据类型 @Override public String getType(Uri uri) { return null; } //用于向数据库插入记录 @Override public Uri insert(Uri uri, ContentValues values) { } //用于删除数据库记录 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { } //用于更新数据库记录 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { } }
(b)清单文件中配置内容提供者
<provider android:name="com.itheima.transation.AccountProvider" //设置主机名 android:authorities="com.itheima.account.provider" > </provider>
(c)定义路径匹配规则
//定义当路径匹配成功后对指定的组件返回的返回码 private static final int QUERYSUCESS = 1; private static final int ADDSUCESS = 2; private static final int DELSUCESS = 3; private static final int UPDATESUCESS = 4; //建立Uri的匹配对象 static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); static { //增长匹配规则,参数1为主机名,参数2为路径,参数3为当路径匹配成功后对指定的组件返回的返回码,必须是正数。该主机名必须和清单文件中配置的主机名一致。 matcher.addURI("com.itheima.account.provider", "query", QUERYSUCESS); matcher.addURI("com.itheima.account.provider", "add", ADDSUCESS); matcher.addURI("com.itheima.account.provider", "delete", DELSUCESS); matcher.addURI("com.itheima.account.provider", "update", UPDATESUCESS); }
注意:主机名必须和清单文件中配置的主机名一致。
Uri的组成规则:协议名://主机名或authority/路径/ID。Uri的组成能够参考下图:
(d)建立SQLiteOpenHelper
public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context) { super(context, "account.db", null, 1); } @Override public void onCreate(SQLiteDatabase db){ db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))"); db.execSQL("insert into info ('name','money') values ('张三','2000')"); db.execSQL("insert into info ('name','money') values ('李四','5000')"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
获取数据库,在ContentProvider中实现覆盖的增删改查方法:
@Override public boolean onCreate() { //在onCreate()方法中建立SQLiteOpenHelper对象 helper = new MyOpenHelper(getContext()); return true; } //内容提供者的查询方法 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //匹配路径,返回值是一个int类型的返回码,若是匹配不成功,返回-1 int code = matcher.match(uri); if (code == QUERYSUCESS) { SQLiteDatabase db = helper.getReadableDatabase(); //调用SQLiteDatabase对象的query()查询数据 Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder); return cursor; } else { throw new IllegalArgumentException("路径不匹配 请检查"); } } @Override public Uri insert(Uri uri, ContentValues values) { int code = matcher.match(uri); if (code == ADDSUCESS) { SQLiteDatabase db = helper.getReadableDatabase(); long insert = db.insert("info", null, values); return Uri.parse("com.itheima.account.provider" + insert); } else { throw new IllegalArgumentException("路径不匹配 请检查"); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int code = matcher.match(uri); if (code == DELSUCESS) { SQLiteDatabase db = helper.getReadableDatabase(); int delete = db.delete("info", selection, selectionArgs); return delete; } else { throw new IllegalArgumentException("路径不匹配 请检查"); } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int code = matcher.match(uri); if (code == UPDATESUCESS) { SQLiteDatabase db = helper.getReadableDatabase(); int update = db.update("info", values, selection, selectionArgs); return update; } else { throw new IllegalArgumentException("路径不匹配 请检查"); } }
内容提供者定义好了,那么如何在其余应用中访问内容提供者提供的数据呢?这时候须要用到ContentResolver来访问。
建立另一个项目,在项目中利用ContentResolver来访问其余应用的内容提供者。
界面布局:
<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" tools:context=".MainActivity" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="add" android:text="add" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="del" android:text="del" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="update" android:text="update" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="find" android:text="find" /> </LinearLayout>
在MainActivity中经过Context获取ContentResolver来访问私有数据库:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } public void add(View v){ //定义访问路径 Uri uri = Uri.parse("content://com.itheima.account.provider/add"); //建立一系列ContentResolver可以处理的数据,其值为key-value的结构,key必须和数据库字段一致 ContentValues values = new ContentValues(); values.put("name", "zhaoqi"); values.put("money", "1000"); //经过Context对象获取ContentResolver对象,调用ContentResolver对象的insert()方法插入数据,参数1为访问uri,参数2为插入的数据 Uri insert = getContentResolver().insert(uri, values); System.out.println("uri--"+insert.toString()); } public void del(View v){ Uri uri = Uri.parse("content://com.itheima.account.provider/delete"); int delete = getContentResolver().delete(uri, "name=?", new String[]{"zhaoqi"}); System.out.println("delete=="+delete); } public void update(View v){ Uri uri = Uri.parse("content://com.itheima.account.provider/update"); ContentValues values = new ContentValues(); values.put("money", "20000"); int update = getContentResolver().update(uri, values, "name=?", new String[]{"zhaoqi"}); System.out.println("update--"+update); } public void find(View v){ Uri uri = Uri.parse("content://com.itheima.account.provider/query"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor!=null && cursor.getCount()>0){ while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex("name")); String money = cursor.getString(cursor.getColumnIndex("money")); System.out.println("name"+name + "money"+money); } } } }
运行后,发现其余应用就能够经过内容提供者来访问私有数据库了。
注意:匹配路径后面是能够携带数字的,即Uri中的ID,Android中有一个方便的api来获取这个值:
int id = (int) ContentUris.parseId(uri);
本案例经过内容提供者获取系统短信数据库中的短信数据,将这些短信数据经过XML序列化到文件中。
系统短信数据的位置存放在系统短信应用包名下的databases下,以下图:
导出数据库,利用SQLiteStudio工具查看数据中的数据:
要备份短信数据,须要备份短信数据库中sms表中的短信的address、date、sms三个字段的数据。此外当经过ContentResolver来查询的时候须要提供访问的Uri,那么这个Uri如何获取呢?能够查看系统短信的源码来获得访问Uri,由于短信源码中确定定义了供其余应用访问的ContentProvider。
首先咱们找到源码目录,在清单文件中查找:
从上图能够得知短信Provider的主机名。
接着找到源码中SmsProvider这个类,查看Uri后面的path:
从上图能够得知访问短信数据库的Uri的权限是sms,查看匹配规则,第一个匹配规则的路径为null,表明查询全部数据,这样咱们就知道查询短信数据库的Uri为content://sms/。
接下来咱们经过ContentResolver来查询短信数据库并将数据序列化到文件中:
public void click(View v){ try { //建立一个xml序列化对象 XmlSerializer serializer = Xml.newSerializer(); //备份文件存储位置 File file = new File(Environment.getExternalStorageDirectory().getPath(),"smsback.xml"); FileOutputStream fos = new FileOutputStream(file); serializer.setOutput(fos, "utf-8"); serializer.startDocument("utf-8", true); serializer.startTag(null, "smss"); Uri uri = Uri.parse("content://sms/"); //经过ContentResolver获取数据库中的内容 Cursor cursor = getContentResolver().query(uri, new String[]{"address","date","body"}, null, null, null); while(cursor.moveToNext()){ String address = cursor.getString(0); String date = cursor.getString(1); String body = cursor.getString(2); serializer.startTag(null,"sms"); serializer.startTag(null, "address"); serializer.text(address); serializer.endTag(null, "address"); serializer.startTag(null, "date"); serializer.text(date); serializer.endTag(null, "date"); serializer.startTag(null, "body"); serializer.text(body); serializer.endTag(null, "body"); serializer.endTag(null, "sms"); } cursor.close(); serializer.endTag(null, "smss"); serializer.endDocument(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
运行结果:
导出到电脑查看:
利用ContentResolver向数据库中插入一条短信。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v){ Uri uri = Uri.parse("content://sms/"); ContentValues values = new ContentValues(); values.put("address", "95553"); values.put("body", "您的余额为0.00元....."); values.put("date", System.currentTimeMillis()); getContentResolver().insert(uri, values); } }
加入权限:
<uses-permission android:name="android.permission.WRITE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" />
运行结果:
首先咱们查看手机系统联系人和联系人数据库。
将contact2.db导出到电脑,使用SQLiteStudio查看数据库,查看data表:
经过data表,能够发现data1字段存储的是联系人的信息,那么如何区这些信息是哪一个联系人的呢?经过观察能够发现raw_contact_id表示的就是属于某个联系人,而后查看raw_contacts表:
经过查看raw_contacts表,发现contact_id就与刚才data表中的raw_contact_id是对应的。接下来须要知道data表中data1字段的信息是姓名呢,仍是电话或者其余的类型,这时候就须要经过minetype_id来区分信息的类型,以下图:
接着,到mimetypes表中查找对应的mimetype_id是什么类型,以下图:
从上图的mimetypes表能够看出,id为5表示手机号码,6表示姓名,1表示邮箱。
接下来咱们就经过这几张表查询出联系人信息:
(a)raw_contacts表:能够获得全部联系人的id
contact_id:联系人id
(b)data表:联系人的具体信息,一个信息占一行
data1:信息的具体内容
raw_contact_id:联系人id,描述信息属于哪一个联系人
mimetype_id:描述信息是属于什么类型
(c)mimetypes表:经过mimetype_id到该表查看具体类型
查询联系人的步骤:
1. 经过raw_contacts获取联系人id,表查询一共有多少个联系人;
2. 经过联系人id获取data表中的data1字段和mimetypes字段;
3. 经过mimetypes表查询类型。
获得了如何查询到联系人后,接下来使用ContentResolver来查询联系人信息,那么问题来了,如何知道访问的Uri呢?这又须要经过查看联系人源码才能获得Uri。查看ContactsProvider源码清单文件以下图:
在Activity经过raw_contacts表查询全部的contact_id即一共多少联系人:
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null); while (cursor.moveToNext()) { String contact_id = cursor.getString(0); System.out.println("contact_id====="+ contact_id); }
运行结果:
运行出错,查看日志提示须要在清单文件中加入权限:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
再次运行程序,运行结果获取到了联系人id:
经过data表查询data一、mimetype_id字段:
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); Uri datauri = Uri.parse("content://com.android.contacts/data"); Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null); while (cursor.moveToNext()) { String contact_id = cursor.getString(0); System.out.println("contact_id====="+ contact_id); Cursor dataCursor = getContentResolver().query(datauri, new String[] {"data1","mimetype_id"}, "raw_contact_id=?", new String[]{contact_id}, null); while (dataCursor.moveToNext()) { String data1 = dataCursor.getString(0); String mimetype_id = dataCursor.getString(1); System.out.println("data1 = "+data1+"-----mimetype_id = "+mimetype_id); } }
运行结果报错,提示无效的列mimetype_id:
为何会报无效的列mimetype_id呢?这是由于系统的ContentProvider在作查询的时候不是直接查询的mimetype_id这个字段,而是查询view_data这个视图,这个视图将data表和mimetypes表联系起来,因此查询的字段应该是mimetypes表中的mimetype字段,以下图:
咱们将查询的字段改为mimetype,再次查询,运行结果以下:
拿到联系人信息后,判断mimetype的类型:
if ("vnd.android.cursor.item/email_v2".equals(mimetype)) { System.out.println("邮件data:"+data1); }else if("vnd.android.cursor.item/name".equals(mimetype)){ System.out.println("姓名data:"+data1); }else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ System.out.println("电话号码data:"+data1); }
运行结果以下:
将数据封装成对象,定义联系人实体bean:
public class Contact { private String id; private String name; private String phone; private String email; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "Contact [id=" + id + ", name=" + name + ", phone=" + phone + ", email=" + email + "]"; } }
查询联系人数据而且将数据封装,而且建立联系人对象集合用来存储联系人:
//建立保存联系人的集合 List<Contact> contactLists = new ArrayList<Contact>(); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); Uri datauri = Uri.parse("content://com.android.contacts/data"); Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null); while(cursor.moveToNext()){ String contact_id = cursor.getString(0); //必定要判断contact_id是否为空,由于删除联系人后,联系人的数据还会存在数据库中,可是contact_id为null if (contact_id != null) { //建立联系人对象 Contact contact = new Contact(); //设置联系人id contact.setId(contact_id); Cursor dataCursor = getContentResolver().query(datauri, new String[]{"data1","mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null); while(dataCursor.moveToNext()){ String data1 = dataCursor.getString(0); String mimetype = dataCursor.getString(1); if ("vnd.android.cursor.item/email_v2".equals(mimetype)) { //设置联系人email contact.setEmail(data1); }else if("vnd.android.cursor.item/name".equals(mimetype)){ //设置联系人的姓名 contact.setName(data1); }else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ //设置联系人电话 contact.setPhone(data1); } } //将联系人添加到集合中 contactLists.add(contact); } }
注意:第9行,判断contact_id是否为空,由于删除联系人后,联系人的数据还会存在数据库中,可是contact_id为null;
谷歌为什么这样设计呢,是因为,在国外,联系人的数据都会上传到服务器,若是删除联系人,那么联系人的数据data1字段都须要删除,那么当同步的时候,就很是的麻烦。
输入姓名,电话,邮箱,点击“还原”按钮,将联系人数据插入到数据库中。
界面布局:
<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" tools:context=".MainActivity" > <EditText android:id="@+id/et_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入姓名" /> <EditText android:id="@+id/et_phone" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入电话号码" /> <EditText android:id="@+id/et_email" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入email" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="click" android:text="还原" /> </LinearLayout>
MainActivity中实现插入联系人,插入数据以前先查询raw_contacts表,查询一共多少联系人数据,肯定新的联系人的id(查询值+1),而后向raw_contacts表中插入新的联系人id,最后向data表中插入数据(raw_contact_id,mimetype_id,data1等)。
public class MainActivity extends Activity { private EditText et_name; private EditText et_phone; private EditText et_email; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_name = (EditText) findViewById(R.id.et_name); et_phone = (EditText) findViewById(R.id.et_phone); et_email = (EditText) findViewById(R.id.et_email); } public void click(View v) { String email = et_email.getText().toString().trim(); String name = et_name.getText().toString().trim(); String phone = et_phone.getText().toString().trim(); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts"); Uri datauri = Uri.parse("content://com.android.contacts/data"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); int count = cursor.getCount(); int contact_id = count + 1; ContentValues values = new ContentValues(); values.put("contact_id", contact_id); getContentResolver().insert(uri, values); ContentValues nameValues = new ContentValues(); nameValues.put("raw_contact_id", contact_id); nameValues.put("mimetype", "vnd.android.cursor.item/name"); nameValues.put("data1", name); getContentResolver().insert(datauri, nameValues); ContentValues emailValues = new ContentValues(); emailValues.put("raw_contact_id", contact_id); emailValues.put("mimetype", "vnd.android.cursor.item/email_v2"); emailValues.put("data1", email); getContentResolver().insert(datauri, emailValues); ContentValues phoneValues = new ContentValues(); phoneValues.put("raw_contact_id", contact_id); phoneValues.put("mimetype", "vnd.android.cursor.item/phone_v2"); phoneValues.put("data1", phone); getContentResolver().insert(datauri, phoneValues); } }
利用ContentResolver能够获取内容提供者提供的其余应用是私有数据库信息,可是若是有这样的需求,当其余应用的私有数据库发生改变时,咱们的应用可以收到数据变化的通知,这里就用到了ContentObserver内容观察者来实现。
接下来,利用内容观察者实现短信数据库变化:
public class MainActivity extends Activity { private Uri uri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); uri = Uri.parse("content://sms/"); //调用getContentResolver().registerContentObserver()方法注册内容观察者,参数1表示须要观察的Uri,参数2表当为false表示观察精确的Uri,true表示指定Uri或者指定Uri下的全部Uri都能匹配 getContentResolver().registerContentObserver(uri, true, new MyObserver(new Handler())); } //定义内容观察者,该类继承ContentObserver private class MyObserver extends ContentObserver{ public MyObserver(Handler handler) { super(handler); } //当内容发生变化的时候调用 @Override public void onChange(boolean selfChange) { Cursor cursor = getContentResolver().query(uri, new String[]{"address","body"}, null, null, "date desc"); cursor.moveToFirst(); String address = cursor.getString(0); String body = cursor.getString(1); System.out.println("body:"+body+"address:"+address); super.onChange(selfChange); } } }
模拟器中模拟接收一条短信:
查看日志打印,获取到了最新的短信:
注意,在自定义的内容提供者中,咱们须要在改变数据库数据后通知内容观察者数据发生改变:
ContentResolver cr = getContext().getContentResolver(); //发出通知,全部注册在这个uri上的内容观察者均可以收到通知 cr.notifyChange(uri, null);