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,实现了数据库的创建和基本的增删改查操作,效果如下:

《React Native调用原生端sqlite数据库》 demo.gif

下面来说说具体实现方式。

Android端

Android端sqlite数据库的使用非常简单,官方提供了SQLiteDatabase和SQLiteOpenHelper等相关类来操作数据库,API非常简单。Android端具体实现步骤如下:

  1. 先创建一个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);
    }
}
  1. 创建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();
            }
        }
    }
}
  1. 创建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);
        }
    }
}
  1. 创建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为例。

  1. 创建Podfile,使用CocoaPods安装FMDB。
  2. 在项目的Build Phases ——> Link Binary With Libraries中添加libsqlite3.tbd库。
  3. 创建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];
  }
}
  1. 创建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
        })
      }
    });

总结

  1. RN端量小的数据可以使用AsyncStorage,大数据量需要存储还是要用数据库。
  2. 经过实践,我觉得还是直接在原生端操作数据库更好,api简单也方便维护。第三方库react-native-sqlite-storage
    也是在原生端的基础上做的封装,好处是方便RN端调用,不熟悉原生的可以直接按照配置说明来使用,缺点也很明显,配置繁琐,使用过程中出了问题也不容易解决。

PS:

推荐一下demo中用到的RN第三方库:

  1. react-native-navigation 基于原生的导航库
  2. teaset非常好的React Native UI组件库
  3. react-native-vector-iconsiconfont组件库
  4. react-native-swipe-list-view仿iOS列表侧滑显示更多操作的React Native列表组件。虽然有bug,但不影响使用。
    原文作者:不變旋律
    原文地址: https://www.jianshu.com/p/7e6c98f221ff
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞