简体   繁体   English

Android SQLite中的多线程处理呢?

[英]What about multithreading in Android SQLite?

In my app I've got to implement some UI and Sync service. 在我的应用程序中,我必须实现一些UI和同步服务。 It runs in the background and updates data. 它在后台运行并更新数据。 Sync service is not very simple, it uses multithreading. 同步服务不是很简单,它使用多线程。

So, here is my story: When I started developing this app I didn't know anything about sqlite, so I just didn't use any thread synchronization in Java. 所以,这就是我的故事:当我开始开发这个应用程序时,我对sqlite一无所知,所以我只是没有在Java中使用任何线程同步。 Result: I got many Exceptions like "SQLiteException: database is locked: BEGIN EXCLUSIVE;" 结果:我遇到了许多异常,例如“ SQLiteException:数据库已锁定:BEGIN EXCLUSIVE;”。

Then I synchronized all my transactions with regular Java synchronized () {} block. 然后,我将所有事务与常规Java sync(){}块进行同步。 Everything got much better. 一切都变得更好了。 But I used Cursors for implementing CursorAdapter for my lists. 但是我使用Cursors为列表实现了CursorAdapter。 So, sometimes I was getting the same "SQLiteException: database is locked: BEGIN EXCLUSIVE;" 因此,有时我会遇到相同的“ SQLiteException:数据库已锁定:BEGIN EXCLUSIVE;”。

I ended creating a small thread safe sqlite utility which handles all that thread-safe stuff. 我结束了创建一个处理所有线程安全问题的小线程安全sqlite实用程序。 Also I've got to use something like ArrayAdapter (read all data from Cursor and close it after reading, also synchronize this block) for my UI. 另外,我还必须为UI使用ArrayAdapter之类的东西(从Cursor读取所有数据,并在读取后关闭它,还同步此块)。 So, it is working OK 这样就可以了

But, I don't like such way to handle UI, cause UI got really slower with this solution - reading some amount of data from cursor is rather fast, but is slower than using CursorAdapter 但是,我不喜欢这种方式来处理UI,因为使用此解决方案会使UI变得非常慢-从游标中读取一些数据相当快,但比使用CursorAdapter慢

So, who got the solution for this question? 那么,谁得到了这个问题的解决方案? Thank you 谢谢

So, finally came out to the solution. 于是,终于出来了解决方案。 Here it is. 这里是。

I read some forums, google groups and found out that sqlite database should be opened only once. 我读了一些论坛,google小组,发现sqlite数据库只能打开一次。 So, I implemented this using singleton. 因此,我使用单例实现了这一点。

Also, I implemented some db code to synchronize all write operations (to prevent many threads execute write operations at one time). 另外,我实现了一些数据库代码来同步所有写操作(以防止许多线程一次执行写操作)。 And I don't care about opening cursors, reading from them. 而且我也不关心打开游标,从中读取信息。

After some days testing I've got no error reports from my users, so I think this works 经过几天的测试,我的用户没有收到任何错误报告,所以我认为这可行

In my previous work I opened sqlite database many times across the application, that was the problem. 在我以前的工作中,我在整个应用程序中多次打开了sqlite数据库,这就是问题所在。

SQLite implements a exclusive write lock, shared read lock model. SQLite实现了互斥写锁定,共享读锁定模型。 This means that you can have concurrent readers active in the database at the same time or a single writer, you can't have both. 这意味着您可以同时在数据库中同时激活多个并发读取器,也可以只有一个写入器,而不能同时拥有两者。 If you use the WAL logging feature you can have a single writer and multiple readers active in the database at the same time, but you still can't have more than one writer. 如果使用WAL日志记录功能,则可以同时在数据库中同时激活一个写入器和多个读取器,但是仍然不能有多个写入器。 There is excellent documentation on SQLite concurrency here and here . 这里这里都有关于SQLite并发性的出色文档。

You might want to consider taking a look at Berkeley DB. 您可能需要考虑看看Berkeley DB。 Berkeley DB offers a SQL API that is completely compatible with SQLite. Berkeley DB提供了与SQLite完全兼容的SQL API If fact, what we've done is to add the SQLite parser, planner and executor on top of the Berkeley DB storage layer. 如果确实如此,我们要做的就是在Berkeley DB存储层的顶部添加SQLite解析器,计划器和执行器。 What this provides to the SQLite application developer is a SQLite-compatible library that has additional scalability, concurrency and reliability (HA), as well as other features. 这为SQLite应用程序开发人员提供了一个与SQLite兼容的库,该库具有其他可伸缩性,并发性和可靠性(HA)以及其他功能。 Berkeley DB supports multiple readers and writes concurrently accessing the database. Berkeley DB支持多个读取器,并且可以并行写入来访问数据库。 There are two excellent white papers written by Mike Owens, author of "The Definitive Guide to SQLite" that compare Berkeley DB and SQLite ( Performance comparison , Behavioral Differences ). 有两本由《 SQLite权威指南》的作者Mike Owens撰写的优秀白皮书,它们比较了Berkeley DB和SQLite( 性能比较行为差异 )。

Disclaimer: I'm one of the product managers for Berkeley DB, so I'm a little biased. 免责声明:我是Berkeley DB的产品经理之一,所以我有点偏见。 But requests like yours (need more concurrency, scalability, reliability from SQLite) is exactly why we decided to provide a combined library that gives you the best of both worlds. 但是,像您这样的请求(需要更多的并发性,可伸缩性,SQLite的可靠性)才是我们决定提供组合的库的原因,这两个库可以为您提供最好的选择。

If you use only one singleton helper class to access the db you don't need to synchronize yourself and you can use the helper from multiple readers/writers because helper class manages synchronization itself. 如果使用一个单例助手类来访问数据库,则无需同步自己,并且可以从多个读取器/写入器中使用该助手,因为助手类本身管理同步。

Look at this post for mor detailed explanation 这篇文章以获得更多详细的解释

Use a singleton helper for opening connections. 使用单例助手打开连接。

1) Open as many readable connections as you want, and close then after your are done with it. 1)根据需要打开尽可能多的可读连接,并在完成后关闭。

2) For writable connections, you should open only a single writable connection. 2)对于可写连接,您应该只打开一个可写连接。

When attempting to open another writable connection, return the already opened connection. 尝试打开另一个可写连接时,请返回已打开的连接。 If no writable connection exists, open a new writable connection. 如果不存在可写连接,请打开一个新的可写连接。 Keep a writable_connection counter, and close the writable connection, when all threads are done with it. 当所有线程都处理完之后,保留一个writable_connection计数器,并关闭可写连接。

I am about to implement this, and will let you all know if it works when I am done. 我将要实现此目标,并在完成后让大家知道它是否有效。

Well, reading the documentation my previous answer seems incorrect. 好吧,阅读文档之前的答案似乎不正确。 I have a single(ton) SQLiteOpenHelper, so it seems all connections are the same. 我有一个(吨)SQLiteOpenHelper,所以似乎所有连接都是相同的。 Even the readable ones are the same as the writable connections. 即使是可读的也与可写的连接相同。

So I am going to skip calling getReadableDatabase, and use getWritableDatabase only. 因此,我将跳过调用getReadableDatabase的操作,仅使用getWritableDatabase。 Then I am going to keep a counter, to make sure the database get closed once and only once. 然后,我将保留一个计数器,以确保数据库关闭一次并且只有一次。

Since the SQLiteOpenHelper serializes the writes, everything should be fine this way. 由于SQLiteOpenHelper序列化了写入操作,因此一切都应如此。 I will just have to keep track of the connection, to make sure I dont keep one open at the end. 我将只需要跟踪连接,以确保最后不要保持打开状态。

So in my DbHelper class I now have: 所以在我的DbHelper类中,我现在有:

private int activeDatabaseCount = 0;
public synchronized SQLiteDatabase openDatabase() {
    SQLiteDatabase connection = getWritableDatabase(); // always returns the same connection instance
    activeDatabaseCount++;
    return connection; 
}
public synchronized void closeDatabase(SQLiteDatabase connection) {
    activeDatabaseCount--;
    if (activeDatabaseCount == 0) {
        if (connection != null) {
            if (connection.isOpen()) {
                connection.close();
            }
        }
    }
}

Ciaoo: Here the solution: Ciaoo:这是解决方案:

I tried to explain everything in the code . 我试图解释代码中的所有内容。

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ScrollView;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

class HelpUI {

   final static class GetDataFromDBInAsyncMode extends AsyncTask<Object, Object, Object> {

      private Context context;
      private String SQL;
      private final int xMin = -X_App.ScreenHeight; // put here your screen height (get it from code)
      private final int xMax = X_App.ScreenHeight * 2; // put here your screen height (get it from code)

      // this is the main view witch go to have all views
      // Use this in onProgressUpdate to add other vies you get in runtime when you get data from database
      private ViewGroup view_container;

      // if you have a scrollview hide views witch isnot visible to user
      private ScrollView scroll_view_container;

      // this workaround make free processors in measureaments, your UI is more fluent
      final ViewTreeObserver.OnScrollChangedListener onScrollChangedListener = () -> {

         if (view_container == null || view_container.getChildCount() == 0) { return; }

         int scrollY = scroll_view_container.getScrollY();

         for (int i = 0; i < view_container.getChildCount(); i++) {

            final View current = view_container.getChildAt(i);
            final int topView = current.getTop(); //container_views.getChildAt(i).getTop();
            final int diffTop = topView - scrollY;

            if ((diffTop > xMin) && (diffTop < xMax)) {
               current.setVisibility(View.VISIBLE);
            } else {

               current.setVisibility(View.INVISIBLE);
            }

         }

      };

      // constructor
      GetDataFromDBInAsyncMode(Context ctx, String mSQL) {
         this.context = ctx;
         this.SQL = mSQL;

         // put here the id of scrollViewContainer
         scroll_view_container = X_App.getRootV().findViewById(R.id.scroll_view_container);
         if (scroll_view_container != null) {
            // add listener on scroll
            scroll_view_container.getViewTreeObserver().addOnScrollChangedListener(onScrollChangedListener);
         }

      }


      @Override
      protected Object doInBackground(Object... objects) {


         // All dirty things go to being in background
         // Your cursor
         final Cursor cursor = X_SqLite.get(X_App.getContext()).GeDataAsCursor(SQL);

         if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {

            // The magic part
            ScheduledExecutorService giveMeAsync = Executors.newSingleThreadScheduledExecutor();

            // Give time to processor to do her tasks and do you dirty task here
            // 50 millisec is enough per a single task
            giveMeAsync.scheduleAtFixedRate(() -> {
               ViewGroup viewInflated = ((Activity) this.context).findViewById(R.id.icon_view);

               // Give your data from Database
               // Do here all your things but take care some of these need to be done on UI thread (like adding view etc, you can do that on onProgressUpdate)
               final String data1 = cursor.getString(cursor.getColumnIndex("data1"));
               viewInflated.setTag(data1);

               // Send this to UI thread
               publishProgress(viewInflated, false);

               // Here test to cursor if is finish or not
               if (!cursor.moveToNext()) {
                  giveMeAsync.shutdownNow();
                  cursor.close();
                  publishProgress(null, true);
               }

            }, 1, 50, TimeUnit.MILLISECONDS);


         }

         // otherwise if cursor is emty close them
         if (cursor != null && cursor.getCount() == 0) {
            cursor.close();
            publishProgress(null, true);
         }


         return null;

      }


      @Override
      protected void onProgressUpdate(Object... values) {
         final View viewInflated = (View) values[0];
         final boolean isCursorEnded = (Boolean) values[0];

         // Here you is in main thread
         // You can do all what you do with the viewInflated


         if (isCursorEnded) {
            //raise your event to tell to your app reading data is finished
         }

      }


   }


}

Usage: 用法:

import android.app.Activity;
import android.os.Bundle;

class MyActivity extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      new HelpUI.GetDataFromDBInAsyncMode(this, "Select * FROM YourTable").execute();
   }
} 

Change variables with your variables/objects 使用变量/对象更改变量

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM