【问题标题】:Using Loaders with sqlite query that returns object将 Loaders 与返回对象的 sqlite 查询一起使用
【发布时间】:2016-11-01 17:39:12
【问题描述】:

这是我在从数据库中获取数据时一直在做的事情——我的片段将调用一个 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?如果可能的话,我不想过多地更改 DbControl 类中的代码——尤其是其中的函数的返回数据类型。

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

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

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

【问题讨论】:

    标签: android android-sqlite loader


    【解决方案1】:

    谁能指出我正确的方向,在我这里以正确的方式实施 Loader?

    你必须像这样使用 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
        }
    }
    

    【讨论】:

    • 我尝试使用您的方法,但未调用 loadInBackground 中的代码。我按照这里的建议添加了 forceload stackoverflow.com/questions/8606048/asynctaskloader-doesnt-run,但这似乎不是一个好主意。
    • 好的,我添加了@Override protected void onStartLoading() { if (takeContentChanged()) forceLoad(); } @Override protected void onStopLoading() { cancelLoad(); },现在可以使用了。
    【解决方案2】:

    我会说 - 不要使用 Loaders。当我刚接触 Android 时,我在我的应用程序中使用了CursorLoader,结果证明这是一个错误的决定。

    如果CursorLoader 足以满足您的需求,那么您就可以了(尽管您的ActivitiesFragments 会被数据库相关逻辑污染),但是一旦您尝试实现自己的Loader,那如果应该封装 DB 相关逻辑并只返回一个构造对象,那么您将不可避免地遇到 LoaderManager 框架的一百万个问题。 @CommonsWare 写了一篇关于它的 nice article - 在将 Loaders 集成到您的应用程序之前阅读它。

    现在我通常使用“管理器”从 SQLite 或 Internet 加载数据。我的“经理”的一般形式是这样的(请注意,这些“经理”不是单例):

    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 的实现由你决定。我通常使用以下简单的实现(除非需要一些微调):

    /**
     * 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 对象注入“经理”,以便:

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

    【讨论】:

    • 哇,感谢您对 Loaders 和另一种方法的另一种看法。我可能稍后再看。顺便说一句,IINM 我读过一些地方,暗示 RxJava 是另一种比 Loaders 更好的方法……你想过这个吗?
    • @imin,我不是 RxJava 方面的专家,但我认为它不仅仅是加载器的替代品——它是完全不同的应用程序架构风格。据我了解,RxJava 将有点类似于我在这个答案中描述的“管理器”方法——它基于 Observables 和 Notifications(又名监听器)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-21
    • 1970-01-01
    • 2019-01-07
    • 2014-07-12
    相关资源
    最近更新 更多