将加载器与 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 数据类型。

顺便说一句,我读过的关于装载机的教程是:

无论如何,这是我到目前为止所做的:

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 足以满足您的需求,那么您就可以了(尽管您的 ActivitiesFragments 会被 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);
    }

}

我将 MainThreadPosterBackgroundThreadPoster 对象注入 "managers" 以便:

  1. 使"managers"单元可测试
  2. 对整个应用程序的后台线程执行集中管理和优化

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
    }
}