【问题标题】:How do I properly execute database calls on other threads?如何在其他线程上正确执行数据库调用?
【发布时间】:2016-10-03 15:40:06
【问题描述】:

我真的很困惑我应该如何在我的 Android 应用程序中使用线程来进行数据库交互。资源太多,不知道选哪个。因此,我希望针对我的具体情况获得更具体、更有针对性的建议,这样我就有了一个起点。

这是我的数据库类结构,到目前为止效果很好:

public class DatabaseHelper extends SQLiteOpenHelper {
    private static volatile SQLiteDatabase mDatabase;
    private static DatabaseHelper mInstance = null;
    private static Context mContext;

    private static final String DB_NAME = "database.db";
    private static final int DB_VERSION = 1;

    private static final DB_CREATE_THINGY_TABLE = "CREATE TABLE blahblahblah...";
    //other various fields here, omitted

    public static synchronized DatabaseHelper getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new DatabaseHelper(context.getApplicationContext());
            try {
                mInstance.open();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return mInstance;
    }

    private DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DB_CREATE_THINGY_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 

    }

    @Override
    public void onConfigure(SQLiteDatabase db){
        super.onConfigure(db);
        db.setForeignKeyConstraintsEnabled(true);
    }

    public void open() throws SQLException {
        mDatabase = getWritableDatabase();
    }

    public void close() {
        mDatabase.close();
    }

    public long addNewThingy(String name) {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.THINGY_COLUMN_NAME, name);
        return mDatabase.insertWithOnConflict(DatabaseHelper.THINGY_TABLE, null, values, SQLiteDatabase.CONFLICT_IGNORE);
    }

    public Thingy getThingyById(long id) {
        Cursor cursor = mDatabase.query(
                DatabaseHelper.THINGY_TABLE, // table
                new String[]{DatabaseHelper.THINGY_COLUMN_ID, DatabaseHelper.THINGY_COLUMN_NAME}, // column names
                DatabaseHelper.THINGY_COLUMN_ID + " = ?", // where clause
                new String[]{id + ""}, // where params
                null, // groupby
                null, // having
                null);  // orderby
        cursor.moveToFirst();
        Thingy thingy = null;
        if (!cursor.isAfterLast()) {
            String name = getStringFromColumnName(cursor, DatabaseHelper.THINGY_COLUMN_NAME);
            thingy = new Thingy(id, name);
            cursor.moveToNext();
        }
        cursor.close();
        return thingy;
    }

}

所以每当我想访问数据库时,我都会这样做mDatabaseHelper = DatabaseHelper.getInstance(context);,我很高兴。我没有对open()close() 或类似的东西进行任何明确的调用。但是现在我在 UI 线程上进行所有数据库调用,我相信(在我的 onCreate 或 onCreateView 方法或不调用任何新线程或任何东西的单独方法中)。

如何正确地使这个线程化,这样我就不会在 UI 线程上执行数据库操作?

我认为我必须更改所有数据库调用才能基本上做到这一点:

  1. 首先对我的数据库类进行任何必要的编辑,以确保它在多个线程尝试同时执行操作的情况下能够正常工作。我已经尝试过让我的班级成为单例(无论如何我认为它是单例?)并使用诸如“易失性”和“同步”之类的关键字,但也许我在某处遗漏了一些东西。

  2. 在自己的线程中执行数据库操作。

  3. 以某种方式在适当的函数/活动/片段中触发额外的代码,一旦数据库操作完成,这些代码将执行。

  4. 让整个过程足够灵活,让我可以在任何地方进行。

我说得有道理吗?这是处理这一切的正确方法吗?您是否可以制作一个简单的示例来向我展示如何使用正确的线程从示例活动/片段/等中正确执行mThingy = mDatabaseHelper.getThingyById(id);mDatabaseHelper.addNewThingy(someName); 之类的操作?

【问题讨论】:

  • 如果需要几毫秒,我认为在主线程上执行 mThingy = mDatabaseHelper.getThingyById(id);mDatabaseHelper.addNewThingy(someName); 没有任何问题。安卓也不行。与网络操作相反,平台不会阻止主线程上的数据库操作。不过,如果您在其他线程上需要它,请使用 AsyncTask
  • 我知道,在这个简单的例子中,它在主线程上已经足够快了,但对于其他函数,我使用更复杂的标准检索整个对象列表,因此可能需要更多时间。在应用程序的其他领域,我还进行文件操作(涉及位图/重新缩放等),所以我仍然想了解如何正确处理这些。
  • 我确实研究了 AsyncTask,但我不明白如何在我的情况下正确使用它。我也不明白参数 AsyncTask 和看起来像 Int... 等的奇怪参数。

标签: java android database multithreading sqlite


【解决方案1】:

使用线程的简单解决方案

public class DatabaseHelper extends SQLiteOpenHelper {
    //...

    public void addNewThingyAsync(final String name, final Callback<Long> cb) {
        new Thread(new Runnable(){
            @Override
            public void run(){
                cb.callback(addNewThingy(name));
            }
        }).start();
    }

    private synchronized long addNewThingy(String name){
        //implementation...
    }

    public void getThingyByIdAsync(final long id, final Callback<Thingy> cb) {
        new Thread(new Runnable(){
            @Override
            public void run(){
                cb.callback(getThingyById(id));
            }
        }).start();
    }

    private synchronized Thingy getThingyById(long id) {
        //implementation...
    }

    public interface Callback<T> {
        public void callback(T t);
    }
}

使用 AsyncTasks 的解决方案

与上述相同,但有以下变化:

public class DatabaseHelper extends SQLiteOpenHelper {
    //...

    public void addNewThingyAsync(final String name, final Callback<Long> cb) {
        new AsyncTask<Void, Void, Long>(){
            @Override
            protected Long doInBackground(Void... ignoredParams) {
                return addNewThingy(name);
            }

            @Override
            protected void onPostExecute(Long result) {
                cb.callback(result);
            }
        }.execute();
    }

    //...

    public void getThingyByIdAsync(final long id, final Callback<Thingy> cb) {
        new AsyncTask<Void, Void, Thingy>(){
            @Override
            protected Thingy doInBackground(Void... ignoredParams) {
                return getThingyById(id);
            }

            @Override
            protected void onPostExecute(Thingy result) {
                cb.callback(result);
            }
        }.execute();
    }
    //...
}

调用(适用于两种方法)

long mId = ...; 
mDatabaseHelper = DatabaseHelper.getInstance(context);
mDatabaseHelper.getThingyByIdAsync(mId, new Callback<Thingy>{
    @Override
    public void callback(Thingy thingy){
        //do whatever you want to do with thingy
    }
});

【讨论】:

  • 我不熟悉这个Callback 类——如何从外部Activity/fragment/etc 调用这些异步方法?
  • 看到双重callback.callback我想我也很困惑
  • 我提供了一个示例调用并重命名了参数以防止混淆。
  • 那么你有什么理由这样做而不是使用AsyncTask? (出于好奇)您的方法是否更通用/通用?
  • 在回调中,你可能需要在“做任何你想做的事”之前显式返回到主线程,因为它在另一个线程上运行。
【解决方案2】:

我将如何正确地制作这个线程,这样我就不会执行 UI线程上的数据库操作?

只需在 UI 线程之外执行任何数据库操作。一种常见的技术涉及AsyncTask。例如:

public class GetThingyTask extends AsyncTask<Long, Void, Thingy> {

    private Context context;

    public AddTask(Context context){
        this.context = context;
    }

    @Override
    protected Thingy doInBackground(Long... ids) {

        final long id = ids[0];

        Cursor cursor = DatabaseHelper.getInstance(context).query(
            DatabaseHelper.THINGY_TABLE,
            new String[]{
                DatabaseHelper.THINGY_COLUMN_ID,
                DatabaseHelper.THINGY_COLUMN_NAME
            },
            DatabaseHelper.THINGY_COLUMN_ID + "=?",
            new String[]{String.valueOf(id)},
            null, null, null);

        String name = null;

        if (cursor.moveToFirst() && (cursor.getCount() > 0)) {
            name = getStringFromColumnName(cursor, DatabaseHelper.THINGY_COLUMN_NAME);
        }

        cursor.close();

        return new Thingy(id, name);

    }

    @Override
    protected void onPostExecute(Thingy thingy) {
        //Broadcast the Thingy somehow. EventBus is a good choice.
    }

}

并使用它(例如,在Activity 内部):

new GetThingyTask(this).execute(id);

【讨论】:

  • 请注意,此代码不是线程安全的。不能保证调用的发生前关系。
  • &lt;Long, Void, Thingy&gt; 是什么意思? Long... ids 是什么意思?
  • @F43nd1r 不确定我是否遵循 - 这不会被 DatabaseHelper.getInstance()synchronized 的事实所抵消吗?
  • The happens-before relationship of calls is not guaranteed 是什么意思?
  • @PPartisan 仅调用在同一对象上同步的方法是线程安全的。对象本身不能是线程安全的,只有对它的调用。
猜你喜欢
  • 2021-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多