天天看點

ContentProvider操作資料庫—一項古老的Android技術一、雜談二、SQLiteOpenHelper三、BaseContentProvider四、封裝思想及過程五、封裝思想總結

一、雜談

本文結構:

1、雜談。

2、上代碼,閹割版的demo。

3、總結一下封裝的思想。

本文旨在回味幾年前的技術,同時對封裝功底進行夯實。畢竟最近一段時間都沒有寫代碼。封裝的思路,要清晰,明白,明白的是這個思路,這個想法,而不是照抄。

好久沒更新CSDN了,因為最近有點懵懵的,周末也不知道在幹啥,沒打錯字,不是萌萌的,是真懵懵的。

之前找工作階段的時候,面試過一家公司,叫xx拼車,名字也是蹭滴滴的熱度,整了個滴答。O(∩_∩)O哈哈~。人事是個挺漂亮的年輕小姐姐,是以說這家公司不錯。

面試這家公司的時候,原定是組長面我,因為我和另一個人一起來的,他先答完了題(by phone),是以經理去面他了,然後找了個人來面我。這個人是個員工,一個有強烈競争感的員工。

問了我上家公司采用的一些技術,其中涉及到資料庫部分的,我答,用的是ContentProvider。接下來他的反應令我懵逼了。

它以一種嘲笑的語氣,半笑着問我,ContentProvider不是用來資料共享的嗎,怎麼能操作資料庫呢?

我(⊙o⊙)…了一下。然後回答,ContentProvider資料共享的時候,不也是操作了資料庫嗎?那麼他一定可以操作自己的資料庫啊。

他很執着的說,ContentProvider是用來共享資料的,不是操作資料的啊。

我(⊙o⊙)…了一下。然後說可以的,是可以的,我可以給你說說。

然後他又重複的說了上一句,ContentProvider是用來共享資料的,不是操作資料的啊。

我心裡想,尼瑪,這麼一根筋嗎,你就當做共享了自己的資料行不?我說,ContentProvider可以共享,也可以操作資料庫,插入查找删除啥的都有,都可以的。

我剛想給他詳細的說一下,代碼怎麼實作,但這時候突然間,他好像魔怔了一般,選擇性失聰并且複讀。一直在重複着那一句話,“ContentProvider怎麼操作資料庫,ContentProvider怎麼操作資料庫,ContentProvider怎麼操作資料庫”,令我無從插嘴。恨自己交流能力太弱。

這時候我想到了,在山的那邊,雪的那邊,有一個男精靈,他名字叫歐陽鋒,他執着又聰明。

一個練功練魔怔了的男人,他倆此時的狀态居然像極,因為對于心法中有地方想不明白,就走火入魔了。

最後我還是簡單說了一下ContentProvider操作資料的過程,然後我們進行了下一話題。

面試完畢之後,我覺得我很有戲,因為我說到了一個他不會的知識點,虐了一下面試官,一般這種情況,面試官都會錄取你的。

故事的結局很意外。他們說我們在考慮一下之後,就一直考慮到了現在,大概已經有三四個月了吧,而我早就入職别的公司了。

我到現在還挺愧疚的,我要不要告訴他們我已經入職了啊,要不然他們還在考慮可怎麼辦啊。

哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈。

之前公司資料庫操作用的SQLiteOpenHelper和ContentProvider,現在入職的公司,是OrmLite和ContentProvider一起用的。是的都是比較老的架構。

畢竟這兩個項目,一個六年了,一個N(N<=4)年了。

(6年前的Android項目你見過嗎?古董)

簡單說一下吧,ContentProvider操作資料庫的好處,那就是定義統一的接口,以接口的形式通路表啥的。不管你表結構怎麼改,我這不用改,挺友善是吧。但是,據我個人所知,現在類似與GreenDao這種架構的流行,對資料操作的優化以及程式設計時也不用自己寫多少,是以ContentProvider沒有多少公司再用了。

有用的,要麼是項目很久都沒有被重構,要麼是做項目的程式員所掌握的技能很久都沒有被重構。

牛逼吹完了,上上代碼,這裡以SQLiteOpenHelper為例,回顧一下幾年前主流的資料庫操作。(老程式員可以哭了,現在還有菜鳥學你們之前的用法)

卧槽,寫到這發現有點不對啊。别人都是學新的技術,新技術一出,就像瘋狗一樣去學,生怕屎涼了,哦不,是新技術過時了(沒辦法,Android技術更新太快),為啥我越學越往回學了啊?

二、SQLiteOpenHelper

SQLiteOpenHelper略。

打我啊。

三、BaseContentProvider

先寫個ContentProvider,實作了先。内部定義SQLIteOpenHelper。

/**
 * ContentProvider基礎類.
 *
 */
public abstract class BaseContentProvider extends ContentProvider {
    //略一堆常量

    private SQLiteOpenHelper mOpenHelper;


    /**
     * 子類應該通過本方法設定SQLiteOpenHelper
     *
     * @param helper
     */
    public void setSQLiteOpenHelper(SQLiteOpenHelper helper) {
        mOpenHelper = helper;
    }
    
    //未完。。。。。。

           

然後重寫COntentProvider中的一些方法,例如增删改查的方法。這裡用到一個類,SqlArguments ,這個是自己封裝的,Android SDK沒有,别問我你怎麼找不到。

就是根據傳來的uri以及其他參數,來傳回不同的參數。。。對參數的一個封裝,就是查詢時需要的表名,以及其他條件。

重寫getType:

//就是根據uri傳回個表名
 @Override
    public String getType(Uri uri) {
        SqlArguments args = new SqlArguments(uri, null, null);
        if (TextUtils.isEmpty(args.where)) {
            return "xx.xx.xx/" + args.table;
        } else {
            return "yy.yy.yy/" + args.table;
        }
    }
           

重寫查詢,這裡有個setNotificationUri的方法,這個方法就是在資料庫改了之後,通知我們注冊的觀察者,這個觀察這是在cursorAdapter中注冊的,想詳細了解這裡的話,自行百度吧。這裡上連結會處于稽核中,而一直不過關的。有病。

@Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {

        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(args.table);

        SQLiteDatabase db = null;
        Cursor result = null;
        try {
            db = mOpenHelper.getWritableDatabase();// .getReadableDatabase();
            result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
            if (result != null) {
                result.setNotificationUri(getContext().getContentResolver(), uri);
            }
        } catch (SQLiteDiskIOException e) {
            Log.i(TAG, "query : " + e.toString());
            if (result != null) {
                result.close();
                result = null;
            }
        }

        return result;
    }
           

再寫個插入,批量插入。這裡用到了資料庫的事務,要插一起插,完事的先等着,沒完事的插完了一起拔。(o)/~

@Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        SqlArguments args = new SqlArguments(uri);

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            int numValues = values.length;
            for (int i = 0; i < numValues; i++) {
                if (db.insert(args.table, null, values[i]) < 0)
                    return 0;
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        sendNotify(uri);
        return values.length;
    }
//通知一下子觀察者,資料庫變了。
private void sendNotify(Uri uri) {
        String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
        if (notify == null || "true".equals(notify)) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
    }
           

更新和删除就不寫了,和插入差不多。傳回int類型,更新了幾條,删除了幾條。

這樣,一個BaseContentProvider就寫好了。然後寫一個類,繼承我們的BaseContentProvider就好了。

四、封裝思想及過程

上學那時候,學什麼課程都要做個學生資訊管理系統。後來我膩了,老子來學計算機是打算做遊戲的,天天做個破管理系統幹毛啊?

後來老師一想,恩,就讓我做了個跟遊戲有關的玩家資訊管理系統。。。

OMG,後來拷貝代碼的時候,沒改名,玩家有一個表叫做課程表。交作業的時候,老師問我,玩家為什麼要有課程?

我答,這是本遊戲的特色,玩家除了玩遊戲以外還可以學習。我天貓的是一本正經的那種表情和語氣當着全班同學的面回答的啊。回答完自己都憋不住笑。同學們都說我是個弱智。

是以這裡建立的表還是Course

然後說一下封裝的思想。封裝,就是将一系列操作,打包,暴露給外部使用的時候,十分簡單。而正因為這種封裝的封閉性,是以我們封裝的一定要嚴謹,一定要不能出錯。層層判斷是必須的,現在你拿出任何一個知名的APP出來,你看他的源代碼,都是判斷的十分嚴謹,恨不得所有地方都寫上if。對資料的來源,不信任,不關心,這是Java的一個封裝最基本的思想,哪怕你知道資料的來源可靠。如果代碼裡這種判斷都沒有,那麼這個代碼一定是個垃圾。

劃重點:對資料的來源,不信任且不關心,哪怕你知道這資料是從哪來的,并且傳的是什麼,那也要加上判斷。

CourseContentProvider:

/**
 * Created by WenCh on 2017/11/5.
 */

public class CourseContentProvider extends BaseContentProvider {

    public static final int DATABASE_VERSION = Constants.DATABASE_VERSION;

    public static final String DATABASE_NAME = "netease_vopen.db";

    public static final String AUTHORITY = Constants.DATABASE_AUTHORITY;

    public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);

    public static final String TABLE_All_DTTA = "t_vopen_all_data";

    @Override
    public boolean onCreate() {
        setSQLiteOpenHelper(new com.contentprovidertest.CourseHelper(getContext()));
        return true;
    }

    public static class CourseHelper {
        /**===================資料庫字段==================*/
        /**課程id*/
        public static final String COURSE_ID = "course_id";
        /**課程名稱*/
        public static final String COURSE_NAME = "course_name";
        /**課程tag*/
        public static final String COURSE_TAG = "course_tag";

        /**===================資料庫字段==================*/

        public static Uri getUri() {
            return Uri.parse("content://" + CourseContentProvider.AUTHORITY + "/"
                    + TABLE_All_DTTA);
        }
    }
}

//這裡也可以寫上其他表的Helper,我就不寫了,略了,略略略
           

DBApi

/**
 * Created by WenCh on 2017/11/5.
 */

public class DBApi {
    public static final int DB_OPERATION_FAILED = -1;

    /*************************************************************
     * table name :TABLE_All_DTTA = "t_vopen_all_data"
     *************************************************************/
    //該表資料字段
    public static class DBCourseInfo {
        public String mCourseId;
        public String mCourseName;
        public String mCourseTag;
    }

    /**
     * 向表所有資料表中插入一項
     *
     * @param context 不瞎都知道
     * @param course  不傻都知道
     * @return 不是沙比都知道
     * @deprecated ^_^
     */
    public static Uri insertCourse(Context context, DBCourseInfo course) {
        if (null == course) {
            return null;
        }
        ContentValues initialValues = new ContentValues();
        if (!("判斷一下相應的條件," +
                "比如說資料庫裡有其他字段,這個字段是辨別的type," +
                "因為資料庫裡有很多表,是以根據這個type來判斷是哪個表,否可能插入錯誤," +
                "這裡是demo,就不寫了").isEmpty())
            initialValues.put(CourseContentProvider.CourseHelper.COURSE_TAG, course.mCourseTag);
        return context.getContentResolver().insert(CourseContentProvider.CourseHelper.getUri(), initialValues);
    }
}

//這裡也可以像其他表插入,比如插入學生。。。。
//insertStudent,可以這樣命名,然後調用的CourseContentProvider中
//的别的Helper,比如StudentHelper。懂了嗎?
           

懂了

DBUtils

這個類裡,可以寫一些對對條件的判斷,對資料庫的操作,調用DBApi。來實作更強大的功能。

/**
 * Created by WenCh on 2017/11/5.
 * <p>
 * 該類用來處理資料庫操作後的封裝
 */

public class DBUtils {
    private static final String TAG = "DBUtils";

    /**
     * 查詢與某一門課程相似的課程清單。
     *
     * @param context
     * @param info
     * @return
     */
    public static Cursor getRelativeCourses(Context context, BeanCourse info) {
        String[] projection = new String[]{CourseContentProvider.CourseHelper.COURSE_CONTENT, "_id"};
        StringBuilder selectionBuilder = new StringBuilder();
        List<String> selectionArgsList = new ArrayList<String>();
        //不包括自己
        selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_ID + "<> ?");
        selectionArgsList.add(info.plid);

        //有相同的tag
        String tags = info.tags;
        if (!TextUtils.isEmpty(tags)) {
            selectionBuilder.append(" AND ");
            String[] tagTokens = tags.split(",");
            selectionBuilder.append("(");
            for (int i = 0; i < tagTokens.length; i++) {
                String tag = tagTokens[i];
                selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_TAG + " LIKE ? " + " OR ");
                selectionArgsList.add("%" + tag + "%");
            }
            selectionBuilder.setLength(selectionBuilder.length() - 4);
            selectionBuilder.append(")");
        }
        String selection = null;
        if (selectionBuilder.length() > 0) {
            selection = selectionBuilder.toString();
        }
        String[] selectionArgs = null;
        if (selectionArgsList.size() > 0) {
            selectionArgs = selectionArgsList.toArray(new String[0]);
        }
        String sortOrder = CourseContentProvider.CourseHelper.COURSE_NAME + " DESC";
        return DBApi.queryCourse(context, projection, selection, selectionArgs, sortOrder);
    }
}

//這裡也可以加上其他方法,例如,模糊插入,判斷表裡有沒有,有就更新,沒有就插入等等。
           

怎麼樣,經過這一層層封裝,是不是可以完成很強大的功能?而邏輯還是清晰的?如果你什麼都寫一個類裡,那麼你一定特别亂。

五、封裝思想總結

BaseContentProvider完成一些ContentProvider的操作。實作。

CourseContentProvider繼承自BaseContentProvider,内部含有很多表的Helper,Helper包含一些字段,uri等。

DBApi是完成對相應的表的資料庫操作,注意,是相應的表的。可以insertStudent,也可以insertCourse。調用CourseContentProvider以及其各種Helper。

DBUtils則是通過DBApi來完成更強大的功能,模糊查詢,模糊插入等。當然了,最基本的插入啥的一定會有的。

周末愉快。卧槽,寫完這部落格已經0:12了。。。。。。

周一愉快。