从资产文件夹复制数据库后,Android Pie中没有此类表错误

时间:2018-12-19 17:41:47

标签: android sqlite android-sqlite

从资产文件夹复制数据库后,我无法访问android pie上的数据库。它适用于所有其他版本。我在执行数据库操作时遇到了找不到此类列的错误。请帮我解决这个问题。下面是我正在使用的dbhelper类:

public class DBHelper extends SQLiteOpenHelper {

    private static String DB_PATH = "/data/data/com.abc.xyz/databases/";    
    private static String DB_NAME = "db";    
    private SQLiteDatabase myDataBase;    
    private final Context myContext;

    public DBHelper(Context context) {    
        super(context, DB_NAME, null, 1);
        this.myContext = context;
    }

    /**
     * Creates an empty database on the system and rewrites it with your own database.
     * */
    public void createDataBase() throws IOException {

        boolean dbExist = checkDataBase();    
        if(dbExist){
            //do nothing - database already exist    
        } else {
            //By calling this method and empty database will be created into the default system path
            //of your application so we are gonna be able to overwrite that database with our database.
            this.getReadableDatabase();
            try {
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }   
    }

    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase() {

        SQLiteDatabase checkDB = null;
        try{
            String myPath = DB_PATH + DB_NAME;
            checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
            checkDB.close();
        } catch(SQLiteException e) {

            //database does't exist yet.
        }

        if(checkDB != null) {
            checkDB.close();
        }

        return checkDB != null ? true : false;
    }

    /**
     * Copies your database from your local assets-folder to the just created empty database in the
     * system folder, from where it can be accessed and handled.
     * This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException {

        //Open your local db as the input stream
        InputStream myInput = myContext.getAssets().open(DB_NAME);
        // Path to the just created empty db
        String outFileName = DB_PATH + DB_NAME;
        //Open the empty db as the output stream
        OutputStream myOutput = new FileOutputStream(outFileName);
        //transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[1024];
        int length;
        while ((length = myInput.read(buffer))>0) {
            myOutput.write(buffer, 0, length);
        }
        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
    }

    public void openDataBase() throws SQLException {

        //Open the database
        String myPath = DB_PATH + DB_NAME;
        myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
    }

    @Override
    public synchronized void close() {
        if(myDataBase != null)
            myDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

3 个答案:

答案 0 :(得分:0)

我相信以下内容将解决在检查数据库是否存在时打开数据库的问题,而是检查数据库是否以文件形式存在(请参阅注释):-

public class DBHelper extends SQLiteOpenHelper {

    //private static String DB_PATH = "/data/data/com.abc.xyz/databases/"; //<<<<<<<<<< overwritten
    private static String DB_NAME = "db";
    private SQLiteDatabase myDataBase;
    private final Context myContext;

    private int bytes_copied = 0;
    private static int buffer_size = 1024;
    private int blocks_copied = 0;
    private boolean database_copied = false;
    private boolean database_existed = false;

    public DBHelper(Context context) {
        super(context, DB_NAME, null, 1);

        this.myContext = context;
        // Check for and create (copy DB from assets) when constructing the DBHelper
        if (!checkDataBase()) {
            bytes_copied = 0;
            blocks_copied = 0;
            createDataBase();
        } else {
            database_existed = true;
        }
    }

    /**
     * Creates an empty database on the system and rewrites it with your own database.
     * */
    public void createDataBase() {

        boolean dbExist = checkDataBase(); // Double check
        if(dbExist){
            //do nothing - database already exist
        } else {
            //By calling this method an empty database will be created into the default system path
            //of your application so we are gonna be able to overwrite that database with our database.
            //this.getReadableDatabase();
            try {
                copyDataBase();
            } catch (IOException e) {
                File db = new File(myContext.getDatabasePath(DB_NAME).getPath());
                if (db.exists()) {
                    db.delete();
                }
                e.printStackTrace();
                throw new RuntimeException("Error copying database (see stack-trace above)");
            }
        }
    }

    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase() {
        /**
         * Does not open the database instead checks to see if the file exists
         * also creates the databases directory if it does not exists
         * (the real reason why the database is opened, which appears to result in issues)
         */

        File db = new File(myContext.getDatabasePath(DB_NAME).getPath()); //Get the file name of the database
        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()) {
            dbdir.mkdirs();
        }
        return false;
    }

    /**
     * Copies your database from your local assets-folder to the just created empty database in the
     * system folder, from where it can be accessed and handled.
     * This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException {

        final String TAG = "COPYDATABASE";

        //Open your local db as the input stream
        Log.d(TAG,"Initiated Copy of the database file " + DB_NAME + " from the assets folder.");
        InputStream myInput = myContext.getAssets().open(DB_NAME); // Open the Asset file
        String dbpath = myContext.getDatabasePath(DB_NAME).getPath();
        Log.d(TAG,"Asset file " + DB_NAME + " found so attmepting to copy to " + dbpath);

        // Path to the just created empty db
        //String outFileName = DB_PATH + DB_NAME;
        //Open the empty db as the output stream
        OutputStream myOutput = new FileOutputStream(dbpath);
        //transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[buffer_size];
        int length;
        while ((length = myInput.read(buffer))>0) {
            blocks_copied++;
            Log.d(TAG,"Ateempting copy of block " + String.valueOf(blocks_copied) + " which has " + String.valueOf(length) + " bytes.");
            myOutput.write(buffer, 0, length);
            bytes_copied += length;
        }
        Log.d(TAG,
                "Finished copying Database " + DB_NAME +
                        " from the assets folder, to  " + dbpath +
                        String.valueOf(bytes_copied) + "were copied, in " +
                        String.valueOf(blocks_copied) + " blocks of size " +
                        String.valueOf(buffer_size) + "."
        );
        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
        Log.d(TAG,"All Streams have been flushed and closed.");
    }

    public void openDataBase() throws SQLException {
        //Open the database
        //String myPath = DB_PATH + DB_NAME;
        String myPath = myContext.getDatabasePath(DB_NAME).getPath();
        myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
    }

    @Override
    public synchronized void close() {
        if(myDataBase != null)
            myDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }


    @Override
    public void onConfigure(SQLiteDatabase db) {
        super.onConfigure(db);
        Log.d("DBCONFIGURE","Database has been configured ");
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        Log.d("DBOPENED","Database has been opened.");
    }
}

测试

以下活动用于测试上述内容(注意,实例化DBHelper对象 myDBHlpr 会启动数据库检查/复制):-

public class MainActivity extends AppCompatActivity {

    DBHelper myDBhlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("VERSION","Build Version is " + String.valueOf(Build.VERSION.SDK_INT));
        myDBhlpr = new DBHelper(this);
        Cursor csr = myDBhlpr.getWritableDatabase().query(
                "sqlite_master",
                null,null,null,null,null,null
        );
        while (csr.moveToNext()) {
            Log.d(
                    "DBITEMS",
                    "Found " + csr.getString(csr.getColumnIndex("name")) +
                            " which has a type of " + csr.getString(csr.getColumnIndex("type")) +
                            " the SQL used to create it, was :-\n\t" + csr.getString(csr.getColumnIndex("sql"))
            );
        }
        csr.close();
    }
}

第1阶段-运行,没有在Assets文件夹中使用数据库文件(db):
  
      
  • 请注意日志 /VERSION: Build Version is 28 中的行,即Android Pie
  •   

发生异常,其中包括指向InputStream myInput = myContext.getAssets().open(DB_NAME); 的错误的堆栈跟踪(即资产不存在,如预期的那样。):-

2019-01-25 17:51:12.303 30371-30371/so.cdfa D/VERSION: Build Version is 28
2019-01-25 17:51:12.304 30371-30371/so.cdfa D/COPYDATABASE: Initiated Copy of the database file db from the assets folder.
2019-01-25 17:51:12.304 30371-30371/so.cdfa W/System.err: java.io.FileNotFoundException: db
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.content.res.AssetManager.nativeOpenAsset(Native Method)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:744)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:721)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at so.cdfa.DBHelper.copyDataBase(DBHelper.java:101)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at so.cdfa.DBHelper.createDataBase(DBHelper.java:55)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at so.cdfa.DBHelper.<init>(DBHelper.java:36)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at so.cdfa.MainActivity.onCreate(MainActivity.java:18)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.app.Activity.performCreate(Activity.java:7136)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.app.Activity.performCreate(Activity.java:7127)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
2019-01-25 17:51:12.305 30371-30371/so.cdfa W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
2019-01-25 17:51:12.306 30371-30371/so.cdfa W/System.err:     at android.os.Looper.loop(Looper.java:193)
2019-01-25 17:51:12.307 30371-30371/so.cdfa W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6669)
2019-01-25 17:51:12.307 30371-30371/so.cdfa W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2019-01-25 17:51:12.307 30371-30371/so.cdfa W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
2019-01-25 17:51:12.307 30371-30371/so.cdfa W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2019-01-25 17:51:12.307 30371-30371/so.cdfa D/AndroidRuntime: Shutting down VM
2019-01-25 17:51:12.312 30371-30371/so.cdfa E/AndroidRuntime: FATAL EXCEPTION: main
    Process: so.cdfa, PID: 30371
    java.lang.RuntimeException: Unable to start activity ComponentInfo{so.cdfa/so.cdfa.MainActivity}: java.lang.RuntimeException: Error copying database (see stack-trace above)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.RuntimeException: Error copying database (see stack-trace above)
        at so.cdfa.DBHelper.createDataBase(DBHelper.java:62)
        at so.cdfa.DBHelper.<init>(DBHelper.java:36)
        at so.cdfa.MainActivity.onCreate(MainActivity.java:18)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
2019-01-25 17:51:12.330 30371-30371/so.cdfa I/Process: Sending signal. PID: 30371 SIG: 9

步骤2-将数据库复制到资产文件夹后运行。

复制到Assets文件夹并重命名为 db 的人造sqlite数据库文件。复制数据库并将其项目(表索引等)写入日志,例如:-

再次记下构建版本和日志记录

2019-01-25 17:54:51.734 30587-30587/so.cdfa D/VERSION: Build Version is 28
2019-01-25 17:54:51.735 30587-30587/so.cdfa D/COPYDATABASE: Initiated Copy of the database file db from the assets folder.
2019-01-25 17:54:51.735 30587-30587/so.cdfa D/COPYDATABASE: Asset file db found so attmepting to copy to /data/user/0/so.cdfa/databases/db
2019-01-25 17:54:51.736 30587-30587/so.cdfa D/COPYDATABASE: Ateempting copy of block 1 which has 1024 bytes.
............. lines removed from brevity 
2019-01-25 17:54:51.746 30587-30587/so.cdfa D/COPYDATABASE: Ateempting copy of block 40 which has 1024 bytes.
2019-01-25 17:54:51.746 30587-30587/so.cdfa D/COPYDATABASE: Finished copying Database db from the assets folder, to  /data/user/0/so.cdfa/databases/db40960were copied, in 40 blocks of size 1024.
2019-01-25 17:54:51.746 30587-30587/so.cdfa D/COPYDATABASE: All Streams have been flushed and closed.
2019-01-25 17:54:51.770 30587-30587/so.cdfa D/DBCONFIGURE: Database has been configured 
2019-01-25 17:54:51.772 30587-30587/so.cdfa D/DBOPENED: Database has been opened.
2019-01-25 17:54:51.772 30587-30587/so.cdfa D/DBITEMS: Found sqlite_sequence which has a type of table the SQL used to create it, was :-
        CREATE TABLE sqlite_sequence(name,seq)
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found Schedules which has a type of table the SQL used to create it, was :-
        CREATE TABLE `Schedules` (
      `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
      `date` REAL
    )
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found type_affinity_example which has a type of table the SQL used to create it, was :-
        CREATE TABLE type_affinity_example (
        id INTEGER PRIMARY KEY, -- <<<<<<<< MUST BE INTEGER VALUE ELSE INVALID DATATYPE
        col1 rumplestiltskin,
        col2 BLOB,
        col3 INTEGER,
        col4 NUMERIC,
        col5 REAL,
        col6 REALINT, -- because of rule 1 will be INTEGER type affinity NOT REAL!!!!
        col7 TEXT
    )
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found OHLCV_Components which has a type of table the SQL used to create it, was :-
        CREATE TABLE OHLCV_Components (symbol TEXT, tradeDate TEXT, openPrice REAL, highPrice REAL, lowPrice REAL, closePrice REAL, volume INTEGER)
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found league which has a type of table the SQL used to create it, was :-
        CREATE TABLE league (league_id INTEGER PRIMARY KEY, league_name TEXT)
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found team which has a type of table the SQL used to create it, was :-
        CREATE TABLE team (team_id INTEGER PRIMARY KEY, team_name TEXT)
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found player which has a type of table the SQL used to create it, was :-
        CREATE TABLE player (player_id INTEGER PRIMARY KEY, player_name)
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found team_league_history which has a type of table the SQL used to create it, was :-
        CREATE TABLE team_league_history (
        tlh_team_reference INTEGER, 
        tlh_league_reference INTEGER, 
        tlh_from_date TEXT DEFAULT CURRENT_DATE, 
        tlh_to_date TEXT DEFAULT '3000-12-31'
    )
2019-01-25 17:54:51.773 30587-30587/so.cdfa D/DBITEMS: Found player_team_history which has a type of table the SQL used to create it, was :-
        CREATE TABLE player_team_history (
        pth_player_reference INTEGER, 
        pth_team_reference INTEGER, 
        pth_from_date TEXT DEFAULT CURRENT_DATE, 
        pth_to_date TEXT DEFAULT '3000-12-31'
    )
2019-01-25 17:54:51.774 30587-30587/so.cdfa D/DBITEMS: Found android_metadata which has a type of table the SQL used to create it, was :-
        CREATE TABLE android_metadata (locale TEXT)
  • 注意,其格式如上,可以复制非sqlite数据库文件(任何文件),这将导致数据库异常损坏。检查文件的前16个字节是否符合SQlite文件头( 头字符串:“ SQLite格式3 \ 000” Database File Format可以否定这个问题。

  • 注意,因为已经创建了数据库本身(尽管为空),您需要将其删除。您可以通过删除应用程序的数据或卸载应用程序来执行此操作(您也可以可以通过设备资源管理器将其删除。)

Step3-后续运行(数据库存在):-

2019-01-25 17:59:32.868 30666-30666/so.cdfa D/VERSION: Build Version is 28
2019-01-25 17:59:32.873 30666-30666/so.cdfa D/DBCONFIGURE: Database has been configured 
2019-01-25 17:59:32.874 30666-30666/so.cdfa D/DBOPENED: Database has been opened.
2019-01-25 17:59:32.875 30666-30666/so.cdfa D/DBITEMS: Found sqlite_sequence which has a type of table the SQL used to create it, was :-
        CREATE TABLE sqlite_sequence(name,seq)
2019-01-25 17:59:32.875 30666-30666/so.cdfa D/DBITEMS: Found Schedules which has a type of table the SQL used to create it, was :-
        CREATE TABLE `Schedules` (
      `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
      `date` REAL
...........

答案 1 :(得分:0)

我要做的就是在 createDataBase()中的 this.getReadableDatabase()之后关闭数据库连接:

public void createDataBase() throws IOException {
    boolean dbExist = checkDataBase();    
    if(dbExist){
        //do nothing - database already exist    
    } else {
        //By calling this method and empty database will be created into the default system path
        //of your application so we are gonna be able to overwrite that database with our database.
        this.getReadableDatabase();
        this.close()
        try {
            copyDataBase();
        } catch (IOException e) {
            throw new Error("Error copying database");
        }
    }   
}

答案 2 :(得分:0)

我一直为此苦苦挣扎。只需从createDatabase()方法中删除以下代码行即可解决该问题:

this.getReadableDataBase();

似乎现在可以使用API​​ 27和28正常工作了。