SQLite
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> SQLite

Cơ sở dữ liệu được điền sẵn không hoạt động ở API 28 không có ngoại lệ bảng nào như vậy

Nguyên nhân điển hình của việc Ứng dụng sử dụng SQLite và sao chép cơ sở dữ liệu có từ trước đột nhiên không hoạt động cho API 28 là để giải quyết vấn đề của cơ sở dữ liệu thư mục không tồn tại (bản sao sẽ không thành công nếu thư mục không tồn tại) là tạo cơ sở dữ liệu trống và sau đó ghi đè cơ sở dữ liệu.

Tuy nhiên, theo mặc định, từ API 28, SDK sử dụng WAL (Ghi trước nhật ký) và việc tạo cơ sở dữ liệu trống bị ghi đè, dẫn đến các tệp -shm và -wal được tạo. Chính sự tồn tại của các tệp này dẫn đến việc cơ sở dữ liệu bị trống sau khi sao chép.

  • Tôi tin rằng điều này là do khi cơ sở dữ liệu đã sao chép được mở, lỗi sẽ được phát hiện và các phương pháp của SDK tạo ra một cơ sở dữ liệu trống có thể sử dụng được (đây chỉ là phỏng đoán và chưa thực sự được chứng minh).

Khắc phục nhanh

Cách khắc phục nhanh chóng nhưng không được khuyến nghị là ghi đè onConfigure trong lớp phân lớp SQLiteOpenHelper để sử dụng disableWriteAheadLogging để cơ sở dữ liệu được mở ở chế độ nhật ký.

  • mã đầy đủ bên dưới (đoạn mã thứ 2) bao gồm điều này, nhưng dòng đã được nhận xét.

Bản sửa lỗi được đề xuất

Phương pháp được khuyến nghị, để đạt được những lợi ích của WAL, là kiểm tra sự tồn tại của thư mục cơ sở dữ liệu và tạo thư mục nếu nó không tồn tại thay vì tạo cơ sở dữ liệu bị ghi đè ( và do đó - tệp shm và -wal không tồn tại khi cơ sở dữ liệu được sao chép )

Sau đây là một phương pháp ví dụ trong đó thư mục được kiểm tra / tạo khi kiểm tra xem cơ sở dữ liệu có tồn tại hay không ( rõ ràng điều này sẽ cần được điều chỉnh cho phù hợp ):-

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()) {
        db.getParentFile().mkdirs();
        dbdir.mkdirs();
    }
    return false;
}
  • Lưu ý rằng điều này phụ thuộc vào việc biến DB_NAME là tên cơ sở dữ liệu (tên tệp của tệp cơ sở dữ liệu) và vị trí cuối cùng của cơ sở dữ liệu là vị trí chuẩn (data / data / the_package / databases /).

Ở trên đã được trích xuất từ ​​lớp con sau của SQLiteOpenHelper:-

public class DBHelper extends SQLiteOpenHelper {

    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;

    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();
        }
    }

    /**
     * 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 exists
        } 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();
            //<<<<<<<<<< Dimsiss the above comment
            //By calling this method an empty database IS NOT created nor are the related -shm and -wal files
            //The method that creates the database is flawed and was only used to resolve the issue
            //of the copy failing in the absence of the databases directory.
            //The dbExist method, now utilised, checks for and creates the database directory, so there
            //is then no need to create the database just to create the databases library. As a result
            //the -shm and -wal files will not exist and thus result in the error associated with
            //Android 9+ failing with due to tables not existining after an apparently successful
            //copy.
            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
        Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove for Live 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;
    }

    /**
     * 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."); //TODO remove for Live App
        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); //TODO remove for Live App

        // Path to the just created empty db
        //String outFileName = DB_PATH + DB_NAME;
        //Open the empty db as the output stream
        File outfile = new File(myContext.getDatabasePath(DB_NAME).toString());
        Log.d("DBPATH","path is " + outfile.getPath()); //TODO remove for Live App
        //outfile.setWritable(true); // NOT NEEDED as permission already applies
        //OutputStream myoutputx2 = new FileOutputStream(outfile);
        /* Note done in checkDatabase method
        if (!outfile.getParentFile().exists()) {
            outfile.getParentFile().mkdirs();
        }
        */

        OutputStream myOutput = new FileOutputStream(outfile);
        //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."); //TODO remove for Live App
            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) + "."
        ); //TODO remove for Live App
        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
        Log.d(TAG,"All Streams have been flushed and closed."); //TODO remove for Live App
    }


    @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 "); //TODO remove for Live App
        //db.disableWriteAheadLogging(); //<<<<<<<<<< un-comment to force journal mode
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        Log.d("DBOPENED","Database has been opened."); //TODO remove for live App
    }
}
  • Lưu ý rằng đoạn mã trên là / dành cho phát triển / thử nghiệm và do đó bao gồm mã có thể bị xóa.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Logcat cho biết dữ liệu cột không hợp lệ1

  2. Xử lý xung đột khóa chính khi chèn dữ liệu vào SQLite

  3. Chỉnh sửa Kết quả Tr.bình () của bạn trong SQLite với Từ khoá DISTINCT

  4. Lựa chọn cơ sở dữ liệu để tạo hai bảng được kết nối?

  5. Đọc cơ sở dữ liệu từ thư mục nội dung