将加载器与 returns 对象的 sqlite 查询一起使用
Using Loaders with sqlite query that returns object
这就是我一直在做的事情,从我的数据库中获取数据 - 我的片段将调用一个 dbhelper,它将 运行 查询并将结果数据传递到一个对象中。
我应该使用加载程序,这样查询就不会在 UI 线程中完成,但以前我没有费心,因为我的应用程序的数据库非常小。展望未来,这是一个坏主意,因为我的应用程序变得越来越大,数据库也是如此。所以现在我正在尽我所能阅读有关加载器的所有内容,包括 CursorLoader 和自定义加载器,但我在我的应用程序中实现它时仍然遇到问题。
所以这是我当前的代码(经过简化,只是为了显示相关部分):
public class CategoryEdit extends Fragment{
private DbControl dbc;
private ObjectCategory objCategory;
private int categoryID = 3;
private EditText etName;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
dbc = new DbControl(getActivity());
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
etName.setText(objCategory.name);
}
}
public class DbControl {
public static final String FILE_NAME = "DbControl.java";
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
public DbControl(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public ObjectCategory getCategoryData(int itemID) throws Exception{
ObjectCategory objCategory = new ObjectCategory();
Cursor cursor = database.query(MySQLiteHelper.TABLE_CATEGORY, new String[] {"name"},
"id = " + itemID, null, null, null, null);
if (cursor!=null && cursor.getCount()>0 && cursor.moveToFirst()) {
objCategory.name = cursor.getString(cursor.getColumnIndex("name"));
}
return objCategory;
}
}
任何人都可以指出正确的方向,在我这里以正确的方式实施 Loader 吗?如果可能的话,我不想过多地更改 class DbControl 中的代码 - 特别是其中函数的 return 数据类型。
顺便说一句,我读过的关于装载机的教程是:
- http://www.recursiverobot.com/post/60331340133/very-simple-example-of-a-loader-and-loadermanager - 但这是 3 多年前写的,它使用 Runnable 来执行 getSupportLoaderManager,而且我还没有看到其他 examples/tutorials 这样做,所以我担心这个文章已过时。此外,它使用 CursorLoader,根据我的理解,它只能 return cursor.
- http://www.grokkingandroid.com/using-loaders-in-android/ - 这似乎也处理 CursorLoader
无论如何,这是我到目前为止所做的:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args){
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
return objCategory; //this returns error, it says required Loader<ObjectCategory> instead of objCategory, but I'm not sure how to do that here
}
@Override
public void onLoadFinished(Loader<ObjectCategory> cursorLoader, ObjectCategory cursor) {
//Since onCreateLoader has error above, I'm not sure how to implement this part
}
@Override
public void onLoaderReset(Loader<ObjectCategory> cursorLoader) {
//Can I just leave this empty?
}
}
我会说 - 不要使用 Loaders
。当我刚接触 Android 时,我在我的应用程序中使用了 CursorLoader
,结果证明这是一个错误的决定。
如果 CursorLoader
足以满足您的需求,那么您就可以了(尽管您的 Activities
和 Fragments
会被 DB 相关逻辑污染),但是一旦您尝试实现你自己的 Loader
,它应该封装与数据库相关的逻辑,并且只是 return 一个构造的对象,你将不可避免地遇到 LoaderManager
框架的一百万个问题。 @CommonsWare 写了一篇关于它的 nice article - 在将 Loaders
集成到您的应用程序之前阅读它。
现在我通常使用 "managers" 从 SQLite 或互联网加载数据。我的"manager"的一般形式是这样的(请注意这些"managers"不是Singletons):
public class TutorialManager {
interface TutorialManagerListener {
void onDataFetchCompleted(SomeData data);
}
private BackgroundThreadPoster mBackgroundThreadPoster;
private MainThreadPoster mMainThreadPoster;
private Set<TutorialManagerListener> mListeners = new HashSet<>(1);
public TutorialManager(@NonNull BackgroundThreadPoster backgroundThreadPoster,
@NonNull MainThreadPoster mainThreadPoster) {
mBackgroundThreadPoster = backgroundThreadPoster;
mMainThreadPoster = mainThreadPoster;
}
@UiThread
public void registerListener(@NonNull TutorialManagerListener listener) {
mListeners.add(listener);
}
@UiThread
public void unregisterListener(@NonNull TutorialManagerListener listener) {
mListeners.remove(listener);
}
@UiThread
private void notifyFetchCompleted(SomeData data) {
for (TutorialManagerListener listener : mListeners) {
listener.onDataFetchCompleted(data);
}
}
/**
* Call to this method will fetch data on background thread and then notify all registered
* listeners about new data on UI thread.
*/
public void fetchData() {
mBackgroundThreadPoster.post(new Runnable() {
@Override
public void run() {
// logic that loads the data on background thread goes here
// final SomeData data = ... ;
mMainThreadPoster.post(new Runnable() {
@Override
public void run() {
notifyFetchCompleted(data);
}
});
}
});
}
}
实施MainThreadPoster
:
/**
* This class should be used in order to execute {@link Runnable} objects on UI thread
*/
public class MainThreadPoster {
private final Handler mMainHandler;
public MainThreadPoster() {
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* Post {@link Runnable} for execution on main thread
*/
public void post(Runnable runnable) {
mMainHandler.post(runnable);
}
}
BackgroundThreadPoster
的实现最多 you.I 通常使用以下简单实现(除非需要一些微调):
/**
* This class should be used in order to execute {@link Runnable} objects on background threads
*/
public class BackgroundThreadPoster {
private final ExecutorService mBackgroundExecutor;
public BackgroundThreadPoster() {
mBackgroundExecutor = Executors.newCachedThreadPool();
}
/**
* Post {@link Runnable} for execution on a random background thread
*/
public void post(Runnable runnable) {
mBackgroundExecutor.execute(runnable);
}
}
我将 MainThreadPoster
和 BackgroundThreadPoster
对象注入 "managers" 以便:
- 使"managers"单元可测试
- 对整个应用程序的后台线程执行集中管理和优化
Can anyone point me in the right direction, in implementing Loader the right way in my case here?
您必须像这样使用 AsyncTaskLoader:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<ObjectCategory>(getActivity()) {
@Override
public ObjectCategory loadInBackground() {
try {
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return objCategory;
}
@Override
protected void onStartLoading() {
forceLoad();
}
};
}
@Override
public void onLoadFinished(Loader<ObjectCategory> loader, ObjectCategory data) {
//do some stuff here
}
@Override
public void onLoaderReset(Loader<ObjectCategory> loader) {
//Yes, you can leave it empty
}
}
这就是我一直在做的事情,从我的数据库中获取数据 - 我的片段将调用一个 dbhelper,它将 运行 查询并将结果数据传递到一个对象中。
我应该使用加载程序,这样查询就不会在 UI 线程中完成,但以前我没有费心,因为我的应用程序的数据库非常小。展望未来,这是一个坏主意,因为我的应用程序变得越来越大,数据库也是如此。所以现在我正在尽我所能阅读有关加载器的所有内容,包括 CursorLoader 和自定义加载器,但我在我的应用程序中实现它时仍然遇到问题。
所以这是我当前的代码(经过简化,只是为了显示相关部分):
public class CategoryEdit extends Fragment{
private DbControl dbc;
private ObjectCategory objCategory;
private int categoryID = 3;
private EditText etName;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
dbc = new DbControl(getActivity());
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
etName.setText(objCategory.name);
}
}
public class DbControl {
public static final String FILE_NAME = "DbControl.java";
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
public DbControl(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public ObjectCategory getCategoryData(int itemID) throws Exception{
ObjectCategory objCategory = new ObjectCategory();
Cursor cursor = database.query(MySQLiteHelper.TABLE_CATEGORY, new String[] {"name"},
"id = " + itemID, null, null, null, null);
if (cursor!=null && cursor.getCount()>0 && cursor.moveToFirst()) {
objCategory.name = cursor.getString(cursor.getColumnIndex("name"));
}
return objCategory;
}
}
任何人都可以指出正确的方向,在我这里以正确的方式实施 Loader 吗?如果可能的话,我不想过多地更改 class DbControl 中的代码 - 特别是其中函数的 return 数据类型。
顺便说一句,我读过的关于装载机的教程是:
- http://www.recursiverobot.com/post/60331340133/very-simple-example-of-a-loader-and-loadermanager - 但这是 3 多年前写的,它使用 Runnable 来执行 getSupportLoaderManager,而且我还没有看到其他 examples/tutorials 这样做,所以我担心这个文章已过时。此外,它使用 CursorLoader,根据我的理解,它只能 return cursor.
- http://www.grokkingandroid.com/using-loaders-in-android/ - 这似乎也处理 CursorLoader
无论如何,这是我到目前为止所做的:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args){
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
return objCategory; //this returns error, it says required Loader<ObjectCategory> instead of objCategory, but I'm not sure how to do that here
}
@Override
public void onLoadFinished(Loader<ObjectCategory> cursorLoader, ObjectCategory cursor) {
//Since onCreateLoader has error above, I'm not sure how to implement this part
}
@Override
public void onLoaderReset(Loader<ObjectCategory> cursorLoader) {
//Can I just leave this empty?
}
}
我会说 - 不要使用 Loaders
。当我刚接触 Android 时,我在我的应用程序中使用了 CursorLoader
,结果证明这是一个错误的决定。
如果 CursorLoader
足以满足您的需求,那么您就可以了(尽管您的 Activities
和 Fragments
会被 DB 相关逻辑污染),但是一旦您尝试实现你自己的 Loader
,它应该封装与数据库相关的逻辑,并且只是 return 一个构造的对象,你将不可避免地遇到 LoaderManager
框架的一百万个问题。 @CommonsWare 写了一篇关于它的 nice article - 在将 Loaders
集成到您的应用程序之前阅读它。
现在我通常使用 "managers" 从 SQLite 或互联网加载数据。我的"manager"的一般形式是这样的(请注意这些"managers"不是Singletons):
public class TutorialManager {
interface TutorialManagerListener {
void onDataFetchCompleted(SomeData data);
}
private BackgroundThreadPoster mBackgroundThreadPoster;
private MainThreadPoster mMainThreadPoster;
private Set<TutorialManagerListener> mListeners = new HashSet<>(1);
public TutorialManager(@NonNull BackgroundThreadPoster backgroundThreadPoster,
@NonNull MainThreadPoster mainThreadPoster) {
mBackgroundThreadPoster = backgroundThreadPoster;
mMainThreadPoster = mainThreadPoster;
}
@UiThread
public void registerListener(@NonNull TutorialManagerListener listener) {
mListeners.add(listener);
}
@UiThread
public void unregisterListener(@NonNull TutorialManagerListener listener) {
mListeners.remove(listener);
}
@UiThread
private void notifyFetchCompleted(SomeData data) {
for (TutorialManagerListener listener : mListeners) {
listener.onDataFetchCompleted(data);
}
}
/**
* Call to this method will fetch data on background thread and then notify all registered
* listeners about new data on UI thread.
*/
public void fetchData() {
mBackgroundThreadPoster.post(new Runnable() {
@Override
public void run() {
// logic that loads the data on background thread goes here
// final SomeData data = ... ;
mMainThreadPoster.post(new Runnable() {
@Override
public void run() {
notifyFetchCompleted(data);
}
});
}
});
}
}
实施MainThreadPoster
:
/**
* This class should be used in order to execute {@link Runnable} objects on UI thread
*/
public class MainThreadPoster {
private final Handler mMainHandler;
public MainThreadPoster() {
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* Post {@link Runnable} for execution on main thread
*/
public void post(Runnable runnable) {
mMainHandler.post(runnable);
}
}
BackgroundThreadPoster
的实现最多 you.I 通常使用以下简单实现(除非需要一些微调):
/**
* This class should be used in order to execute {@link Runnable} objects on background threads
*/
public class BackgroundThreadPoster {
private final ExecutorService mBackgroundExecutor;
public BackgroundThreadPoster() {
mBackgroundExecutor = Executors.newCachedThreadPool();
}
/**
* Post {@link Runnable} for execution on a random background thread
*/
public void post(Runnable runnable) {
mBackgroundExecutor.execute(runnable);
}
}
我将 MainThreadPoster
和 BackgroundThreadPoster
对象注入 "managers" 以便:
- 使"managers"单元可测试
- 对整个应用程序的后台线程执行集中管理和优化
Can anyone point me in the right direction, in implementing Loader the right way in my case here?
您必须像这样使用 AsyncTaskLoader:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<ObjectCategory>(getActivity()) {
@Override
public ObjectCategory loadInBackground() {
try {
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return objCategory;
}
@Override
protected void onStartLoading() {
forceLoad();
}
};
}
@Override
public void onLoadFinished(Loader<ObjectCategory> loader, ObjectCategory data) {
//do some stuff here
}
@Override
public void onLoaderReset(Loader<ObjectCategory> loader) {
//Yes, you can leave it empty
}
}