React Native调用原生端sqlite数据库
在app开发过程中数据存储是必不可少的,RN中数据存储一般都用AsyncStorage。但是对于大批量的数据持久化存储,最好还是用数据库来存。RN中并没有提供直接的数据库存储API,需要我们自己根据iOS和Android进行封装调用。
Github上有个库提供了对原生sqlite数据库的操作封装react-native-sqlite-storage
,看过之后我觉得还是自己分别在Android和iOS原生端来实现数据库存储更好,原生端数据库API非常简单,尤其是Android,iOS也可以借助第三方FMDB来实现。不过对于不熟悉Android或者iOS的人来说,直接使用这个库是最好的选择。
在我之前的文章《RN与原生交互(二)——数据传递》中已经说明了RN如何调用原生端方法获取数据,这里RN调用原生sqlite数据库原理也一样,都是在原生端写好所有的封装操作,以Native Module的形式供RN端调用。
我写了个简单的Demo,实现了数据库的创建和基本的增删改查操作,效果如下:
demo.gif
下面来说说具体实现方式。
Android端
Android端sqlite数据库的使用非常简单,官方提供了SQLiteDatabase和SQLiteOpenHelper等相关类来操作数据库,API非常简单。Android端具体实现步骤如下:
- 先创建一个DBHelper类继承SQLiteOpenHelper,重写onCreate和onUpgrade方法,并创建该类的构造函数:
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "StudentDB.db"; //数据库名称
private static final int version = 1; //数据库版本
public static final String STUDENT_TABLE = "Student";
public DBHelper(Context context) {
super(context, DB_NAME, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table if not exists " + STUDENT_TABLE +
" (studentName text primary key, schoolName text, className text)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String sql = "DROP TABLE IF EXISTS " + STUDENT_TABLE;
db.execSQL(sql);
onCreate(db);
}
}
- 创建DBManager类,将所有数据库的增删改查操作放到这里面来。
数据的查询使用Cursor,插入数据使用android的ContentValues,非常简单,这点比iOS原生的API好用一百倍。部分核心代码如下:
public class DBManager {
private static final String TAG = "StudentDB";
private DBHelper dbHelper;
private final String[] STUDENT_COLUMNS = new String[] {
"studentName",
"schoolName",
"className",
};
public DBManager(Context context) {
this.dbHelper = new DBHelper(context);
}
/**
* 是否存在此条数据
* @return bool
*/
public boolean isStudentExists(String studentName) {
boolean isExists = false;
SQLiteDatabase db = null;
Cursor cursor = null;
try {
db = dbHelper.getReadableDatabase();
String sql = "select * from Student where studentName = ?";
cursor = db.rawQuery(sql, new String[]{studentName});
if (cursor.getCount() > 0) {
isExists = true;
}
} catch (Exception e) {
Log.e(TAG, "isStudentExists query error", e);
} finally {
if (cursor != null) {
cursor.close();
}
if (db != null) {
db.close();
}
}
return isExists;
}
/**
* 保存数据
*/
public void saveStudent(String studentName, String schoolName, String className) {
SQLiteDatabase db = null;
try {
db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("studentName", studentName);
cv.put("schoolName", schoolName);
cv.put("className", className);
db.insert(DBHelper.STUDENT_TABLE, null, cv);
} catch (Exception e) {
Log.e(TAG, "saveStudent error", e);
} finally {
if (db != null) {
db.close();
}
}
}
}
- 创建module类继承
ReactContextBaseJavaModule
,将DBManager中的增删改查方法导出供RN端直接调用。部分核心代码:
public class DBManagerModule extends ReactContextBaseJavaModule {
private ReactContext mReactContext;
public DBManagerModule(ReactApplicationContext reactContext) {
super(reactContext);
mReactContext = reactContext;
}
@Override
public String getName() {
return "DBManagerModule";
}
@ReactMethod
public void saveStudent(String studentName, String schoolName, String className) {
DBManager dbManager = new DBManager(mReactContext);
if (!dbManager.isStudentExists(studentName)) {
dbManager.saveStudent(studentName, schoolName, className);
}
}
}
- 创建package类继承
ReactPackage
,实现这个接口的方法,将上面创建的module类在createNativeModules
方法中实例化。
public class DBManagerPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
nativeModules.add(new DBManagerModule(reactContext));
return nativeModules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
这样在RN端就可以调用Android的数据库存储数据了。
iOS端
iOS端数据库的存储一般都不使用原生api,因为它原生api不那么友好。我们一般使用FMDB来实现数据库的存储操作,用CoreData也可以,原理都一样,这里以FMDB为例。
- 创建Podfile,使用CocoaPods安装FMDB。
- 在项目的Build Phases ——> Link Binary With Libraries中添加libsqlite3.tbd库。
- 创建DBHelper类
不同于Android可以直接继承SQLiteOpenHelper直接重写方法就OK了,iOS数据库的存储操作还是需要我们自己完成。
创建DBHelper的单例,指定数据库文件,创建数据库和表,核心代码如下:
+ (DBHelper *)sharedDBHelper {
static DBHelper *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_db = [[FMDatabase alloc] initWithPath:[self getDBFilePath]];
[self createTables];
}
return self;
}
- (NSString *)getDBFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent:@"StudentDB.db"];
return storePath;
}
- (void)createTables {
if ([_db open]) {
NSMutableString *sql = [NSMutableString string];
[sql appendString:@"create table if not exists Student ("];
[sql appendString:@"studentName text primary key, "];
[sql appendString:@"schoolName text, "];
[sql appendString:@"className text);"];
BOOL result = [_db executeUpdate:sql];
if (result) {
NSLog(@"create table Student successfully.");
}
[_db close];
}
}
- 创建module类,这里module类名字应该与Android端一致,方便RN端调用的时候统一。这里名字起为DBManagerModule,iOS端module类只需要实现RCTBridgeModule协议就可以了,这一步比Android要更简单。DBManagerModule核心代码:
@implementation DBManagerModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(saveStudent:(NSDictionary *)dict) {
[[DBHelper sharedDBHelper] saveStudent:dict];
}
RCT_EXPORT_METHOD(deleteStudent:(NSString *)studentName) {
[[DBHelper sharedDBHelper] deleteStudentByName:studentName];
}
RCT_EXPORT_METHOD(getAllStudent:(RCTResponseSenderBlock)callback) {
NSArray *students = [[DBHelper sharedDBHelper] getAllStudent];
callback(@[students]);
}
RCT_EXPORT_METHOD(deleteAllStudent) {
[[DBHelper sharedDBHelper] deleteAllStudent];
}
@end
到这里RN端就可以直接调用module类中的方法操作iOS数据库了。
RN端的用法
比如查询所有数据:
DBManagerModule.getAllStudent((result) => {
let students = [];
if (result != null) {
students = result;
this.setState({
studentList: students
})
}
});
总结
- RN端量小的数据可以使用AsyncStorage,大数据量需要存储还是要用数据库。
- 经过实践,我觉得还是直接在原生端操作数据库更好,api简单也方便维护。第三方库
react-native-sqlite-storage
也是在原生端的基础上做的封装,好处是方便RN端调用,不熟悉原生的可以直接按照配置说明来使用,缺点也很明显,配置繁琐,使用过程中出了问题也不容易解决。
PS:
推荐一下demo中用到的RN第三方库:
-
react-native-navigation
基于原生的导航库 -
teaset
非常好的React Native UI组件库 -
react-native-vector-icons
iconfont组件库 -
react-native-swipe-list-view
仿iOS列表侧滑显示更多操作的React Native列表组件。虽然有bug,但不影响使用。