Сегодня мы поговорим о пользовательских загрузчиках в Android. Загрузчики были представлены в Android 3.0, но мы также можем использовать загрузчики в предыдущих версиях Android, начиная с Android 1.6, с помощью библиотеки совместимости Android.
В этом уроке мы не будем обсуждать основы загрузчика, который широко доступен в Интернете. Очень хорошее руководство по загрузчикам в Android можно найти здесь . Здесь мы обсудим, как создать пользовательский загрузчик в Android, который может читать данные из базы данных SQLite и возвращать прочитанные данные как коллекцию POJO.
Android предоставляет класс CursorLoader, который может читать данные от контент-провайдеров, но для чтения данных непосредственно из базы данных SQLite нам нужен наш собственный загрузчик. Для этого написана очень хорошая библиотека загрузчика, которая читает данные из курсора и может быть найдена здесь, и я взял идею пользовательского загрузчика из этих упомянутых ссылок. Но наш пользовательский загрузчик способен читать данные из базы данных SQLite без использования какого-либо поставщика контента и возвращает данные в виде коллекции объектов.
Создайте новый проект Android и не забудьте использовать библиотеку ‘android-support-v4.jar’ и реализовать пользовательский загрузчик следующим образом:
Сначала мы создадим общий файл класса источника данных «DataSource.java» с общими методами для выполнения операций CRUD над данными. Код выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
package com.example.customloaderexample.db; import java.util.List; import android.content.Context; import android.database.sqlite.SQLiteDatabase; public abstract class DataSource { protected SQLiteDatabase mDatabase; public DataSource(SQLiteDatabase database) { mDatabase = database; } public abstract boolean insert(T entity); public abstract boolean delete(T entity); public abstract boolean update(T entity); public abstract List read(); public abstract List read(String selection, String[] selectionArgs, String groupBy, String having, String orderBy); } |
Далее мы создадим наш файл класса модели «Test.java» со следующим кодом. Этот класс представляет данные, которые мы хотим сохранить и извлечь из базы данных.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package com.example.customloaderexample.model; public class Test { private int id; private String name; public Test(){} public Test(String name){ this .name = name; } public int getId() { return id; } public void setId( int id) { this .id = id; } public String getName() { return name; } public void setName(String name) { this .name = name; } @Override public String toString() { return name; } } |
Далее мы создадим подкласс «TestDataSource.java», чтобы специально выполнять операции CRUD над объектами «Test».
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
package com.example.customloaderexample.db; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.example.customloaderexample.model.Test; public class TestDataSource extends DataSource { public static final String TABLE_NAME = "test" ; public static final String COLUMN_ID = "_id" ; public static final String COLUMN_NAME = "name" ; // Database creation sql statement public static final String CREATE_COMMAND = "create table " + TABLE_NAME + "(" + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_NAME + " text not null);" ; public TestDataSource(SQLiteDatabase database) { super (database); // TODO Auto-generated constructor stub } @Override public boolean insert(Test entity) { if (entity == null ) { return false ; } long result = mDatabase.insert(TABLE_NAME, null , generateContentValuesFromObject(entity)); return result != - 1 ; } @Override public boolean delete(Test entity) { if (entity == null ) { return false ; } int result = mDatabase.delete(TABLE_NAME, COLUMN_ID + " = " + entity.getId(), null ); return result != 0 ; } @Override public boolean update(Test entity) { if (entity == null ) { return false ; } int result = mDatabase.update(TABLE_NAME, generateContentValuesFromObject(entity), COLUMN_ID + " = " + entity.getId(), null ); return result != 0 ; } @Override public List read() { Cursor cursor = mDatabase.query(TABLE_NAME, getAllColumns(), null , null , null , null , null ); List tests = new ArrayList(); if (cursor != null && cursor.moveToFirst()) { while (!cursor.isAfterLast()) { tests.add(generateObjectFromCursor(cursor)); cursor.moveToNext(); } cursor.close(); } return tests; } @Override public List read(String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { Cursor cursor = mDatabase.query(TABLE_NAME, getAllColumns(), selection, selectionArgs, groupBy, having, orderBy); List tests = new ArrayList(); if (cursor != null && cursor.moveToFirst()) { while (!cursor.isAfterLast()) { tests.add(generateObjectFromCursor(cursor)); cursor.moveToNext(); } cursor.close(); } return tests; } public String[] getAllColumns() { return new String[] { COLUMN_ID, COLUMN_NAME }; } public Test generateObjectFromCursor(Cursor cursor) { if (cursor == null ) { return null ; } Test test = new Test(); test.setId(cursor.getInt(cursor.getColumnIndex(COLUMN_ID))); test.setName(cursor.getString(cursor.getColumnIndex(COLUMN_NAME))); return test; } public ContentValues generateContentValuesFromObject(Test entity) { if (entity == null ) { return null ; } ContentValues values = new ContentValues(); values.put(COLUMN_NAME, entity.getName()); return values; } } |
Теперь мы создадим наш помощник по открытию базы данных SQLite ‘DbHelper.java’. Этот класс расширяет SQLiteOpenHelper, который помогает нам создавать исходную базу данных и таблицы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
package com.example.customloaderexample.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DbHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "test.db" ; private static final int DATABASE_VERSION = 1 ; public DbHelper(Context context) { super (context, DATABASE_NAME, null , DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase database) { database.execSQL(TestDataSource.CREATE_COMMAND); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL( "DROP TABLE IF EXISTS " + TestDataSource.TABLE_NAME); onCreate(db); } } |
Далее мы создадим общий подкласс AsyncTask ContentChangingTask.java для выполнения изменения содержимого в фоновом режиме. Этот класс является базовым классом для изменения содержимого. Мы создадим подклассы этого базового класса для выполнения операции CRUD с данными в фоновом режиме.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
package com.example.customloaderexample.loader; import android.os.AsyncTask; import android.support.v4.content.Loader; public abstract class ContentChangingTask<T1, T2, T3> extends AsyncTask<T1, T2, T3> { private Loader<?> loader= null ; ContentChangingTask(Loader<?> loader) { this .loader=loader; } @Override protected void onPostExecute(T3 param) { loader.onContentChanged(); } } |
Теперь мы создадим базовый базовый класс для нашего пользовательского загрузчика AbstractDataLoader.java. Этот класс является базовым классом нашего пользовательского загрузчика.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
|
package com.example.customloaderexample.loader; import java.util.List; import android.content.Context; import android.support.v4.content.AsyncTaskLoader; public abstract class AbstractDataLoader> extends AsyncTaskLoader { protected E mLastDataList = null ; protected abstract E buildList(); public AbstractDataLoader(Context context) { super (context); } /** * Runs on a worker thread, loading in our data. Delegates the real work to * concrete subclass' buildCursor() method. */ @Override public E loadInBackground() { return buildList(); } /** * Runs on the UI thread, routing the results from the background thread to * whatever is using the dataList. */ @Override public void deliverResult(E dataList) { if (isReset()) { // An async query came in while the loader is stopped emptyDataList(dataList); return ; } E oldDataList = mLastDataList; mLastDataList = dataList; if (isStarted()) { super .deliverResult(dataList); } if (oldDataList != null && oldDataList != dataList && oldDataList.size() > 0 ) { emptyDataList(oldDataList); } } /** * Starts an asynchronous load of the list data. When the result is ready * the callbacks will be called on the UI thread. If a previous load has * been completed and is still valid the result may be passed to the * callbacks immediately. * * Must be called from the UI thread. */ @Override protected void onStartLoading() { if (mLastDataList != null ) { deliverResult(mLastDataList); } if (takeContentChanged() || mLastDataList == null || mLastDataList.size() == 0 ) { forceLoad(); } } /** * Must be called from the UI thread, triggered by a call to stopLoading(). */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Must be called from the UI thread, triggered by a call to cancel(). Here, * we make sure our Cursor is closed, if it still exists and is not already * closed. */ @Override public void onCanceled(E dataList) { if (dataList != null && dataList.size() > 0 ) { emptyDataList(dataList); } } /** * Must be called from the UI thread, triggered by a call to reset(). Here, * we make sure our Cursor is closed, if it still exists and is not already * closed. */ @Override protected void onReset() { super .onReset(); // Ensure the loader is stopped onStopLoading(); if (mLastDataList != null && mLastDataList.size() > 0 ) { emptyDataList(mLastDataList); } mLastDataList = null ; } protected void emptyDataList(E dataList) { if (dataList != null && dataList.size() > 0 ) { for ( int i = 0 ; i < dataList.size(); i++) { dataList.remove(i); } } } } |
Теперь мы создадим специальный загрузчик SQLiteTestDataLoader.java для объекта «Тест», который является подклассом предыдущего универсального загрузчика. Этот класс является пользовательским загрузчиком для объектов «Тест». Мы создадим этот класс как наш загрузчик.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package com.example.customloaderexample.loader; import java.util.List; import android.content.Context; import com.example.customloaderexample.db.DataSource; import com.example.customloaderexample.model.Test; public class SQLiteTestDataLoader extends AbstractDataLoader<List> { private DataSource mDataSource; private String mSelection; private String[] mSelectionArgs; private String mGroupBy; private String mHaving; private String mOrderBy; public SQLiteTestDataLoader(Context context, DataSource dataSource, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) { super (context); mDataSource = dataSource; mSelection = selection; mSelectionArgs = selectionArgs; mGroupBy = groupBy; mHaving = having; mOrderBy = orderBy; } @Override protected List buildList() { List testList = mDataSource.read(mSelection, mSelectionArgs, mGroupBy, mHaving, mOrderBy); return testList; } public void insert(Test entity) { new InsertTask( this ).execute(entity); } public void update(Test entity) { new UpdateTask( this ).execute(entity); } public void delete(Test entity) { new DeleteTask( this ).execute(entity); } private class InsertTask extends ContentChangingTask<Test, Void, Void> { InsertTask(SQLiteTestDataLoader loader) { super (loader); } @Override protected Void doInBackground(Test... params) { mDataSource.insert(params[ 0 ]); return ( null ); } } private class UpdateTask extends ContentChangingTask<Test, Void, Void> { UpdateTask(SQLiteTestDataLoader loader) { super (loader); } @Override protected Void doInBackground(Test... params) { mDataSource.update(params[ 0 ]); return ( null ); } } private class DeleteTask extends ContentChangingTask<Test, Void, Void> { DeleteTask(SQLiteTestDataLoader loader) { super (loader); } @Override protected Void doInBackground(Test... params) { mDataSource.delete(params[ 0 ]); return ( null ); } } } |
Далее мы создадим исходное представление xml ‘activity_main.xml’ для нашей активности запуска в папке res / layout.
01
02
03
04
05
06
07
08
09
10
11
|
<FrameLayoutxmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "match_parent" android:layout_height= "match_parent" > <fragmentclass= "com.example.customloaderexample.CustomLoaderExampleListFragment" android:id= "@+id/titles" android:layout_width= "match_parent" android:layout_height= "match_parent" /> </FrameLayout> |
Теперь мы создадим активность запуска для запуска нашего проекта. Это FragmentActivity, которое содержит наш ListFragment и выполняет начальную вставку записи в базу данных.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.example.customloaderexample; import java.util.List; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import com.example.customloaderexample.db.DbHelper; import com.example.customloaderexample.db.TestDataSource; import com.example.customloaderexample.model.Test; public class MainFragmentActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); DbHelper helper = new DbHelper( this ); SQLiteDatabase database = helper.getWritableDatabase(); TestDataSource dataSource = new TestDataSource(database); List list = dataSource.read(); if (list == null || list.size() == 0 ){ dataSource.insert( new Test( "Samik" )); dataSource.insert( new Test( "Piyas" )); dataSource.insert( new Test( "Sujal" )); } helper.close(); database.close(); } } |
Не забудьте сохранить запись действия средства запуска в «AndroidManifest.xml» вместе с соответствующим фильтром намерений (чтобы сделать это действие средства запуска).
Теперь мы создадим ListFragment, чтобы извлечь список тестовых объектов из базы данных и показать с помощью нашего пользовательского загрузчика.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
package com.example.customloaderexample; import java.util.List; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.ArrayAdapter; import com.example.customloaderexample.db.DbHelper; import com.example.customloaderexample.db.TestDataSource; import com.example.customloaderexample.loader.SQLiteTestDataLoader; import com.example.customloaderexample.model.Test; public class CustomLoaderExampleListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List>{ private ArrayAdapter mAdapter; // The Loader's id (this id is specific to the ListFragment's LoaderManager) private static final int LOADER_ID = 1 ; private static final boolean DEBUG = true ; private static final String TAG = "CustomLoaderExampleListFragment" ; private SQLiteDatabase mDatabase; private TestDataSource mDataSource; private DbHelper mDbHelper; @Override public void onActivityCreated(Bundle savedInstanceState) { // TODO Auto-generated method stub super .onActivityCreated(savedInstanceState); //setHasOptionsMenu(true); mDbHelper = new DbHelper(getActivity()); mDatabase = mDbHelper.getWritableDatabase(); mDataSource = new TestDataSource(mDatabase); mAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1); setEmptyText( "No data, please add from menu." ); setListAdapter(mAdapter); setListShown( false ); if (DEBUG) { Log.i(TAG, "+++ Calling initLoader()! +++" ); if (getLoaderManager().getLoader(LOADER_ID) == null ) { Log.i(TAG, "+++ Initializing the new Loader... +++" ); } else { Log.i(TAG, "+++ Reconnecting with existing Loader (id '1')... +++" ); } } // Initialize a Loader with id '1'. If the Loader with this id already // exists, then the LoaderManager will reuse the existing Loader. getLoaderManager().initLoader(LOADER_ID, null , this ); } @Override public Loader<List> onCreateLoader( int id, Bundle args) { SQLiteTestDataLoader loader = new SQLiteTestDataLoader(getActivity(), mDataSource, null , null , null , null , null ); return loader; } @Override public void onLoadFinished(Loader<List> loader, List data) { if (DEBUG) Log.i(TAG, "+++ onLoadFinished() called! +++" ); mAdapter.clear(); for (Test test : data){ mAdapter.add(test); } if (isResumed()) { setListShown( true ); } else { setListShownNoAnimation( true ); } } @Override public void onLoaderReset(Loader<List> arg0) { mAdapter.clear(); } @Override public void onDestroy() { super .onDestroy(); mDbHelper.close(); mDatabase.close(); mDataSource = null ; mDbHelper = null ; mDatabase = null ; } } |
Теперь запустите проект как приложение Android, и вы увидите следующий экран:
Подтверждение :
- http://www.vogella.com/articles/AndroidSQLite/article.html
- https://github.com/commonsguy/cwac-loaderex
- http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html