本文主要是记录一些零碎的知识点
这篇文章在读取短信库时使用的ContentProvider,以及获取本地多媒体信息时,都是是直接使用的getContentResolver(),感觉有必要好好总结一下
一个应用实现ContentProvider来提供内容给别的应用来操作,
一个应用通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以。
新建一个类继承ContentProvider,发现有以下几个方法
public boolean onCreate() 在创建ContentProvider时调用
public Cursor query(Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor
public Uri insert(Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中
public int update(Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据
public int delete(Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据
public String getType(Uri) 用于返回指定的Uri中的数据的MIME类型
唉,本来只是想写个简单的demo实现一下这些功能,结果越写越多,强迫症呀,说一下具体实现,其实还想再做一些,做编辑的,做下拉上滑刷新的功能,唉,只是个demo呀,忘了初衷了。。。。。看见的实现的功能其实离要记录的已经很远了。。。。。
回归具体一步一步实现。
首先新建一个类,继承SQLiteOpenHelper,用来管理数据库的创建,打开,更新
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; /** * Created by chenling on 2016/3/20. */ public class SlackDbHelper extends SQLiteOpenHelper { public SlackDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); Log.i("slack", "SlackDbHelper SQLiteDatabase.........."); } @Override public void onCreate(SQLiteDatabase db) { String ddl="create table users (_id integer primary key autoincrement,username varchar(100))"; db.execSQL(ddl); Log.i("slack","onCreate SQLiteDatabase.........."); } //更新表,版本号变化时 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { String ddl="create table users (id integer primary key autoincrement,username varchar(100),password varchar(100))"; db.execSQL(ddl); Log.i("slack", "onUpgrade SQLiteDatabase.........."); } }接着新建一个Provider类继承ContentProvider,里面封装要做的操作,我这里是封装了一个数据库
import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Environment; import android.support.annotation.Nullable; import android.util.Log; import java.io.File; /** * Created by chenling on 2016/3/20. * 自定义的 ContentProvider,提供自定义的数据的CRUD */ public class SlackContentProvider extends ContentProvider { private SlackDbHelper slackDbHelper; private SQLiteDatabase sqLiteDatabase; private String databasename; private String tablename; private File sdcardDir; @Override public boolean onCreate() { Log.i("slack", "onCreate SlackContentProvider.........."); databasename = "userinfo"; tablename = "users"; sdcardDir= Environment.getExternalStorageDirectory(); Log.i("slack", "onCreate.........."+sdcardDir.getAbsolutePath()); slackDbHelper = new SlackDbHelper(getContext(),databasename,null,1); sqLiteDatabase = slackDbHelper.getWritableDatabase();//得到可读可写的数据库 //return true 表示创建 return true; } @Nullable @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i("slack", "query SlackContentProvider.........."); //public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) return sqLiteDatabase.query(tablename,projection,selection,selectionArgs,null,null,sortOrder); } @Nullable @Override public String getType(Uri uri) { return null; } @Nullable @Override public Uri insert(Uri uri, ContentValues values) { Log.i("slack", "insert SlackContentProvider.........."); //public long insert (String table, String nullColumnHack, ContentValues values) sqLiteDatabase.insert(tablename,null,values); return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.i("slack", "delete SlackContentProvider.........."); return sqLiteDatabase.delete(tablename,selection,selectionArgs); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.i("slack", "update SlackContentProvider.........."); return sqLiteDatabase.update(tablename,values,selection,selectionArgs); } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cl.android.content"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".SlcakContentResolverActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!--自定义ContentProvider注册--> <provider android:authorities="com.slack.cl.User_Info_Provider" android:name=".SlackContentProvider" android:exported="true"></provider> </application> </manifest>这样一个provider我们就简单封装好了,其他的应用可以通过你注册时的uri使用这个对象,比如我上面在authorities属性里写的
看一下我们怎么使用,新建一个activity,我们要做的是通过uri调用我们自定义的provider
import android.app.AlertDialog; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; /** * Created by chenling on 2016/3/20. * ContentResolver 操作自定义的数据 * */ public class SlcakContentResolverActivity extends AppCompatActivity { private ListView listView; private Uri uri; private EditText username; private ContentValues values; private static final int EDIT_ID = 1;//编辑 private static final int DELETE_ID = 2;//删除 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView)findViewById(R.id.dataListView); username = (EditText)findViewById(R.id.username); uri = Uri.parse("content://com.slack.cl.User_Info_Provider"); //自定义的URi ,这个是 Restful 风格的 values = new ContentValues(); /** * ContextMenu用户手指长按某个View触发的菜单 * 实现场景:用户长按某个List元素,则弹出ContextMenu,选择菜单“Delete”,按下后,弹出AlertDialog, * 请用户再去确定是否删除,确定后将数据从SQLite中删除,并更新ListView的显示。 * */ //向ListView注册Context Menu,当系统检测到用户长按某单元是,触发Context Menu弹出 registerForContextMenu(listView); } // 步骤2:创建ContextMenu同OptionMenu,用户长按元素后,会弹出菜单 @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { menu.add(Menu.NONE, EDIT_ID , Menu.NONE, "Edit"); menu.add(Menu.NONE, DELETE_ID , Menu.NONE, "Delete"); super.onCreateContextMenu(menu, v, menuInfo); } //步骤 3: ContextMenu的触发操作,例子将触发delete() 还可以做编辑等待 @Override public boolean onContextItemSelected(MenuItem item) { /* 在此处,我们关键引入 AdapterView.AdapterContextMenuInfo来获取单元的信息。 在有三个重要的信息。 1、id:The row id of the item for which the context menu is being displayed , 在cursorAdaptor中,实际就是表格的_id序号; 2、position 是list的元素的顺序; 3、view就可以获得list中点击元素的View, 通过view可以获取里面的显示的信息 */ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); switch(item.getItemId()){ case DELETE_ID: delete(info.id); return true; case EDIT_ID: // edit(info.id); return true; default: break; } return super.onContextItemSelected(item); } //步骤4: 对触发弹框,和Add的相似,确定后,更新数据库和更新ListView的显示,上次学习已有相类的例子,不再重复。其中getNameById是通过id查名字的方法。值得注意的是,为了内部类中使用,delete的参数采用来final的形式。 private void delete(final long rowId){ if(rowId>0){ new AlertDialog.Builder(this) .setTitle("确定要删除?") .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { String [] selectionArgs = new String[1]; selectionArgs[0] = String.valueOf(rowId); String where = "_id = ?"; //public final int delete (Uri url, String where, String[] selectionArgs) getContentResolver().delete(uri,where,selectionArgs); selectAllInfo(); } }) .setNegativeButton("取消", null) .show(); } } //查询所有数据的按钮 public void selectAllData(View view) { Log.i("slack", "selectAllData SlcakContentResolverActivity.........."); selectAllInfo(); } //查询所有的数据,在listView里显示 private void selectAllInfo() { // Log.i("slack", "selectAllInfo SlcakContentResolverActivity.........."); //public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor cursor = getContentResolver().query(uri,null,null,null,null); // Log.i("slack", "query done SlcakContentResolverActivity.........."); // 用 CursorAdapter 必须是 _id 做为id的列名 CursorAdapter cursorAdapter= new CursorAdapter(getApplicationContext(),cursor,true) { //新建一个视图来保存cursor指向的数据 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { //找到布局和控件 // LayoutInflater inflater = LayoutInflater.from(context); //一般都这样写,返回列表行元素,注意这里返回的就是bindView中的view return LayoutInflater.from(context).inflate(R.layout.item,parent,false); } @Override public void bindView(View view, Context context, Cursor cursor) { String username = cursor.getString(cursor.getColumnIndex("username")); TextView textView = (TextView)view.findViewById(R.id.textView); textView.setText(username); } }; Log.i("slack", "selectAllInfo done SlcakContentResolverActivity.........."); // listView.setAdapter(mSimpleAdapter); listView.setAdapter(cursorAdapter); } public void addAData(View view) { Log.i("slack", "addAData SlcakContentResolverActivity.........."); if(!TextUtils.isEmpty(username.getText().toString())){ Log.i("slack", "isEmpty username SlcakContentResolverActivity.........."); values.clear(); values.put("username", username.getText().toString()); getContentResolver().insert(uri, values); Log.i("slack", "getContentResolver().insert SlcakContentResolverActivity.........."); //添加完查询一次,数据量要是大,不可以这么查,上滑下拉刷新 selectAllInfo(); }else{ Toast.makeText(this, "username null", Toast.LENGTH_SHORT).show(); } } }上面的代码有几个地方需要解释一下,
privateContentValuesvalues;
values.put("username", username.getText().toString());是一个键值对的形式,键是对应的数据库的列名。
查询时,没有什么,由于是查询所有,没有任何参数 Cursor cursor = getContentResolver().query(uri,null,null,null,null);
就是显示时出现了一些问题,我想把数据显示在listView里,一开始使用的是这篇文章里的SimpleCursorAdapter,后来发现被废弃了,查阅说在参数后面加一个into flag,就加了一“0”,确实不报错了,但是数据就是显示不了,很郁闷,后来就干脆直接使用其父类CursorAdapder,但是依旧报错,好坑呀,后来看报错信息发现要使用这个Adapder,数据库id 的名字必须是“_id”
// 用 CursorAdapter 必须是 _id 做为id的列名 CursorAdapter cursorAdapter= new CursorAdapter(getApplicationContext(),cursor,true) { //新建一个视图来保存cursor指向的数据 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { //找到布局和控件 // LayoutInflater inflater = LayoutInflater.from(context); //一般都这样写,返回列表行元素,注意这里返回的就是bindView中的view return LayoutInflater.from(context).inflate(R.layout.item,parent,false); } @Override public void bindView(View view, Context context, Cursor cursor) { String username = cursor.getString(cursor.getColumnIndex("username")); TextView textView = (TextView)view.findViewById(R.id.textView); textView.setText(username); } };估计 SimpleCursorAdapter也是同样的原因,没有验证。
又做了一个删除的,使用的是ContentMenu,用户长按时出现的菜单,这里把listView添加进去了,没有写listView的点击监听事件
这个一共ContentMenu分三步,(上面的代码都有详细的注释)
1.在onCreate函数里 注册 :registerForContextMenu(listView);
2.创建ContentMenu,这是需要重写onCreateContextMenu
3.ContentMenu里的元素的触发事件,需要重写onContextItemSelected
删除时写了一个弹窗 使用的是 AlertDialog里面的按钮也需要设置监听事件。
new AlertDialog.Builder(this) .setTitle("确定要删除?") .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { String [] selectionArgs = new String[1]; selectionArgs[0] = String.valueOf(rowId); String where = "_id = ?"; //public final int delete (Uri url, String where, String[] selectionArgs) getContentResolver().delete(uri,where,selectionArgs); selectAllInfo(); } }) .setNegativeButton("取消", null) .show();
这样就基本完成了,时间限制,编辑信息的没有实现,但是provider里都封装好了,最后再总结一下
一个应用实现ContentProvider来提供内容给别的应用来操作,
一个应用通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以。
附件:源码下载:http://download.csdn.net/detail/i_do_can/9468004
gitHub地址:https://github.com/CL-window/content