React Native调用原生端sqlite数据库

作者: 不變旋律 | 来源:发表于2018-07-07 22:26 被阅读45次

    在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端具体实现步骤如下:

    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,但不影响使用。

    相关文章

      网友评论

      本文标题:React Native调用原生端sqlite数据库

      本文链接:https://www.haomeiwen.com/subject/agavuftx.html