mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-29 12:45:37 +08:00
AsyncStorage improvements
Differential Revision: D2475604 committer: Service User <svcscm@fb.com>
This commit is contained in:
committed by
facebook-github-bot-9
parent
7615d74d14
commit
33cc607c1f
@@ -24,9 +24,9 @@ import com.facebook.react.bridge.ReadableArray;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.KEY_COLUMN;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.KEY_COLUMN;
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.TABLE_CATALYST;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.TABLE_CATALYST;
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.VALUE_COLUMN;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.VALUE_COLUMN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for database operations.
|
* Helper for database operations.
|
||||||
|
|||||||
@@ -9,16 +9,12 @@
|
|||||||
|
|
||||||
package com.facebook.react.modules.storage;
|
package com.facebook.react.modules.storage;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteStatement;
|
import android.database.sqlite.SQLiteStatement;
|
||||||
|
|
||||||
import com.facebook.common.logging.FLog;
|
import com.facebook.common.logging.FLog;
|
||||||
import com.facebook.infer.annotation.Assertions;
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.Callback;
|
import com.facebook.react.bridge.Callback;
|
||||||
import com.facebook.react.bridge.GuardedAsyncTask;
|
import com.facebook.react.bridge.GuardedAsyncTask;
|
||||||
@@ -31,18 +27,19 @@ import com.facebook.react.common.ReactConstants;
|
|||||||
import com.facebook.react.common.SetBuilder;
|
import com.facebook.react.common.SetBuilder;
|
||||||
import com.facebook.react.modules.common.ModuleDataCleaner;
|
import com.facebook.react.modules.common.ModuleDataCleaner;
|
||||||
|
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.KEY_COLUMN;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.KEY_COLUMN;
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.TABLE_CATALYST;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.TABLE_CATALYST;
|
||||||
import static com.facebook.react.modules.storage.CatalystSQLiteOpenHelper.VALUE_COLUMN;
|
import static com.facebook.react.modules.storage.ReactDatabaseSupplier.VALUE_COLUMN;
|
||||||
|
|
||||||
public final class AsyncStorageModule
|
public final class AsyncStorageModule
|
||||||
extends ReactContextBaseJavaModule implements ModuleDataCleaner.Cleanable {
|
extends ReactContextBaseJavaModule implements ModuleDataCleaner.Cleanable {
|
||||||
|
|
||||||
private @Nullable SQLiteDatabase mDb;
|
private ReactDatabaseSupplier mReactDatabaseSupplier;
|
||||||
private boolean mShuttingDown = false;
|
private boolean mShuttingDown = false;
|
||||||
|
|
||||||
public AsyncStorageModule(ReactApplicationContext reactContext) {
|
public AsyncStorageModule(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
mReactDatabaseSupplier = new ReactDatabaseSupplier(reactContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,10 +56,6 @@ public final class AsyncStorageModule
|
|||||||
@Override
|
@Override
|
||||||
public void onCatalystInstanceDestroy() {
|
public void onCatalystInstanceDestroy() {
|
||||||
mShuttingDown = true;
|
mShuttingDown = true;
|
||||||
if (mDb != null && mDb.isOpen()) {
|
|
||||||
mDb.close();
|
|
||||||
mDb = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,10 +67,17 @@ public final class AsyncStorageModule
|
|||||||
new Callback() {
|
new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void invoke(Object... args) {
|
public void invoke(Object... args) {
|
||||||
if (args.length > 0) {
|
if (args.length == 0) {
|
||||||
throw new RuntimeException("Clearing AsyncLocalStorage failed: " + args[0]);
|
FLog.d(ReactConstants.TAG, "Cleaned AsyncLocalStorage.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
FLog.d(ReactConstants.TAG, "Cleaned AsyncLocalStorage.");
|
// Clearing the database has failed, delete it instead.
|
||||||
|
if (mReactDatabaseSupplier.deleteDatabase()) {
|
||||||
|
FLog.d(ReactConstants.TAG, "Deleted Local Database AsyncLocalStorage.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Everything failed, crash the app
|
||||||
|
throw new RuntimeException("Clearing and deleting database failed: " + args[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ public final class AsyncStorageModule
|
|||||||
String[] columns = {KEY_COLUMN, VALUE_COLUMN};
|
String[] columns = {KEY_COLUMN, VALUE_COLUMN};
|
||||||
HashSet<String> keysRemaining = SetBuilder.newHashSet();
|
HashSet<String> keysRemaining = SetBuilder.newHashSet();
|
||||||
WritableArray data = Arguments.createArray();
|
WritableArray data = Arguments.createArray();
|
||||||
Cursor cursor = Assertions.assertNotNull(mDb).query(
|
Cursor cursor = mReactDatabaseSupplier.get().query(
|
||||||
TABLE_CATALYST,
|
TABLE_CATALYST,
|
||||||
columns,
|
columns,
|
||||||
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
|
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
|
||||||
@@ -171,8 +171,8 @@ public final class AsyncStorageModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);";
|
String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);";
|
||||||
SQLiteStatement statement = Assertions.assertNotNull(mDb).compileStatement(sql);
|
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
|
||||||
mDb.beginTransaction();
|
mReactDatabaseSupplier.get().beginTransaction();
|
||||||
try {
|
try {
|
||||||
for (int idx=0; idx < keyValueArray.size(); idx++) {
|
for (int idx=0; idx < keyValueArray.size(); idx++) {
|
||||||
if (keyValueArray.getArray(idx).size() != 2) {
|
if (keyValueArray.getArray(idx).size() != 2) {
|
||||||
@@ -193,12 +193,12 @@ public final class AsyncStorageModule
|
|||||||
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
|
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
|
||||||
statement.execute();
|
statement.execute();
|
||||||
}
|
}
|
||||||
mDb.setTransactionSuccessful();
|
mReactDatabaseSupplier.get().setTransactionSuccessful();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e);
|
FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e);
|
||||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
||||||
} finally {
|
} finally {
|
||||||
mDb.endTransaction();
|
mReactDatabaseSupplier.get().endTransaction();
|
||||||
}
|
}
|
||||||
callback.invoke();
|
callback.invoke();
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ public final class AsyncStorageModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Assertions.assertNotNull(mDb).delete(
|
mReactDatabaseSupplier.get().delete(
|
||||||
TABLE_CATALYST,
|
TABLE_CATALYST,
|
||||||
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
|
AsyncLocalStorageUtil.buildKeySelection(keys.size()),
|
||||||
AsyncLocalStorageUtil.buildKeySelectionArgs(keys));
|
AsyncLocalStorageUtil.buildKeySelectionArgs(keys));
|
||||||
@@ -250,7 +250,7 @@ public final class AsyncStorageModule
|
|||||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Assertions.assertNotNull(mDb).beginTransaction();
|
mReactDatabaseSupplier.get().beginTransaction();
|
||||||
try {
|
try {
|
||||||
for (int idx = 0; idx < keyValueArray.size(); idx++) {
|
for (int idx = 0; idx < keyValueArray.size(); idx++) {
|
||||||
if (keyValueArray.getArray(idx).size() != 2) {
|
if (keyValueArray.getArray(idx).size() != 2) {
|
||||||
@@ -269,19 +269,19 @@ public final class AsyncStorageModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!AsyncLocalStorageUtil.mergeImpl(
|
if (!AsyncLocalStorageUtil.mergeImpl(
|
||||||
mDb,
|
mReactDatabaseSupplier.get(),
|
||||||
keyValueArray.getArray(idx).getString(0),
|
keyValueArray.getArray(idx).getString(0),
|
||||||
keyValueArray.getArray(idx).getString(1))) {
|
keyValueArray.getArray(idx).getString(1))) {
|
||||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mDb.setTransactionSuccessful();
|
mReactDatabaseSupplier.get().setTransactionSuccessful();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
FLog.w(ReactConstants.TAG, e.getMessage(), e);
|
||||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
||||||
} finally {
|
} finally {
|
||||||
mDb.endTransaction();
|
mReactDatabaseSupplier.get().endTransaction();
|
||||||
}
|
}
|
||||||
callback.invoke();
|
callback.invoke();
|
||||||
}
|
}
|
||||||
@@ -296,12 +296,12 @@ public final class AsyncStorageModule
|
|||||||
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
|
||||||
@Override
|
@Override
|
||||||
protected void doInBackgroundGuarded(Void... params) {
|
protected void doInBackgroundGuarded(Void... params) {
|
||||||
if (!ensureDatabase()) {
|
if (!mReactDatabaseSupplier.ensureDatabase()) {
|
||||||
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Assertions.assertNotNull(mDb).delete(TABLE_CATALYST, null, null);
|
mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FLog.w(ReactConstants.TAG, "Exception in database clear ", e);
|
FLog.w(ReactConstants.TAG, "Exception in database clear ", e);
|
||||||
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
|
||||||
@@ -325,7 +325,7 @@ public final class AsyncStorageModule
|
|||||||
}
|
}
|
||||||
WritableArray data = Arguments.createArray();
|
WritableArray data = Arguments.createArray();
|
||||||
String[] columns = {KEY_COLUMN};
|
String[] columns = {KEY_COLUMN};
|
||||||
Cursor cursor = Assertions.assertNotNull(mDb)
|
Cursor cursor = mReactDatabaseSupplier.get()
|
||||||
.query(TABLE_CATALYST, columns, null, null, null, null, null);
|
.query(TABLE_CATALYST, columns, null, null, null, null, null);
|
||||||
try {
|
try {
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
@@ -345,25 +345,9 @@ public final class AsyncStorageModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify the database exists and is open.
|
* Verify the database is open for reads and writes.
|
||||||
*/
|
*/
|
||||||
private boolean ensureDatabase() {
|
private boolean ensureDatabase() {
|
||||||
if (mShuttingDown) {
|
return !mShuttingDown && mReactDatabaseSupplier.ensureDatabase();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mDb != null && mDb.isOpen()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
mDb = initializeDatabase();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and/or open the database.
|
|
||||||
*/
|
|
||||||
private SQLiteDatabase initializeDatabase() {
|
|
||||||
CatalystSQLiteOpenHelper helperForDb =
|
|
||||||
new CatalystSQLiteOpenHelper(getReactApplicationContext());
|
|
||||||
return helperForDb.getWritableDatabase();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.react.modules.storage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
|
|
||||||
// VisibleForTesting
|
|
||||||
public class CatalystSQLiteOpenHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
// VisibleForTesting
|
|
||||||
public static final String DATABASE_NAME = "RKStorage";
|
|
||||||
static final int DATABASE_VERSION = 1;
|
|
||||||
|
|
||||||
static final String TABLE_CATALYST = "catalystLocalStorage";
|
|
||||||
static final String KEY_COLUMN = "key";
|
|
||||||
static final String VALUE_COLUMN = "value";
|
|
||||||
|
|
||||||
static final String VERSION_TABLE_CREATE =
|
|
||||||
"CREATE TABLE " + TABLE_CATALYST + " (" +
|
|
||||||
KEY_COLUMN + " TEXT PRIMARY KEY, " +
|
|
||||||
VALUE_COLUMN + " TEXT NOT NULL" +
|
|
||||||
")";
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
public CatalystSQLiteOpenHelper(Context context) {
|
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
db.execSQL(VERSION_TABLE_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
// TODO: t5494781 implement data migration
|
|
||||||
if (oldVersion != newVersion) {
|
|
||||||
mContext.deleteDatabase(DATABASE_NAME);
|
|
||||||
onCreate(db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.modules.storage;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
// VisibleForTesting
|
||||||
|
public class ReactDatabaseSupplier extends SQLiteOpenHelper {
|
||||||
|
|
||||||
|
// VisibleForTesting
|
||||||
|
public static final String DATABASE_NAME = "RKStorage";
|
||||||
|
static final int DATABASE_VERSION = 1;
|
||||||
|
private static final int SLEEP_TIME_MS = 30;
|
||||||
|
|
||||||
|
static final String TABLE_CATALYST = "catalystLocalStorage";
|
||||||
|
static final String KEY_COLUMN = "key";
|
||||||
|
static final String VALUE_COLUMN = "value";
|
||||||
|
|
||||||
|
static final String VERSION_TABLE_CREATE =
|
||||||
|
"CREATE TABLE " + TABLE_CATALYST + " (" +
|
||||||
|
KEY_COLUMN + " TEXT PRIMARY KEY, " +
|
||||||
|
VALUE_COLUMN + " TEXT NOT NULL" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private @Nullable SQLiteDatabase mDb;
|
||||||
|
|
||||||
|
public ReactDatabaseSupplier(Context context) {
|
||||||
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(VERSION_TABLE_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
if (oldVersion != newVersion) {
|
||||||
|
deleteDatabase();
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the database exists and is open.
|
||||||
|
*/
|
||||||
|
/* package */ synchronized boolean ensureDatabase() {
|
||||||
|
if (mDb != null && mDb.isOpen()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Sometimes retrieving the database fails. We do 2 retries: first without database deletion
|
||||||
|
// and then with deletion.
|
||||||
|
SQLiteException lastSQLiteException = null;
|
||||||
|
for (int tries = 0; tries < 2; tries++) {
|
||||||
|
try {
|
||||||
|
if (tries > 0) {
|
||||||
|
deleteDatabase();
|
||||||
|
}
|
||||||
|
mDb = getWritableDatabase();
|
||||||
|
break;
|
||||||
|
} catch (SQLiteException e) {
|
||||||
|
lastSQLiteException = e;
|
||||||
|
}
|
||||||
|
// Wait before retrying.
|
||||||
|
try {
|
||||||
|
Thread.sleep(SLEEP_TIME_MS);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDb == null) {
|
||||||
|
throw lastSQLiteException;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and/or open the database.
|
||||||
|
*/
|
||||||
|
/* package */ synchronized SQLiteDatabase get() {
|
||||||
|
ensureDatabase();
|
||||||
|
return mDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ synchronized boolean deleteDatabase() {
|
||||||
|
if (mDb != null && mDb.isOpen()) {
|
||||||
|
mDb.close();
|
||||||
|
mDb = null;
|
||||||
|
}
|
||||||
|
return mContext.deleteDatabase(DATABASE_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user