Android SQLite DB Khi nào đóng


96

Tôi đang làm việc với cơ sở dữ liệu SQLite trên Android. Trình quản lý cơ sở dữ liệu của tôi là một singleton và ngay bây giờ sẽ mở một kết nối đến cơ sở dữ liệu khi nó được khởi tạo. Có an toàn không khi để cơ sở dữ liệu mở toàn bộ thời gian để khi ai đó gọi lớp của tôi làm việc với cơ sở dữ liệu thì nó đã được mở? Hay tôi có nên mở và đóng cơ sở dữ liệu trước và sau mỗi lần cần truy cập. Có tác hại gì khi để nó mở suốt thời gian không?

Cảm ơn!

Câu trả lời:


60

tôi sẽ giữ nó mở toàn bộ thời gian và đóng nó trong một số phương pháp vòng đời chẳng hạn như onStophoặc onDestroy. theo cách đó, bạn có thể dễ dàng kiểm tra xem cơ sở dữ liệu đã được sử dụng chưa bằng cách gọi isDbLockedByCurrentThreadhoặc isDbLockedByOtherThreadstrênSQLiteDatabase đối tượng mỗi lần trước khi bạn sử dụng nó. điều này sẽ ngăn chặn nhiều thao tác đối với cơ sở dữ liệu và cứu ứng dụng của bạn khỏi sự cố có thể xảy ra

vì vậy trong singleton của bạn, bạn có thể có một phương thức như thế này để lấy SQLiteOpenHelperđối tượng duy nhất của bạn :

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

vì vậy bất cứ khi nào bạn muốn sử dụng đối tượng trình trợ giúp đang mở của mình, hãy gọi phương thức getter này (đảm bảo rằng nó được phân luồng)

một phương thức khác trong singleton của bạn có thể được (được gọi MỌI LÚC trước khi bạn thử gọi getter ở trên):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

bạn cũng có thể muốn đóng cơ sở dữ liệu trong singleton:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

nếu người dùng ứng dụng của bạn có khả năng tạo nhiều tương tác với cơ sở dữ liệu rất nhanh, bạn nên sử dụng một cái gì đó như tôi đã trình bày ở trên. nhưng nếu có ít tương tác với cơ sở dữ liệu, tôi sẽ không lo lắng về điều đó, và chỉ cần tạo và đóng cơ sở dữ liệu mỗi lần.


Tôi có thể tăng tốc độ truy cập cơ sở dữ liệu bằng hệ số 2 với cách tiếp cận này (cơ sở dữ liệu mở giữ), nhờ
teh.fonsi

2
Quay là một kỹ thuật rất tệ. Xem câu trả lời của tôi dưới đây.
mixel

1
@mixel điểm tốt. Tôi tin rằng tôi đã đăng câu trả lời này trước khi API 16 có sẵn, nhưng tôi có thể sai
james

1
@binnyb Tôi nghĩ tốt hơn nên cập nhật câu trả lời của bạn để nó không gây hiểu lầm cho mọi người.
mixel

3
WARN isDbLockedByOtherThreads () là Depricated và trả về false kể từ API 16. Ngoài ra việc kiểm tra isDbLockedByCurrentThread () sẽ tạo ra vòng lặp vô hạn vì nó dừng luồng hiện tại và không gì có thể 'mở khóa DB' để phương thức này trả về true.
matreshkin

20

Hiện tại không cần phải kiểm tra xem cơ sở dữ liệu có bị khóa bởi một luồng khác hay không. Trong khi bạn sử dụng singleton SQLiteOpenHelper trong mọi luồng, bạn vẫn an toàn. Từ isDbLockedByCurrentThreadtài liệu:

Tên của phương thức này xuất phát từ thời điểm khi có một kết nối hoạt động với cơ sở dữ liệu có nghĩa là luồng đang giữ một khóa thực sự trên cơ sở dữ liệu. Ngày nay, không còn "khóa cơ sở dữ liệu" thực sự nữa mặc dù các luồng có thể chặn nếu chúng không thể có được kết nối cơ sở dữ liệu để thực hiện một thao tác cụ thể.

isDbLockedByOtherThreads không được dùng nữa kể từ API Cấp 16.


Không có ích gì khi sử dụng một phiên bản cho mỗi luồng. SQLiteOpenHelper là luồng an toàn. Đây cũng là bộ nhớ rất kém hiệu quả. Thay vào đó, một ứng dụng nên giữ một phiên bản SQLiteOpenHelper cho mỗi cơ sở dữ liệu. Để đồng thời tốt hơn, bạn nên sử dụng WriteAheadLogging cung cấp tính năng tổng hợp kết nối cho tối đa 4 kết nối.
ejboy

@ejboy Ý tôi là vậy. "Sử dụng singleton (= một) SQLiteOpenHelper trong mọi luồng", không phải "mỗi luồng".
mixel

15

Về các câu hỏi:

Trình quản lý cơ sở dữ liệu của tôi là một singleton và ngay bây giờ sẽ mở một kết nối đến cơ sở dữ liệu khi nó được khởi tạo.

Chúng ta nên chia 'mở DB', 'mở một kết nối'. SQLiteOpenHelper.getWainedDatabase () cung cấp một cơ sở dữ liệu đã mở. Nhưng chúng tôi không phải kiểm soát các kết nối vì nó được thực hiện trong nội bộ.

Có an toàn không khi để cơ sở dữ liệu mở toàn bộ thời gian để khi ai đó gọi lớp của tôi làm việc với cơ sở dữ liệu thì nó đã được mở?

Vâng, đúng vậy. Kết nối không bị treo nếu các giao dịch được đóng đúng cách. Lưu ý rằng DB của bạn cũng sẽ tự động bị đóng nếu GC hoàn thiện nó.

Hay tôi có nên mở và đóng cơ sở dữ liệu trước và sau mỗi lần cần truy cập.

Việc đóng phiên bản SQLiteDatabase không mang lại hiệu quả gì ngoại trừ việc đóng các kết nối nhưng đây là điều không tốt của nhà phát triển nếu có một số kết nối tại thời điểm này. Ngoài ra, sau SQLiteDatabase.close (), SQLiteOpenHelper.getWainedDatabase () sẽ trả về một phiên bản mới.

Có tác hại gì khi để nó mở suốt thời gian không?

Không, không có. Cũng lưu ý rằng việc đóng DB vào một thời điểm không liên quan và chuỗi chẳng hạn trong Activity.onStop () có thể đóng các kết nối đang hoạt động và để dữ liệu ở trạng thái không nhất quán.


Cảm ơn bạn, sau khi đọc những lời của bạn "sau SQLiteDatabase.close (), SQLiteOpenHelper.getWainstDatabase () sẽ trả về một phiên bản mới" Tôi nhận ra rằng cuối cùng tôi đã có câu trả lời cho một vấn đề cũ của ứng dụng của mình. Đối với một sự cố, trở nên nghiêm trọng sau khi chuyển sang Android 9 (xem stackoverflow.com/a/54224922/297710 )
yvolk


1

Từ quan điểm hiệu suất, cách tối ưu là giữ một phiên bản SQLiteOpenHelper duy nhất ở cấp ứng dụng. Việc mở cơ sở dữ liệu có thể tốn kém và là một thao tác chặn, vì vậy không nên thực hiện nó trên luồng chính và / hoặc trong các phương thức vòng đời hoạt động.

setIdleConnectionTimeout ()Phương thức (được giới thiệu trong Android 8.1) có thể được sử dụng để giải phóng RAM khi cơ sở dữ liệu không được sử dụng. Nếu thời gian chờ nhàn rỗi được đặt, (các) kết nối cơ sở dữ liệu sẽ bị đóng sau một thời gian không hoạt động, tức là khi cơ sở dữ liệu không được truy cập. Các kết nối sẽ được mở lại một cách minh bạch với ứng dụng khi một truy vấn mới được thực thi.

Ngoài ra, một ứng dụng có thể gọi releaseMemory () khi nó ở chế độ nền hoặc phát hiện áp lực bộ nhớ, ví dụ: trong onTrimMemory ()


0

Bạn cũng có thể sử dụng ContentProvider. Nó sẽ làm điều này cho bạn.


-9

Tạo ngữ cảnh Ứng dụng của riêng bạn, sau đó mở và đóng cơ sở dữ liệu từ đó. Đối tượng đó cũng có một phương thức OnTermina () mà bạn có thể sử dụng để đóng kết nối. Tôi chưa thử nhưng có vẻ là một cách tiếp cận tốt hơn.

@binnyb: Tôi không thích sử dụng finalize () để đóng kết nối. Có thể làm việc, nhưng từ những gì tôi hiểu viết mã trong một phương thức Java finalize () là một ý tưởng tồi.


Bạn không muốn dựa vào việc hoàn thành bởi vì bạn không bao giờ biết khi nào nó sẽ được gọi. Một đối tượng có thể ở trong tình trạng lấp lửng cho đến khi GC quyết định xóa nó và chỉ khi đó finalize () mới được gọi. Đó là lý do tại sao nó vô dụng. Cách chính xác để làm điều đó là có một phương thức được gọi khi đối tượng không còn được sử dụng nữa (bất kể có được thu gom rác hay không) và đó chính xác là onTermina ().
erwan 29/12/12

5
Documents nói khá rõ ràng rằng onTerminatesẽ không được gọi trong một môi trường sản xuất - developer.android.com/reference/android/app/...
Eliezer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.