简而言之,一种方法是将新资产文件复制到适当的位置,同时保留原始数据库文件的副本,然后您可以应用更新,以便有效地将用户数据保留到新复制的数据库中。BUT仅当应用程序针对该特定更改进行升级并且数据库存在时。
如果数据库不存在,则应使用资产文件的标准副本。
以下是如何完成此操作的示例。
此示例在很大程度上依赖于允许管理和对资产文件夹中的文件和/或数据库文件本身进行一些询问的例程。
一个类即DBAssetHandler.java满足上述要求(以及使用 SQLiteOpenHelper 时提取 user_version 又名数据库版本的能力)。
it is :-
public class DBAssetHandler {
static final String[] tempfiles = new String[]{"-journal","-wal","-shm"}; // temporary files to rename
public static final String backup = "-backup"; //value to be appended to file name when renaming (psuedo delete)
public static final int OUCH = -666666666;
/**
* Check if the database already exists. NOTE will create the databases folder is it doesn't exist
* @return true if it exists, false if it doesn't
*/
public static boolean checkDataBase(Context context, String dbname) {
File db = new File(context.getDatabasePath(dbname).getPath()); //Get the file name of the database
Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove if publish App
if (db.exists()) return true; // If it exists then return doing nothing
// Get the parent (directory in which the database file would be)
File dbdir = db.getParentFile();
// If the directory does not exits then make the directory (and higher level directories)
if (!dbdir.exists()) {
db.getParentFile().mkdirs();
dbdir.mkdirs();
}
return false;
}
/**
* Copy database file from the assets folder
* (long version caters for asset file name being different to the database name)
* @param context Context is needed to get the applicable package
* @param dbname name of the database file
* @param assetfilename name of the asset file
* @param deleteExistingDB true if an existing database file should be deleted
* note will delete journal and wal files
* note doen't actually delete the files rater it renames
* the files by appended -backup to the file name
* SEE/USE clearForceBackups below to delete the renamed files
*/
public static void copyDataBase(Context context, String dbname, String assetfilename, boolean deleteExistingDB) {
final String TAG = "COPYDATABASE";
int stage = 0, buffer_size = 4096, blocks_copied = 0, bytes_copied = 0;
File f = new File(context.getDatabasePath(dbname).toString());
InputStream is;
OutputStream os;
/**
* If forcing then effectively delete (rename) current database files
*/
if (deleteExistingDB) {
//String[] tempfiles = new String[]{"-journal","-wal","-shm"};
//String backup = "-backup";
f.renameTo(context.getDatabasePath(dbname + "-backup"));
for (String s: tempfiles) {
File tmpf = new File(context.getDatabasePath(dbname + s).toString());
if (tmpf.exists()) {
tmpf.renameTo(context.getDatabasePath(dbname + s + backup));
}
}
}
//Open your local db as the input stream
Log.d(TAG,"Initiated Copy of the database file " + assetfilename + " from the assets folder."); //TODO remove if publishing
try {
is = context.getAssets().open(assetfilename); // Open the Asset file
stage++;
Log.d(TAG, "Asset file " + assetfilename + " found so attmepting to copy to " + f.getPath()); //TODO remove if publishing
os = new FileOutputStream(f);
stage++;
//transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[buffer_size];
int length;
while ((length = is.read(buffer)) > 0) {
blocks_copied++;
Log.d(TAG, "Attempting copy of block " + String.valueOf(blocks_copied) + " which has " + String.valueOf(length) + " bytes."); //TODO remove if publishing
os.write(buffer, 0, length);
bytes_copied += length;
}
stage++;
Log.d(TAG,
"Finished copying Database " + dbname +
" from the assets folder, to " + f.getPath() +
String.valueOf(bytes_copied) + "were copied, in " +
String.valueOf(blocks_copied) + " blocks of size " +
String.valueOf(buffer_size) + "."
); //TODO remove if publishing
//Close the streams
os.flush();
stage++;
os.close();
stage++;
is.close();
Log.d(TAG, "All Streams have been flushed and closed.");
} catch (IOException e) {
String exception_message = "";
e.printStackTrace();
switch (stage) {
case 0:
exception_message = "Error trying to open the asset " + dbname;
break;
case 1:
exception_message = "Error opening Database file for output, path is " + f.getPath();
break;
case 2:
exception_message = "Error flushing written database file " + f.getPath();
break;
case 3:
exception_message = "Error closing written database file " + f.getPath();
break;
case 4:
exception_message = "Error closing asset file " + f.getPath();
}
throw new RuntimeException("Unable to copy the database from the asset folder." + exception_message + " see starck-trace above.");
}
}
/**
* Copy the databsse from the assets folder where asset name and dbname are the same
* @param context
* @param dbname
* @param deleteExistingDB
*/
public static void copyDataBase(Context context, String dbname, boolean deleteExistingDB) {
copyDataBase(context, dbname,dbname,deleteExistingDB);
}
/**
* Get the SQLite_user_vesrion from the DB in the asset folder
*
* @param context needed to get the appropriate package assets
* @param assetfilename the name of the asset file (assumes/requires name matches database)
* @return the version number as stored in the asset DB
*/
public static int getVersionFromDBInAssetFolder(Context context, String assetfilename) {
InputStream is;
try {
is = context.getAssets().open(assetfilename);
} catch (IOException e) {
return OUCH;
}
return getDBVersionFromInputStream(is);
}
/**
* Get the version from the database itself without opening the database as an SQliteDatabase
* @param context Needed to ascertain package
* @param dbname the name of the dataabase
* @return the version number extracted
*/
public static int getVersionFromDBFile(Context context, String dbname) {
InputStream is;
try {
is = new FileInputStream(new File(context.getDatabasePath(dbname).toString()));
} catch (IOException e) {
return OUCH;
}
return getDBVersionFromInputStream(is);
}
/**
* Get the Database Version (user_version) from an inputstream
* Note the inputstream is closed
* @param is The Inputstream
* @return The extracted version number
*/
private static int getDBVersionFromInputStream(InputStream is) {
int rv = -1, dbversion_offset = 60, dbversion_length = 4 ;
byte[] dbfileheader = new byte[64];
byte[] dbversion = new byte[4];
try {
is.read(dbfileheader);
is.close();
} catch (IOException e) {
e.printStackTrace();
return rv;
}
for (int i = 0; i < dbversion_length; i++ ) {
dbversion[i] = dbfileheader[dbversion_offset + i];
}
return ByteBuffer.wrap(dbversion).getInt();
}
/**
* Check to see if the asset file exists
*
* @param context needed to get the appropriate package
* @param assetfilename the name of the asset file to check
* @return true if the asset file exists, else false
*/
public static boolean ifAssetFileExists(Context context, String assetfilename) {
try {
context.getAssets().open(assetfilename);
} catch (IOException e) {
return false;
}
return true;
}
/**
* Delete the backup
* @param context
* @param dbname
*/
public static void clearForceBackups(Context context, String dbname) {
String[] fulllist = new String[tempfiles.length + 1];
for (int i = 0;i < tempfiles.length; i++) {
fulllist[i] = tempfiles[i];
}
fulllist[tempfiles.length] = ""; // Add "" so database file backup is also deleted
for (String s: fulllist) {
File tmpf = new File(context.getDatabasePath(dbname + s + backup).toString());
if (tmpf.exists()) {
tmpf.delete();
}
}
}
}
资产文件有两个:-
-
pev1.db- 如上所述的原始预先存在的数据库
-
pev1mod.db- 修改的(额外的列、UNIQUE 约束和额外的行)。
数据库助手(SQLOpenHelper 的子类)即PEV2DBHelper.java,需要注意的是数据库版本(数据库版本) 用于控制,因此与 APK 版本不同(可能比 DB 更频繁地更改)
- 尝试使用时发现问题升级时方法,因此另一种方法是获取数据库的用户版本从文件而不是通过 SQLiteDatabase。
Here is PEV2DBHelper.java :-
/**
* MORE COMPLEX EXAMPLE RETAINING USER DATA
*/
public class PEV2DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "pev1.db";
public static final String ASSETTOCOPY_DBV2 = "pev1mod.db"; //<<<<<<<<<< changed DB
public static final int DBVERSION = 2; //<<<<<<<<<< increase and db file from assets will copied keeping existing data
Context mContext;
public PEV2DBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
int dbversion = DBAssetHandler.getVersionFromDBFile(context,DBNAME);
Log.d("DBFILEVERSION","Database File Version = " + String.valueOf(dbversion));
int af1version = DBAssetHandler.getVersionFromDBInAssetFolder(context,DBNAME);
Log.d("DBFILEVERSION","Asset Database File Version = " + String.valueOf(af1version));
int af2version = DBAssetHandler.getVersionFromDBInAssetFolder(context,ASSETTOCOPY_DBV2);
Log.d("DBFILEVERSION","Asset Database File Version = " + String.valueOf(af2version));
// cater for different DBVERSIONS (for testing )
if (!DBAssetHandler.checkDataBase(context,DBNAME)) {
//If new installation of the APP then copy the appropriate asset file for the DB
switch (DBVERSION) {
case 1:
DBAssetHandler.copyDataBase(context,DBNAME,DBNAME,false);
break;
case 2:
DBAssetHandler.copyDataBase(context,DBNAME,ASSETTOCOPY_DBV2,false);
break;
}
}
// If DBVERSION upgraded to 2 with modified DB but wanting to preserve used data
if (DBAssetHandler.checkDataBase(context,DBNAME) & (DBVERSION > DBAssetHandler.getVersionFromDBFile(context, DBNAME)) & (DBVERSION == 2) ) {
String[] oldcolumns = new String[]{"user","password"};
// Copy in the new DB noting that delete option renames old (truue flag important)
DBAssetHandler.copyDataBase(context,DBNAME,ASSETTOCOPY_DBV2,true);
//Get the newly copied database
SQLiteDatabase newdb = SQLiteDatabase.openDatabase(context.getDatabasePath(DBNAME).toString(),null,SQLiteDatabase.OPEN_READWRITE);
//Get the old database (backup copy)
SQLiteDatabase olddb = SQLiteDatabase.openDatabase(context.getDatabasePath(DBNAME + DBAssetHandler.backup).toString(),null,SQLiteDatabase.OPEN_READWRITE);
//Prepare to insert old rows (note user column is UNIQUE so pretty simple scenario just try inserting all and duplicates will be rejected)
ContentValues cv = new ContentValues();
Cursor oldcsr = olddb.query("user",null,null,null,null,null,null);
newdb.beginTransaction();
while (oldcsr.moveToNext()) {
cv.clear();
for (String columnname: oldcolumns) {
cv.put(columnname,oldcsr.getString(oldcsr.getColumnIndex(columnname)));
}
newdb.insert("user",null,cv);
}
newdb.setTransactionSuccessful();
newdb.endTransaction();
newdb.close();
olddb.close();
// Finally delete the renamed old database
DBAssetHandler.clearForceBackups(context,DBNAME);
}
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
- 请注意,添加、删除提取行的方法几乎没有臃肿之处。然而,它有点过于复杂,因为它处理版本之间的切换以方便演示。
最后是一个调用 PEV2DBHelper 的示例活动,将架构和表中的行写入日志。
使用的活动是MainActivity.java并且是 :-
public class MainActivity extends AppCompatActivity {
PEV2DBHelper mDBHlpr2; //DBHelper for example that retains user data
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doPEV2(); // Test simple more complex scenario
}
private void doPEV2() {
mDBHlpr2 = new PEV2DBHelper(this);
SQLiteDatabase db = mDBHlpr2.getWritableDatabase();
Cursor csr = db.query("sqlite_master",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
csr = db.query("user",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
if (PEV2DBHelper.DBVERSION == 1) {
addUserData(db);
csr = db.query("user",null,null,null,null,null,null);
DatabaseUtils.dumpCursor(csr);
}
csr.close();
db.close();
}
/**
* Add some user data for testing presevation of that data
* @param db the SQLitedatabase
*/
private void addUserData(SQLiteDatabase db) {
ContentValues cv = new ContentValues();
cv.put("user","mr new user");
cv.put("password","a password");
db.insert("user",null,cv);
}
}
Results
1.首次运行时DBVERSION为1(全新App)
在本例中是资产文件pev1.db从资产文件夹复制输出是:-
2019-02-22 19:07:54.676 28670-28670/? D/DBFILEVERSION: Database File Version = -666666666
2019-02-22 19:07:54.677 28670-28670/? D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:07:54.677 28670-28670/? D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:07:54.677 28670-28670/? D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Initiated Copy of the database file pev1.db from the assets folder.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Asset file pev1.db found so attmepting to copy to /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Attempting copy of block 1 which has 4096 bytes.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Attempting copy of block 2 which has 4096 bytes.
2019-02-22 19:07:54.677 28670-28670/? D/COPYDATABASE: Finished copying Database pev1.db from the assets folder, to /data/user/0/mjt.so54807516/databases/pev1.db8192were copied, in 2 blocks of size 4096.
2019-02-22 19:07:54.678 28670-28670/? D/COPYDATABASE: All Streams have been flushed and closed.
2019-02-22 19:07:54.678 28670-28670/? D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:07:54.701 28670-28670/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@71528f1
2019-02-22 19:07:54.701 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.701 28670-28670/? I/System.out: type=table
2019-02-22 19:07:54.701 28670-28670/? I/System.out: name=user
2019-02-22 19:07:54.701 28670-28670/? I/System.out: tbl_name=user
2019-02-22 19:07:54.702 28670-28670/? I/System.out: rootpage=2
2019-02-22 19:07:54.702 28670-28670/? I/System.out: sql=CREATE TABLE "user" (
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "_id" INTEGER NOT NULL,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "user" TEXT,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: "password" TEXT,
2019-02-22 19:07:54.702 28670-28670/? I/System.out: PRIMARY KEY ("_id")
2019-02-22 19:07:54.702 28670-28670/? I/System.out: )
2019-02-22 19:07:54.702 28670-28670/? I/System.out: }
2019-02-22 19:07:54.702 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.702 28670-28670/? I/System.out: type=table
2019-02-22 19:07:54.702 28670-28670/? I/System.out: name=android_metadata
2019-02-22 19:07:54.702 28670-28670/? I/System.out: tbl_name=android_metadata
2019-02-22 19:07:54.702 28670-28670/? I/System.out: rootpage=3
2019-02-22 19:07:54.702 28670-28670/? I/System.out: sql=CREATE TABLE android_metadata (locale TEXT)
2019-02-22 19:07:54.702 28670-28670/? I/System.out: }
2019-02-22 19:07:54.702 28670-28670/? I/System.out: <<<<<
2019-02-22 19:07:54.703 28670-28670/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@1e20cd6
2019-02-22 19:07:54.703 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.703 28670-28670/? I/System.out: _id=1
2019-02-22 19:07:54.703 28670-28670/? I/System.out: user=Fred
2019-02-22 19:07:54.703 28670-28670/? I/System.out: password=fredpassword
2019-02-22 19:07:54.703 28670-28670/? I/System.out: }
2019-02-22 19:07:54.703 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.703 28670-28670/? I/System.out: _id=2
2019-02-22 19:07:54.703 28670-28670/? I/System.out: user=Mary
2019-02-22 19:07:54.704 28670-28670/? I/System.out: password=marypassword
2019-02-22 19:07:54.704 28670-28670/? I/System.out: }
2019-02-22 19:07:54.704 28670-28670/? I/System.out: <<<<<
2019-02-22 19:07:54.705 28670-28670/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@acdb57
2019-02-22 19:07:54.705 28670-28670/? I/System.out: 0 {
2019-02-22 19:07:54.705 28670-28670/? I/System.out: _id=1
2019-02-22 19:07:54.705 28670-28670/? I/System.out: user=Fred
2019-02-22 19:07:54.705 28670-28670/? I/System.out: password=fredpassword
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: 1 {
2019-02-22 19:07:54.706 28670-28670/? I/System.out: _id=2
2019-02-22 19:07:54.706 28670-28670/? I/System.out: user=Mary
2019-02-22 19:07:54.706 28670-28670/? I/System.out: password=marypassword
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: 2 {
2019-02-22 19:07:54.706 28670-28670/? I/System.out: _id=3
2019-02-22 19:07:54.706 28670-28670/? I/System.out: user=mr new user
2019-02-22 19:07:54.706 28670-28670/? I/System.out: password=a password
2019-02-22 19:07:54.706 28670-28670/? I/System.out: }
2019-02-22 19:07:54.706 28670-28670/? I/System.out: <<<<<
- -666666666 是版本,因为不存在文件,因此尝试从文件获取版本返回默认值,表明无法获取版本。
2. 第二次运行一切相同,除了版本号为 1。
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Database File Version = 1
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:09:43.724 28730-28730/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:09:43.725 28730-28730/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:09:43.725 28730-28730/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:09:43.729 28730-28730/mjt.so54807516 I/System.out: >>>>>
..... etc
3.将DBVERSION更改为2后下次运行
2019-02-22 19:13:49.157 28866-28866/mjt.so54807516 D/DBFILEVERSION: Database File Version = 1
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBFILEVERSION: Asset Database File Version = 0
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/DBPATH: DB Path is /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.158 28866-28866/mjt.so54807516 D/COPYDATABASE: Initiated Copy of the database file pev1mod.db from the assets folder.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Asset file pev1mod.db found so attmepting to copy to /data/user/0/mjt.so54807516/databases/pev1.db
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 1 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 2 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 3 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Attempting copy of block 4 which has 4096 bytes.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: Finished copying Database pev1.db from the assets folder, to /data/user/0/mjt.so54807516/databases/pev1.db16384were copied, in 4 blocks of size 4096.
2019-02-22 19:13:49.159 28866-28866/mjt.so54807516 D/COPYDATABASE: All Streams have been flushed and closed.
2019-02-22 19:13:49.186 28866-28866/mjt.so54807516 E/SQLiteDatabase: Error inserting password=fredpassword user=Fred
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: user.user (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at
.........
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2019-02-22 19:13:49.191 28866-28866/mjt.so54807516 E/SQLiteDatabase: Error inserting password=a password user=mr new user
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: user.user (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at
.............
2019-02-22 19:13:49.209 28866-28866/mjt.so54807516 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@34252b0
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: 0 {
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: type=table
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: name=user
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: tbl_name=user
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: rootpage=2
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: sql=CREATE TABLE "user" (
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "_id" INTEGER NOT NULL,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "user" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "password" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: "email" TEXT,
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: PRIMARY KEY ("_id"),
2019-02-22 19:13:49.210 28866-28866/mjt.so54807516 I/System.out: CONSTRAINT "user" UNIQUE ("user")
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: )
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: 1 {
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: type=index
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: name=sqlite_autoindex_user_1
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: tbl_name=user
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: rootpage=4
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: sql=null
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: 2 {
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: type=table
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: name=android_metadata
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: tbl_name=android_metadata
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: rootpage=3
2019-02-22 19:13:49.211 28866-28866/mjt.so54807516 I/System.out: sql=CREATE TABLE android_metadata (locale TEXT)
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: <<<<<
2019-02-22 19:13:49.212 28866-28866/mjt.so54807516 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@c8f0529
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: 0 {
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: _id=1
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: user=Fred
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: password=fredpassword
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: [email protected] /cdn-cgi/l/email-protection
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: }
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: 1 {
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out: _id=2
2019-02-22 19:13:49.213 28866-28866/mjt.so54807516 I/System.out:
...... etc