Android之ContentProvider与ContentResolver

本文主要是记录一些零碎的知识点

这篇文章在读取短信库时使用的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);
    }
}

需要在AndroidManifest.xml文件里进行provider的注册,在加上对内存的读写权限,贴上全部代码

<?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();
        }

    }
}
上面的代码有几个地方需要解释一下,
添加数据时,使用的是getContentResolver().insert(uri, values);由于provider已经封装好了,所以新增时

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


你可能感兴趣的:(android,ContentProvider)