1. 程式人生 > >Android-LoaderManager非同步載入資料庫資料

Android-LoaderManager非同步載入資料庫資料

LoaderManager非同步載入資料庫資料,是在(Activity/fragment/其他UI等) 載入大量的本地Database庫表資料,由於資料大在載入過程中會導致UI執行緒阻塞,導致使用者體驗不好,Android為來解決這個問題,就設計了LoaderManager非同步載入資料庫資料

 

以前我在深圳做專案的時候,公司研發的APP是給中國聯通人員在山上工作辦事的,對這款APP要求離線資料,大量的離線資料(成百上千條)都是儲存在本地Database表裡面的,常常在查詢本地Database資料的時候,導致UI執行緒阻塞,體驗不好,哪個時候還不知道有LoaderManager非同步載入資料庫資料,如果早點知道就可以解決這個問題;

 


 

 

MySQLiteOpenHelper3 資料庫幫助類 建立類表

package liudeli.datastorage.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MySQLiteOpenHelper3 extends
SQLiteOpenHelper { public static MySQLiteOpenHelper3 mySQLiteOpenHelper; /** * 由於表名每次使用很頻繁,所有定義成常量 */ public static final String TABLE_NAME = "_student_table"; private static final String DB_NAME = "student.db"; private static final int VERSION = 1; public synchronized
static MySQLiteOpenHelper3 getInstance(Context context) { if (null == mySQLiteOpenHelper) { mySQLiteOpenHelper = new MySQLiteOpenHelper3(context, DB_NAME, null, VERSION); } return mySQLiteOpenHelper; } /** * 當開發者呼叫 getReadableDatabase(); 或者 getWritableDatabase(); * 就會通過此構造方法配置的資訊 來建立 person_info.db 資料庫 * 此方法的另外作用是,如果存著資料庫就開啟資料庫,不存著資料庫就建立資料庫 * @param context 上下文 * @param name 資料庫名 * @param factory 遊標工廠 * @param version 版本,最低為1 */ private MySQLiteOpenHelper3(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } /** * 此方法是何時呼叫? ,是需要開發者呼叫 getReadableDatabase(); 或者 getWritableDatabase(); * 此方法的作用是,如果沒有表就建立開啟,如果有表就開啟 * @param db 可執行SQL語句 */ @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table "+TABLE_NAME+"(_id integer primary key autoincrement, name text, age integer, my_assets text);"); ContentValues values = new ContentValues(); for (int i = 0; i < 6; i++) { values.clear(); values.put("name", "張三" + i); values.put("age", 62 + i); values.put("my_assets", "1000000" + i); db.insert(TABLE_NAME, null, values); } } /** * 此方法用於資料庫升級 * @param db 可執行SQL語句 * @param oldVersion 以前舊版本的版本號 * @param newVersion 現在目前最新的版本號 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }

 

ConnectMySQLiteOpenHelper3ContentProvider 內容提供者 暴露資料庫裡面的資料 進行查詢 增加

package liudeli.datastorage;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

import liudeli.datastorage.db.MySQLiteOpenHelper3;

public class ConnectMySQLiteOpenHelper3ContentProvider extends ContentProvider {

    private MySQLiteOpenHelper3 dbHelper;

    @Override
    public boolean onCreate() {
        Log.d("Provider", "ConnectMySQLiteOpenHelper3ContentProvider");
        dbHelper = MySQLiteOpenHelper3.getInstance(getContext()); // 必須這在裡面,要是寫在外面是無法獲取上下文的
        return false;
    }

    @Override
    public Cursor query(Uri uri,  String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteDatabase database = dbHelper.getReadableDatabase();
        Cursor cursor = database.query(MySQLiteOpenHelper3.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, "_id desc");
        return cursor;  // 內容提供者裡面的 cursor / database 不能關閉
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        long insertThisID = database.insert(MySQLiteOpenHelper3.TABLE_NAME, null, values);

        // insertThisID是 插入成功後,插入的這條資料ID
        Uri uriResult =  ContentUris.withAppendedId(uri, insertThisID);

        // 內容提供者裡面的 cursor / database 不能關閉
        return uriResult;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri,  ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }
}

 

在AndroidManifest.xml provider 對外提供可以訪問的 Uir

      <!--  定義provider 內容提供者
              provider對外暴露
         -->
        <provider
            android:authorities="db.ConnectMySQLiteOpenHelper3ContentProvider"
            android:name=".ConnectMySQLiteOpenHelper3ContentProvider"
            android:exported="true"
            android:enabled="true" />

 

在LoaderActivity使用LoaderManager非同步載入資料庫資料

package liudeli.datastorage;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LoaderActivity extends Activity {

    private LoaderManager loaderManager;
    private ListView listView;

    // 訪問內容提供者的Uir地址
    private Uri uri = Uri.parse("content://db.ConnectMySQLiteOpenHelper3ContentProvider");

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_loader);

        loaderManager = getLoaderManager();

        /**
         * 引數一:ID
         * 引數二:引數
         * 引數三:LoaderCallbacks回撥
         */
        loaderManager.initLoader(1, null, callbacks);

        listView = findViewById(R.id.list_view);
    }

    /**
     * 定義LoaderCallbacks回撥
     */
    private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
        /**
         * 此方法是 載入讀取大量資料 非同步執行
         * @param id
         * @param args
         * @return
         */
        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            CursorLoader cursorLoader = new CursorLoader(LoaderActivity.this);
            cursorLoader.setUri(uri);
            cursorLoader.setSortOrder(null);
            cursorLoader.setSelectionArgs(null);
            cursorLoader.setSelection(null);
            cursorLoader.setProjection(new String[]{"name", "age"});
            // ....

            /*
            第二種方式,都是可以的
            new CursorLoader(Context context, Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder);*/

            return cursorLoader;
        }

        /**
         * 此方法是 已經讀取資料庫資料完成✅了 更新UI操作
         * @param loader
         * @param cursor
         */
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            List<Map<String, Object>> list = new ArrayList<>();

            while (cursor.moveToNext()) {
                Map<String, Object> mMap = new HashMap<>();
                mMap.put("name", cursor.getString(cursor.getColumnIndex("name")));
                mMap.put("age", cursor.getInt(cursor.getColumnIndex("age")));
                list.add(mMap);
            }

            ListAdapter listAdapter =
                    new SimpleAdapter(LoaderActivity.this,
                            list,
                            android.R.layout.simple_list_item_2,
                            new String[]{"name","age"}, // 從哪裡來
                            new int[]{android.R.id.text1, android.R.id.text2}); // 到哪裡去
            listView.setAdapter(listAdapter);
        }

        /**
         * Called when a previously created loader is being reset, and thus
         * making its data unavailable.  The application should at this point
         * remove any references it has to the Loader's data.
         *
         * @param loader The Loader that is being reset.
         */
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {

        }
    };

    /**
     * 增加資料
     * @param view
     */
    public void insert(View view) {
        ContentResolver contentResolver = getContentResolver();
        ContentValues values = new ContentValues();
        values.clear();
        values.put("name", "大民");
        values.put("age", 99);
        values.put("my_assets", "9000000");
        contentResolver.insert(uri, values);

        /**
         * Loader restartLoader 會自動去讀內容提供者裡面的資料
         * 引數一:ID
         * 引數二:引數
         * 引數三:LoaderCallbacks回撥
         */
        loaderManager.restartLoader(3333, null, callbacks);
    }
}

 

佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="insert"
        android:text="增加"
        android:layout_centerInParent="true"
        />

</RelativeLayout>

 

效果: