一、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

posted @ 2016-04-13 21:58  曳光剂  阅读(1422)  评论(0)    收藏  举报