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>
效果: