注意:在ContentProvider里面写对数据库增删改查的时候,千万不能 db.close(); cursor.close(); 等操作,不然其他应用访问不到数据,也没有必要写isOpen();
ContentProviderServer应用-->定义 MySqliteOpenHeper 数据库帮助操作类(创建数据库,创建表,初始化数据)
定义两张表,dog表 cat表
package liudeli.cp.server.cp; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MySqliteOpenHeper extends SQLiteOpenHelper { private static final String DB_NAME = "dogDatabase.db"; private static final int VERSON = 1; /** * 定义单例模式 懒汉式 */ private static MySqliteOpenHeper mySqliteOpenHeper; public static MySqliteOpenHeper getInstance(Context context) { if (null == mySqliteOpenHeper) { synchronized (MySqliteOpenHeper.class) { if (null == mySqliteOpenHeper) { mySqliteOpenHeper = new MySqliteOpenHeper(context, DB_NAME, null,VERSON); } } } return mySqliteOpenHeper; } private MySqliteOpenHeper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { // 两张表字段一模一样 db.execSQL("create table dog(_id integer primary key autoincrement, name text, age integer);"); db.execSQL("create table cat(_id integer primary key autoincrement, name text, age integer);"); initDogTableData(db); } private void initDogTableData(SQLiteDatabase db) { ContentValues contentValues = new ContentValues(); contentValues.put("name", "阿黄"); contentValues.put("age", 88); db.insert("dog", null, contentValues); contentValues.put("name", "阿大黄"); contentValues.put("age", 22); db.insert("dog", null, contentValues); contentValues.put("name", "小黄"); contentValues.put("age", 23); db.insert("dog", null, contentValues); contentValues.put("name", "小白"); contentValues.put("age", 34); db.insert("cat", null, contentValues); contentValues.put("name", "花白"); contentValues.put("age", 34); db.insert("cat", null, contentValues); contentValues.put("name", "白白"); contentValues.put("age", 34); db.insert("cat", null, contentValues); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
ContentProviderServer应用-->定义 MyContentProvider 对数据库增删改查操作
为什么要有 UriMatcher ?
答:UriMatcher的出现可以解决被访问的细节,例如:我有cat表 和 dog表,我就想把cat表给暴露出去 并且只暴露cat表的:(ID查询,全部查询,ID修改,ID删除)
package liudeli.cp.server.cp; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import java.util.regex.Matcher; public class MyContentProvider extends ContentProvider { private final String TAG = MyContentProvider.class.getSimpleName(); /** * 定义UriMatcher */ private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 授权 抽取出来定义 private static final String authority = "autho.prov.cp.MyContentProvider"; /** * 定义给其他应用程序访问的路径 */ private static final String CAT = "cat"; // 不指定ID private static final String CAT_ID = "cat/#"; // 指定ID /** * 定义Code,此Code是给当前内容提供者进行switch判断使用的 */ private static final int CAT_CODE = 100; private static final int CAT_ID_CODE = 200; /** * 在静态代码块中添加 内容提供者(ContentProvider) 指定好的Uri */ static { /** * 参数一:授权 需和AndroidManifest.xml 对外暴露的授权一致 * 参数二:路径 应该来说是拼接的路径,例如在Web www.baidu.com/路径 -- www.baidu.com/login */ uriMatcher.addURI(authority, CAT, CAT_CODE); // 非ID操作 uriMatcher.addURI(authority, CAT_ID, CAT_ID_CODE); // 指定ID操作 } /** * 初始化数据库错误的示范: (不仅仅是在ContentProvider不能这样,在其他的组件也不能这样) * MySqliteOpenHeper.getInstance(getContext()); 还没有执行 onCreate 是没有getContext的,会报错 * MySqliteOpenHeper.getInstance(this); 还没有执行 onCreate初始化 是没有getContext的,会报错 */ // private MySqliteOpenHeper mySqliteOpenHeper = MySqliteOpenHeper.getInstance(getContext()); private MySqliteOpenHeper mySqliteOpenHeper; /** * 只要在AndroidManifest.xml中配置了provider组件 * 应用打开后,会自动启动此方法 * @return */ @Override public boolean onCreate() { Log.d(TAG, "onCreate()"); mySqliteOpenHeper = MySqliteOpenHeper.getInstance(getContext()); return false; } /** * 查询 * @param uri 其他应用传递过来的Uri * @param projection 其他应用传递过来的查询列 * @param selection 其他应用传递过来的查询条件 * @param selectionArgs 其他应用传递过来的查询条件参数值 * @param sortOrder 其他应用传递过来的排序 * @return */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = mySqliteOpenHeper.getReadableDatabase(); Cursor cursor = null; /** * uriMatcher.match(uri); 匹配Uri * 匹配其他应用传递过来的Uri,与我内容提供者addURI添加好的Uir进行判断 * 判断到 如果 传递过来的Uir 和我addURI添加好的Uir一致才做处理 */ int code = uriMatcher.match(uri); switch (code) { case CAT_CODE: /** * 查询全部 */ cursor = db.query("cat", // 表名 projection, // 查询的列 null, // selection 查询的条件 xxx=? null, // selectionArgs 查询条件的值 null, // groupBy 分组 null, // having 分组过滤条件 "_id desc"); // orderBy 排序 --> 倒序 break; case CAT_ID_CODE: /** * ID查询 * 判断其他应用程序,传递过来的查询条件,是否为空 * 如果为空:就获取Uri携带的ID * 如果不空: 就在查询条件后面加 and _id = * ContentUris.parseId(uri) 解析Uri携带过来的ID */ if (TextUtils.isEmpty(selection)) { selection = " _id = " + ContentUris.parseId(uri); } else { selection = selection + " and _id = " + ContentUris.parseId(uri); } cursor = db.query("cat", projection, selection, selectionArgs, null, null, "_id desc"); break; } /** * 在内容提供者里面,千万不能关闭数据库,关闭游标 */ return cursor; } /** * 增加 * @param uri 其他应用传递过来的Uri * @param values 其他应用传递过来的ContentValues * @return */ @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase database = mySqliteOpenHeper.getWritableDatabase(); /** * uriMatcher.match(uri); 匹配Uri * 匹配其他应用传递过来的Uri,与我内容提供者addURI添加好的Uir进行判断 * 判断到 如果 传递过来的Uir 和我addURI添加好的Uir一致才做处理 */ int code = uriMatcher.match(uri); if (code == CAT_CODE) { // 插入数据 不需要 CAT_ID_CODE,只需CAT_CODE即可 // 参数一:表名 参数二:其他应用传递过来的ContentValues long resultID = database.insert("cat", null, values); /** * 可以返回Uri,返回的Uri给其他应用,其他应用程序就知道,insert的结果详情 * 既然要把insert详情,就需要加入插入的受影响行数 */ uri = ContentUris.withAppendedId(uri, resultID); } else if (code == CAT_ID_CODE){ } /** * 在内容提供者里面,千万不能关闭数据库,关闭游标 */ return uri; } /** * 修改 * @param uri 其他应用传递过来的Uri * @param values 其他应用传递过来的ContentValues * @param selection 其他应用传递过来的查询条件 * @param selectionArgs 其他应用传递过来的查询条件参数值 * @return */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase database = mySqliteOpenHeper.getWritableDatabase(); int updateResult = 0; /** * uriMatcher.match(uri); 匹配Uri * 匹配其他应用传递过来的Uri,与我内容提供者addURI添加好的Uir进行判断 * 判断到 如果 传递过来的Uir 和我addURI添加好的Uir一致才做处理 */ int code = uriMatcher.match(uri); switch (code) { case CAT_CODE: break; case CAT_ID_CODE: // ID 为条件的的修改方式 /** * 判断其他应用程序,传递过来的查询条件,是否为空 * 如果为空:就获取Uri携带的ID * 如果不空: 就在查询条件后面加 and _id = * ContentUris.parseId(uri) 解析Uri携带过来的ID */ if (TextUtils.isEmpty(selection)) { selection = " _id = " + ContentUris.parseId(uri); } else { selection = selection + " and _id = " + ContentUris.parseId(uri); } // 参数一:表名 参数二:其他应用传递过来的ContentValues 参数三:其他应用传递过来的查询条件 updateResult = database.update("cat", values, selection, selectionArgs); break; } /** * 在内容提供者里面,千万不能关闭数据库,关闭游标 */ return updateResult; } /** * 删除 * @param uri 其他应用传递过来的Uri * @param selection 其他应用传递过来的查询条件 * @param selectionArgs 其他应用传递过来的查询条件参数值 * @return */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase database = mySqliteOpenHeper.getWritableDatabase(); int deleteResult = 0; /** * uriMatcher.match(uri); 匹配Uri * 匹配其他应用传递过来的Uri,与我内容提供者addURI添加好的Uir进行判断 * 判断到 如果 传递过来的Uir 和我addURI添加好的Uir一致才做处理 */ int code = uriMatcher.match(uri); switch (code) { case CAT_CODE: break; case CAT_ID_CODE: // ID 为条件的的删除方式 /** * 判断其他应用程序,传递过来的查询条件,是否为空 * 如果为空:就获取Uri携带的ID * 如果不空: 就在查询条件后面加 and _id = * ContentUris.parseId(uri) 解析Uri携带过来的ID */ if (TextUtils.isEmpty(selection)) { selection = " _id = " + ContentUris.parseId(uri); } else { selection = selection + " and _id = " + ContentUris.parseId(uri); } // 参数一:表名 参数二:其他应用传递过来的查询条件 参数三:其他应用传递过来的查询条件的值 deleteResult = database.delete("cat", selection, selectionArgs); break; } /** * 在内容提供者里面,千万不能关闭数据库,关闭游标 */ return deleteResult; } /** * 得到类型 在后续的博客中会有讲解到 * @param uri * @return */ @Override public String getType(Uri uri) { return null; } }
ContentProviderServer应用-->定义 在AndroidManifest.xml 中 对外暴露 MyContentProvider
<!-- ContentProvider是组件需要配置 可以把ContentProvider看作是服务器 authorities 看作是服务器 服务器有访问的链接,authorities(授权) ,是唯一标识 android:enabled="true" 可以被系统实例化 android:exported="true" 允许对外输出 --> <provider android:authorities="autho.prov.cp.MyContentProvider" android:name=".cp.MyContentProvider" android:enabled="true" android:exported="true" />
然后运行 ContentProviderServer应用:由于在AndroidManifest.xml中配置了MyContentProvider组件,只要运行 ContentProviderServer应用,就会自动初始化onCreate()方法
12-14 09:22:55.187 2013-2013/liudeli.cp.server D/MyContentProvider: onCreate()
ContentProviderClient应用 --> MainActivity中调用 ContentProviderServer应用的内容提供者
package liudeli.cp.client; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; import liudeli.cp.client.adapter.MyCursorAdapter; public class MainActivity extends AppCompatActivity { private EditText etID; private ContentResolver contentResolver; private ListView listview; private Uri uri; private Uri uriId; private Cursor cursor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etID = findViewById(R.id.et_id); contentResolver = getContentResolver(); listview = findViewById(R.id.listview); /** * 需要在授权码增加,由于 ContentProviderServer应用通过UriMatcher暴露了 /cat /cat/# * 所以需要在授权码后面拼接 * * /cat == CAT_CODE == [全部查询, , 删除] * * /cat == CAT_CODE_ID == [ID查询,修改] */ uri = Uri.parse("content://autho.prov.cp.MyContentProvider/cat"); uriId = Uri.parse("content://autho.prov.cp.MyContentProvider/cat/#"); } /** * 测试的方法 * @param view */ public void test(View view) { /** * 可以想象客户端访问服务器,需要需要用到协议HTTP * 而想访问ContentProvider,需要ContentResolver */ ContentResolver contentProvider = getContentResolver(); /** * 可以想象访问服务器,需要这样拼接访问地址 http:// * 而想访问ContentProvider,需要这样拼接访问地址 content:// * 必须拿到暴露的授权authorities="autho.prov.cp.MyContentProvider" 进行拼接 */ Uri uri = Uri.parse("content://autho.prov.cp.MyContentProvider"); // 查询 contentProvider.query(uri, null, null, null, null, null); // 增加 // contentProvider.insert(uri, null); // 修改 // contentProvider.update(uri, null, null, null); // 删除 // contentProvider.delete(uri, null, null); } /** * 查询 */ public void query(View view) { cursor = contentResolver.query(uri, new String[]{"_id", "name", "age"}, null, null , null, null); /** * 使用SimpleCursorAdapter 适配器 */ SimpleCursorAdapter adapter = new SimpleCursorAdapter(MainActivity.this, // 上下文 R.layout.layout_item, // Item布局 cursor, // Cursor 查询出来的游标 这里面有数据库里面的数据 new String[]{"_id", "name", "age"}, // 从哪里来,指的是 查询出数据库列名 new int[]{R.id.tv_id, R.id.tv_name, R.id.tv_age}, // 到哪里去,指的是,把查询出来的数据,赋值给Item布局 的控件 SimpleCursorAdapter.NO_SELECTION); // 给ListView设置适配器 listview.setAdapter(adapter); // 注意:在数据展示完成后,不要关闭游标, 在Activity结束后在关闭cursor.close(); } /** * ID查询单个 * @param view */ public void queryId(View view) { if (TextUtils.isEmpty(etID.getText().toString())) { Toast.makeText(MainActivity.this, "请输入ID", Toast.LENGTH_LONG).show(); return; } Uri uriID = null; /** * 需要把ID带过去 uriId = ContentUris.withAppendedId(uri, Long.parseLong(etID.getText().toString())); * ContentUris.withAppendedId 写入ID到Uri * ContentUris.parseId() 解析Uri里面的携带的ID */ uriID = ContentUris.withAppendedId(uriId, Long.parseLong(etID.getText().toString())); cursor = contentResolver.query(uriID, new String[]{"_id", "name", "age"}, null, null , null, null); /** * 使用CursorAdapter 适配器 */ CursorAdapter adapter = new MyCursorAdapter(this, cursor, CursorAdapter.IGNORE_ITEM_VIEW_TYPE); // 给ListView设置适配器 listview.setAdapter(adapter); // 注意:在数据展示完成后,不要关闭游标, 在Activity结束后在关闭cursor.close(); } /** * 增加 */ public void insert(View view) { if (TextUtils.isEmpty(etID.getText().toString())) { Toast.makeText(MainActivity.this, "请输入ID", Toast.LENGTH_LONG).show(); return; } ContentValues vs = new ContentValues(); vs.put("name", "刘新龙" + etID.getText().toString()); vs.put("age", 90); contentResolver.insert(uri, vs); // 规范写法应该是:simpleCursorAdapter.notifyDataSetChanged(); // 我这里为了测试下,就直接这样掉算了 query(null); } /** * 修改 * @param view */ public void update(View view) { if (TextUtils.isEmpty(etID.getText().toString())) { Toast.makeText(MainActivity.this, "请输入ID", Toast.LENGTH_LONG).show(); return; } // 以前的方式 /*ContentValues vs = new ContentValues(); vs.put("name", "王二麻子" + etID.getText().toString()); vs.put("age", 78); contentResolver.update(uri, vs, "_id = ?", new String[]{etID.getText().toString()});*/ /** * 需要把ID带过去 uriId = ContentUris.withAppendedId(uri, Long.parseLong(etID.getText().toString())); * ContentUris.withAppendedId 写入ID到Uri * ContentUris.parseId() 解析Uri里面的携带的ID */ Uri uriID = ContentUris.withAppendedId(uriId, Long.parseLong(etID.getText().toString())); // 现在的写法 ContentValues vs = new ContentValues(); vs.put("name", "大扛粑子" + etID.getText().toString()); vs.put("age", 78); // contentResolver.update(uri, vs, "_id = ?", new String[]{etID.getText().toString()}); contentResolver.update(uriID, vs, null, null); // 规范写法应该是:simpleCursorAdapter.notifyDataSetChanged(); // 我这里为了测试下,就直接这样掉算了 query(null); } /** * 删除 * * @param view */ public void delete(View view) { if (TextUtils.isEmpty(etID.getText().toString())) { Toast.makeText(MainActivity.this, "请输入ID", Toast.LENGTH_LONG).show(); return; } /** * 需要把ID带过去 uriId = ContentUris.withAppendedId(uri, Long.parseLong(etID.getText().toString())); * ContentUris.withAppendedId 写入ID到Uri * ContentUris.parseId() 解析Uri里面的携带的ID */ Uri uriID = ContentUris.withAppendedId(uriId, Long.parseLong(etID.getText().toString())); contentResolver.delete(uriID, "_id = ?", new String[]{etID.getText().toString()}); // 规范写法应该是:simpleCursorAdapter.notifyDataSetChanged(); // 我这里为了测试下,就直接这样掉算了 query(null); } /** * 在Activity结束后在关闭cursor.close(); */ @Override protected void onDestroy() { super.onDestroy(); if (cursor != null) { cursor.close(); } } }
ContentProviderClient应用 使用到的CursorAdapter:
package liudeli.cp.client.adapter; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; import android.database.Cursor; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.TextView; import liudeli.cp.client.R; public class MyCursorAdapter extends CursorAdapter { // 定义布局加载器 private LayoutInflater layoutInflater; /** * 构造方法 * @param context 传入上下文 * @param c 传入Cursor游标 * @param flags 传入标记 */ public MyCursorAdapter(Context context, Cursor c, int flags) { /** * 主要把这些值传递给父类的构造方法,就会自动的传递到 newView(Context context, Cursor cursor, ViewGroup parent) * bindView(View view, Context context, Cursor cursor) */ super(context, c, flags); layoutInflater = LayoutInflater.from(context); } /** * Item布局文件的处理 * @param context 传入上下文 * @param cursor * @param parent * @return */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { // Item布局文件,要显示的Item,在这里处理 View view = layoutInflater.inflate(R.layout.layout_item, null); return view; } /** * 把Cursor获取的数据和布局文件进行绑定 * @param view 此view 是上面 newView方法返回的View * @param context 上下文 * @param cursor 游标 */ @Override public void bindView(View view, Context context, Cursor cursor) { // 获取到布局的控件 TextView tvId = view.findViewById(R.id.tv_id); TextView tvName = view.findViewById(R.id.tv_name); TextView tvAge = view.findViewById(R.id.tv_age); // 获取Cursor里面的数据 int _id = cursor.getInt(cursor.getColumnIndex("_id")); String name = cursor.getString(cursor.getColumnIndex("name")); int age = cursor.getInt(cursor.getColumnIndex("age")); // 把数据绑定到控件里面去 tvId.setText(_id + ""); // setText(数据必须是字符串); tvName.setText(name); // setText(数据必须是字符串); tvAge.setText(age + ""); // setText(数据必须是字符串); } }
ContentProviderClient应用 --> 布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:id="@+id/ll_buttons" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="test" android:onClick="test" android:layout_weight="1" android:visibility="gone" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="查询" android:layout_weight="1" android:onClick="query" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ID查询" android:layout_weight="1" android:onClick="queryId" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="增加" android:layout_weight="1" android:onClick="insert" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="修改" android:layout_weight="1" android:onClick="update" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="删除" android:layout_weight="1" android:onClick="delete" /> </LinearLayout> <LinearLayout android:id="@+id/ll_et_id" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/ll_buttons" android:layout_marginTop="10dp" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="请输入_id号" android:textSize="20sp" /> <EditText android:id="@+id/et_id" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/white" android:layout_marginLeft="10dp" /> </LinearLayout> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/ll_et_id" android:layout_marginTop="20dp"></ListView> </RelativeLayout>
ContentProviderClient应用 --> 布局文件 -->ListView--> Item布局文件
<?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="wrap_content" android:layout_marginBottom="8dp"> <TextView android:id="@+id/tv_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="id" android:textColor="@android:color/black" /> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="name" android:textColor="@android:color/black" android:layout_marginTop="5dp" /> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="age" android:textColor="@android:color/black" android:layout_marginTop="5dp" /> </LinearLayout>