一、Content provider概述
Content provider即数据模型,在android中作为数据存放的容器,包含增删改查,看上去和sql有点类似,但它屏蔽了数据存储的细节,更加透明化,用户只需要关心uri即可,并且它支持不同Android应用程序之间的数据共享,操作起来更加方便
每个类型的Content provider只有一个单独的实例,用于多个进程ContentResolver类之间通信
数据模型类似于sql的存放方式,每一排代表一条记录,每一列代表类型
每一个content通过uri保存数据集,所有为provider提供的uri都是
content://com.xxxx.xxx/xxx/xxx
Android开发提供了各种URI,uri可以作为参数被访问
请求多条记录只需要指定uri的内容即可
二、预定义Content provider
安卓系统对常见的内容提供了预定的Content provider,例如声音,视频,联系人等
1.查询
要实现查询操作,先标识uri,再提供查询字段名称和数据类型,如果是特定的值,还需要提供id值
查询方法通过ContentResolver.query或者Activity.managedQuey方法,返回值为Cursor对象,即为查找内容的一个集合
public final Cursor query (Uri uri,String[] projection,String[] selection,String[] selectArgs,String sortOrder)
uri是provider的uri
projection是返回的数据列名称。null表示返回全部列,可以使用android预定义的常量,如果是自定义常量需要将名字定义在string数组中
selection是返回的行的指定,类似于WHERE语句。null表示全部返回行、
selectionArgs是替换掉selection参数中的问题
Cursor cursor = contentResolver
.query(android.provider.ContactsContract.Contacts.CONTENT_URI
,new String[]{ContactsContract.Contacts._ID}
,ContactsContract.Contacts.DISPLAY_NAME + "=?"
,new String[]{"张三"}
,null)
sortOrder即为排序方式,null表示默认返回的可能为无序的
2.增加记录
通过inset方法
Cursor cursor = contentResolver.insert(Uri uri,ContentValues values)
ContentValues是通过k-v键值对存放列名-值,返回的对象即为修改后的URI
3.增加新值
调用以byte数组作为参数的ContentValues.put()方法向表格中增加少量的二进制数据,适用于小图片视频等。如果是大量的二进制数据,需要保存代表数据的content:URI到表格,使用文件URI调用ContentResolver.openOutputStream()方法
4.更新记录
Cursor cursor = contentResolver.update(Uri uri,ContentValues values,String where,String[] selectionArgs)
values为键值对,where过滤,selectionArgs替换问好
5.删除记录
Cursor cursor = contentResolver.delete(Uri uri,String where,String[] selectionArgs)
6.查询电话实例
package com.thundersoft.session6;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
public class ProviderActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
setContentView(linearLayout);
TextView textView = new TextView(this);
String[] columns = {ContactsContract.Contacts._ID,ContactsContract.Contacts.DISPLAY_NAME};
StringBuilder stringBuilder = new StringBuilder();
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, columns, null, null, null);
int index0 = cursor.getColumnIndex(columns[0]);
int index1 = cursor.getColumnIndex(columns[1]);
for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
int index0i = cursor.getInt(index0);
String index1s = cursor.getString(index1);
stringBuilder.append(index0i + ": "+ index1s + "\n");
}
cursor.close();
textView.setText(stringBuilder.toString());
linearLayout.addView(textView);
}
}
通过查到cursor,获得索引,接着将索引值给cursor获取,遍历这个操作
不要忘记了在SVD中设置权限
这里是最简单的查询,通过columns数组只需要对一个表进行查询
如果需要同时将电话号记录下来,需要用到另外一张预定义的Provider表
即ContactsContract.CommonDataKinds.Phone.CONTENT_URI
通过这张表和上述代码中的表建立联合查询,代码如下
package com.thundersoft.session6;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
public class Provider2Activity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
setContentView(linearLayout);
TextView textView = new TextView(this);
String[] columns = {ContactsContract.Contacts._ID,ContactsContract.Contacts.DISPLAY_NAME
,ContactsContract.CommonDataKinds.Phone.NUMBER,ContactsContract.CommonDataKinds.Phone.CONTACT_ID};
StringBuilder stringBuilder = new StringBuilder();
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while (cursor.moveToNext()){
int idIndex = cursor.getColumnIndex(columns[0]);
int nameIndex = cursor.getColumnIndex(columns[1]);
int id = cursor.getInt(idIndex);
String name = cursor.getString(nameIndex);
Cursor phone = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, columns[3] + "=" + id, null, null);
while (phone.moveToNext()){
int phoneNumIndex = phone.getColumnIndex(columns[2]);
String phoneNum = phone.getString(phoneNumIndex);
stringBuilder.append(name + ": " + phoneNum + "\n");
}
}
cursor.close();
textView.setText(stringBuilder.toString());
linearLayout.addView(textView);
}
}
这里查询的结果就包含了电话号码信息
三、自定义Content provider
如果需要自定义Content Provider,可以通过Android文件的存储方式,或者是SQLite数据库保存数据
Android提供SQLiteOpenHelper类帮忙创建数据库,SQLiteDatabase类帮忙管理数据库
访问数据通过继承ContentProvider类,同时还要再清单文件里声明
1.继承ContentProivder类
需要实现方法如下
使用ContentProivder的Cursor类来共享数据,六个方法的说明如下
onCreate:初始化provider
insert/delete/update/query:curd
getType:返回数据到MIME类型
查找必须要返回Cursor对象,用于遍历,它自身是一个接口,SQLiteCursor可以遍历存储在SQLite数据库中的数据
2.声明Content Provider
声明了对于Android系统才是可见的
Name属性是ContentProvider类子类的完整名称
authorities属性是provider定义的content:URI中authority部分
<provider
android:authorities="com.thundersoft.session6.providerActivity"
android:name=".Provider2Activity"/
其它的provider属性能设置读写数据的权限,现实的图标文本、启用禁用provider等
如果要同步多个同时运行的ContentProvider,需要设置multiprocess为true
这允许各个客户端进程传进一个provider实例,避免IPC
五、实践
1.要求
实现一个登录页面,提供记住密码和用户名功能,登陆后显示列表视图必须包含登录用户名,且点击列表会显示点击那一项
登录后主界面列表选择第一个选项后,显示一组图片,图片加载完成前需要显示进度条,加载完成后,长按某一个图片弹出提示框是否删除,如果确定就不再显示该图片
登录后主界面列表选择第二个选项后,跳转到另一个Activity,并且传过去一个字符串,在新的Activity中包含2个Fragment,左边的Fragment类似手机设置,含一个亮度设置选项,点击后右边的Fragment显示具体的亮度设置界面
登录后主界面列表选择第三个选项后,跳转到另一个Activity,并且传过去int/byte/Serializable等多种类型的数据,在新的Activity中显示传入的数据,检查是否所传所有类型的数据都正确接收,同时在Activity启动的时候开始监听android.net.conn.CONNECTIVITY_CHANGE,Activity退出后不再监听,当网络状态改变的时候提示用户网络状态改变情况。
登录后主界面列选择第四个选项后,跳转到另一个Activity,运用本章学习的资源、样式等知识,实现Activity中显示类似自己手机设置列表的效果(跟进到自己手机的设置界面一样)
(以上是Android 六 当中的实践要求)
登录后主界面列表选择第五个选项后,跳转到另一个Activity显示记账本,其含一个 记账项输入、金额输入、添加按钮和一个列表,添加记账功能往自定义Content Provider写入,数据可以存储在自定义数据库,或者xml文件里,或者最简单的存在列表变量里面;
2.代码实现
intent的就写在这里了
bill.xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="记账本"
android:gravity="center"
android:textSize="30dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="20dp"
android:text="请输入金额:"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="7"
android:id="@+id/money"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:layout_weight="1"
android:id="@+id/submit"/>
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bill"/>
</LinearLayout>
</LinearLayout>
通过点击获取edittext里的内容
DBManager.java
package com.thundersoft.login;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBManager extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "michilay.db";
private static final String CREATE_TABLE =
"create table michilay (time varchar(60),money varchar(20));";
public DBManager(Context context) {
super(context, DATABASE_NAME, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
DBManager继承SQLiteOpenHelper,定义了数据库的名称michilay.db以及表的名称和结构create table michilay (time varchar(60),money varchar(20));
BillProvider.java
package com.thundersoft.login;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BillProvider extends ContentProvider {
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static String authority = "com.thundersoft.login.billProvider";
private static final int USER_CODE = 1;
private DBManager myDataBase;
private SQLiteDatabase db;
static {
MATCHER.addURI(authority, null, USER_CODE);
}
@Override
public boolean onCreate() {
myDataBase = new DBManager(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
db = myDataBase.getWritableDatabase();
return db.query("michilay", new String[]{"time","money"}, null, null, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
db = myDataBase.getWritableDatabase();
db.insert("michilay",null,values);
db.close();
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
这里自定义了一个ContentProvider,只对provider的查找和增加进行了编写
query方法返回的是一个cursor,起初想使用java集合来存储数据保存在cursor,很难行得通,比较常用的方法就是结合SQLite数据库的查询语句,返回的刚好是一个cursor(Android给你的能不好用吗)
insert方法返回的是一个uri,不需要在意,只需要在这个方法里对数据库进行增加操作,insert方法也是Android封装好的
注意操作时候db = myDataBase.getWritableDatabase();别忘了
使用了自定义Provider后一定要进行声明
<provider
android:authorities="com.thundersoft.login.billProvider"
android:name="com.thundersoft.login.BillProvider"
android:exported="true"/>
name为类的名字,authorities为uri,小写开头
BillActivity.java
package com.thundersoft.login;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import androidx.annotation.Nullable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class BillActivity extends Activity {
private final String[] columns = {"time","money"};
private Uri uri = Uri.parse("content://com.thundersoft.login.billProvider");
private List<String> list = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bill);
EditText money = findViewById(R.id.money);
Button button = findViewById(R.id.submit);
ListView bill= findViewById(R.id.bill);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String s = money.getText().toString();
list.clear();
if (s != ""){
ContentResolver resolver = getContentResolver();
ContentValues contentValues = new ContentValues();
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
contentValues.put("time",formatter.format(date));
contentValues.put("money",s);
Log.i("bill","target------------->" + contentValues.get("time") +"||"+ contentValues.get("money"));
resolver.insert(uri,contentValues);
Cursor cursor = resolver.query(uri, null, null, null, null);
while (cursor.moveToNext()){
int columnIndex = cursor.getColumnIndex(columns[0]);
int columnIndex1 = cursor.getColumnIndex(columns[1]);
String string = cursor.getString(columnIndex);
String string1 = cursor.getString(columnIndex1);
list.add(string + " 花费 " + string1);
}
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(BillActivity.this, android.R.layout.simple_list_item_1, list);
bill.setAdapter(arrayAdapter);
cursor.close();
}
}
});
}
}
主程序中,每次点击都要进行list.clear操作,因为setadapter会将adapter中的list给加在后面而不是替换
通过对edittext和data类获取数据放在ContentValues对象中,以k-v的形式保存,k为数据库的属性名,v为值
Resolver对象将values传入uri中进行插入操作,调用自定义provider中的insert操作,传过去values值
query操作也是调用的自定义provider中的query操作,将结果返回并显示
注意每次测试之后需要删除程序才会删除数据库中的数据
3.实现效果
|