一、Android四大框架之ContentProvider的学习与运用,实现SQLite的增删改查。
本文系原创博客,文中不妥烦请指出,如需转载摘要请注明出处!
ContentProvider的学习与运用
Alpha Dog
2016-04-13 10:27:06
首先,项目的地址:https://github.com/DarkPointK/MyContentProvider.git。
网上对于Android的Content Provider框架以及SQLite这款轻量级的嵌入式数据库的介绍有很多,我不再复述,在此我将着重于如何实现对数据库的操作。
工作之余开始系统性的自主学习,在阅读了网络上很多大牛的各类技术文章教程后,想试着写一篇属于自己的博客,以记录一些心得成果还可给其他正在学习的朋友一点帮助,而对于文中存在的不妥之处还烦请指出。
这是一个布局很简单的例子,主要内容在于后台对一个SQLite表的增删改查一系列操作。
- “增”:进入应用时“删”“改”按钮不可用,在ET中输入想要添加的字符串后点击“增”弹出新增数据的URI(关于URI的介绍可以通过这片不错的文章了解:http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html);
- “查”:会根据ET中输入的文字在表中搜索并将搜索到的行的_id显示在TV上,此时“删”“改”按钮变成可用;
- “删”:被点击后也将使这两按钮不可用,并删除TV中显示的ID的行,在下次点击“查”并查得数据时按钮又被启用;
- “改”:当查询到数据并在TV中显示ID时可以在ET中输入新的字符串,点击按钮即可改变相应ID行的数据为此字符串并会刷新搜索队列。
而数据库的表在被创建后会存在于/data/data/package_name/databases文件夹中,如有需要在windows环境下查看表可以利用AS的Device Monitor导出:
我们建立一个自己的项目在AS中,在这个项目中我们至多需要3个类,分别为:继承自SQLiteOpenHelper的类用于直接对数据库操作,继承自ContentProvider的类会被用于决定以何种方式操作数据库,然后就是我们的Activity。
1.1继承自SQLiteOpenHelper的StringDataBase类:
在继承SQLiteOpenHelper后我们需要重写它的onCreate()方法和onUpgrade()方法并且实现构造方法。当StringDataBase被初始化时,其构造方法将提交数据库名及其版本号给父类的方法得到处理。首次打开数据库onCreate()方法会被调用将会处理SQL_CREATE字串中的SQL语句,创建数据库得到一个“string”表。当版本号得到提升将调用onUpgrade()方法。
import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import java.sql.SQLException; /** * Created by Alpha Dog on 2016/4/8. */ public class StringDataBase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "StringDatabase"; private static final String TABLE_NAME = "string"; private static final String KEY_NAME = "str"; private static final String SQL_CREATE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT, " + KEY_NAME + " TEXT)"; private static final String SQL_DROP = "DROP TABLE IS EXISTS" + TABLE_NAME; public StringDataBase(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DROP); onCreate(db); } }
重写完这两个方法后,开始着手完善将会被StringProvider类用到的所有对数据库增删改查操作的方法。
在这一阶段,你可能会发现这里对数据库的操作都是通过以getWritableDataBase()方法获得一个可以操作数据库的SQLiteDatabase实例,且有读写权(此外,有getReadableDatabase()方法可获得只读权实例)。在获得SQLiteDatabase的实例后,调用相应的方法并将返回的结果作为返回值,当在StringProvider类中得到这些返回值时,可以进行下一步的处理。
public long addString(ContentValues values) throws SQLException { long id = getWritableDatabase().insert(TABLE_NAME, "", values); if (id <= 0) { throw new SQLException("Failed to add String"); } return id; } public int deleteString(String id) { if (id == null) { return getWritableDatabase().delete(TABLE_NAME, null, null); } else { return getWritableDatabase().delete(TABLE_NAME, "_id=?", new String[]{id}); } } public int updateString( ContentValues values,String id) { return getWritableDatabase().update(TABLE_NAME,values,"_id=?", new String[]{id}); } public Cursor getString(String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqb = new SQLiteQueryBuilder(); sqb.setTables(TABLE_NAME); if (sortOrder == null || sortOrder == "") { sortOrder = "_id"; } return sqb.query(getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); }
对于SQLiteDatabase及SQLiteQueryBuilder在这里调用到的那些增删改查方法的参数列表有必要在这里细说:
-
- getWritableDatabase().insert (String table, String nullColumnHack, ContentValues values) 第一个参数为要操作的表名;第三个参数类似于需要插入的Map,键为列名,值为值;第二个参数不为空时,当values的值为空将会在指定列安全的插入一个null值;插入失败返回-1。
- getWritableDatabase().delete (String table, String whereClause, String[] whereArgs); 第二个参数是String的语句用于作为SQL的where语句部分,如果null将删除所有行;第三个参数是在whereClause中含有?号时将其取代并绑定;删除成功返回受影响的行数目,否则返回0。
- getWritableDatabase().update (String table, ContentValues values, String whereClause, String[] whereArgs); 参考以上。update方法将会选择表,查询行,更新列,返回受影响的行数目;
- SQLiteQueryBuilder.query (SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder); 有了以上的解释,根据query的参数名及这个SDK的API文档链接了解它们的作用 http://www.android-doc.com/reference/android/database/sqlite/SQLiteQueryBuilder.html#query%28android.database.sqlite.SQLiteDatabase,%20java.lang.String[],%20java.lang.String,%20java.lang.String[],%20java.lang.String,%20java.lang.String,%20java.lang.String%29
1.2继承自ContentProvider的StringProvider类:
ContentProvider需要重写的方法,这些方法将在获取ContentProvider的实例后可以被利用URI调用:
由于需要URI连接的原因,我们需要在AndroidManifest.xml中注册这个Provider:
<provider android:authorities="com.alphadog.mycontentprovider" android:name="com.alphadog.mycontentprovider.StringProvider"/> </application>
其中authorities中的字段将作为URI中关键的一部分,用于请求Provider权限: content://com.alphadog.mycontentprovider/
name中的参数指定的是这个Provider具体提供服务的类。
好了,注册完Provider之后,现在来看看这个StringProvider里究竟要写些什么:
import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; /** * Created by Alpha Dog on 2016/4/8. */ public class StringProvider extends ContentProvider { private static final String PROVIDER_NAME = "com.alphadog.mycontentprovider"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/string"); private static final int STR = 1; private static final int STRS = 2; private StringDataBase sdb = null; private static final UriMatcher um = getUriMatcher(); private static UriMatcher getUriMatcher() { UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "string/#", STR); uriMatcher.addURI(PROVIDER_NAME, "string", STRS); return uriMatcher; } //初始化StringDatabase @Override public boolean onCreate() { Context context = getContext(); sdb = new StringDataBase(context); return true; } @Override public String getType(Uri uri) { switch (um.match(uri)) { case STR: return "vnd.android.cursor.item/vnd.com.alphadog.mycontentprovider.string"; case STRS: return "vnd.android.cursor.dir/vnd.com.alphadog.mycontentprovider.string"; } return ""; } //增 @Override public Uri insert(Uri uri, ContentValues values) { try { long id = sdb.addString(values); Uri returnUri = ContentUris.withAppendedId(CONTENT_URI, id); return returnUri; } catch (Exception e) { return null; } } //删 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return sdb.deleteString(selection); } //改 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return sdb.updateString( values,selection); } //查 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return sdb.getString(projection,selection,selectionArgs,sortOrder); } }
这里的代码为便于理解也非常简洁,onCreate()里显示对StringDataBase进行了初始化,这将导致连接或创建数据库,而其他的增删改查方法也是调用这个初始化过sdb里的方法对数据库进行相应的操作。其中,getType()方法可以被外部调用,它会分析传入的URI字串并返回一个MIME字串,关于MIME的具体意义可以参考这篇博客:http://blog.csdn.net/harvic880925/article/details/44620851 。
2.1 Activity中的调用
至此,一个自定义的Provide就算大功告成了,剩下的便是对这个Provider的使用方法了,这是个令人激动的阶段,对此可以参考下我写这个Activity,然后根据你的需求进行优化改动。
import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; import static android.widget.Toast.LENGTH_SHORT; public class MainActivity extends AppCompatActivity { private static final String PROVIDER_NAME = "com.alphadog.mycontentprovider"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/string"); private Cursor c; private String s; private boolean fresh = true; private Uri seleUri; private int id; @InjectView(R.id.tv) TextView tv; @InjectView(R.id.et) EditText et; @InjectView(R.id.add) Button add; @InjectView(R.id.del) Button del; @InjectView(R.id.update) Button update; @InjectView(R.id.query) Button query; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.inject(this); update.setEnabled(false); del.setEnabled(false); et.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { fresh = true; } @Override public void afterTextChanged(Editable s) { } }); } @OnClick({R.id.add, R.id.del, R.id.update, R.id.query}) public void onClick(View view) { switch (view.getId()) { case R.id.add: add(); break; case R.id.del: del(); break; case R.id.update: update(); break; case R.id.query: query(); break; } } private void add() { ContentValues cv = new ContentValues(); cv.put("str", et.getText().toString()); Uri uri = getContentResolver().insert(CONTENT_URI, cv); if (uri != null) { Toast.makeText(getBaseContext(), uri.toString(), LENGTH_SHORT).show(); } } private void del() { // 条件只传id,因为在StringDataBase中有完整处理where语句 int uri = getContentResolver().delete(CONTENT_URI, "" + id, null); Toast.makeText(getBaseContext(), "删除成功" + id, LENGTH_SHORT).show(); update.setEnabled(false); del.setEnabled(false); fresh = true; } private void update() { ContentValues cv = new ContentValues(); if (!c.getString(1).equals(et.getText().toString())) { cv.put("str", et.getText().toString()); int uri = getContentResolver().update(CONTENT_URI, cv, "" + id, null); Log.i("修改id为" + id + "的值为", " " + et.getText().toString()); Toast.makeText(getBaseContext(), "修改成功" + id+"为"+et.getText().toString(), LENGTH_SHORT).show(); fresh = true; } else { Toast.makeText(this, "没做任何更改!", LENGTH_SHORT).show(); fresh = false; } } private void query() { s = et.getText().toString(); if (fresh&& !s.equals("") ) { s = "str = '" + et.getText().toString() + "'"; Log.i("正在搜索的s为", s); Cursor cursor = getContentResolver().query(CONTENT_URI, null, s, null, null); if (cursor == null || cursor.getCount() <= 0) { Toast.makeText(this, "然而什么都没搜到", LENGTH_SHORT).show(); return; } c = cursor; } display(); } private void display() { if (c != null && c.moveToNext()) { update.setEnabled(true); del.setEnabled(true); Log.i("c", c.getString(0) + " " + c.getString(1)); tv.setText(c.getString(0)); id = c.getInt(0);
Toast.makeText(this, "正在查找:"+c.getString(1)+" = "+id, LENGTH_SHORT).show(); fresh = false; } else { tv.setText(""); update.setEnabled(false); del.setEnabled(false); Toast.makeText(this, "搜索结束,队列将刷新并从头开始", LENGTH_SHORT).show(); fresh = true; } } }
这一块的内容主要是描述“增删改查”操作时逻辑上的判断,本文的开头已经具体描述。每个操作都是先得到一个ContentResolver实例然后以URI及必要参数传入调用方法。需要注意的是在query()方法查询到数据后返回的是cursor类型,取值的方法具体参考:http://www.cnblogs.com/TerryBlog/archive/2010/07/05/1771459.html