จัดส่งแอปพลิเคชันพร้อมฐานข้อมูล


959

หากแอปพลิเคชันของคุณต้องการฐานข้อมูลและมาพร้อมกับข้อมูลในตัววิธีที่ดีที่สุดในการจัดส่งแอปพลิเคชันนั้นคืออะไร ฉันควร:

  1. สอบฐานข้อมูล SQLite และรวมไว้ใน.apk?

  2. รวมคำสั่ง SQL กับแอปพลิเคชันและสร้างฐานข้อมูลและแทรกข้อมูลเมื่อใช้ครั้งแรกหรือไม่

ข้อเสียที่ฉันเห็นคือ:

  1. เวอร์ชัน SQLite ที่เป็นไปได้ไม่ตรงกันอาจทำให้เกิดปัญหาและฉันไม่รู้ว่าฐานข้อมูลควรไปที่ใดและวิธีการเข้าถึง

  2. อาจใช้เวลานานในการสร้างและเติมฐานข้อมูลบนอุปกรณ์

ข้อเสนอแนะใด ๆ ตัวชี้ไปยังเอกสารเกี่ยวกับปัญหาใด ๆ จะได้รับการชื่นชมอย่างมาก



คำตอบ:


199

มีสองตัวเลือกสำหรับการสร้างและอัพเดทฐานข้อมูล

หนึ่งคือการสร้างฐานข้อมูลภายนอกจากนั้นวางไว้ในโฟลเดอร์สินทรัพย์ของโครงการแล้วคัดลอกฐานข้อมูลทั้งหมดจากที่นั่น สิ่งนี้เร็วกว่ามากหากฐานข้อมูลมีตารางและส่วนประกอบอื่น ๆ จำนวนมาก การอัพเกรดจะถูกทริกเกอร์โดยการเปลี่ยนหมายเลขเวอร์ชันของฐานข้อมูลในไฟล์ res / values ​​/ strings.xml การอัปเกรดจะสำเร็จได้โดยการสร้างฐานข้อมูลใหม่ภายนอกแทนที่ฐานข้อมูลเก่าในโฟลเดอร์สินทรัพย์ด้วยฐานข้อมูลใหม่บันทึกฐานข้อมูลเก่าในที่เก็บข้อมูลภายในภายใต้ชื่ออื่นคัดลอกฐานข้อมูลใหม่จากโฟลเดอร์สินทรัพย์ไปยังที่เก็บข้อมูลภายในโอนทั้งหมด ของข้อมูลจากฐานข้อมูลเก่า (ที่ถูกเปลี่ยนชื่อก่อนหน้านี้) เป็นฐานข้อมูลใหม่และในที่สุดก็ลบฐานข้อมูลเก่า คุณสามารถสร้างฐานข้อมูลเดิมโดยใช้ปลั๊กอิน SQLite Manager FireFoxเพื่อประมวลผลคำสั่ง sql ของคุณ

ตัวเลือกอื่นคือการสร้างฐานข้อมูลภายในจากไฟล์ sql นี่ไม่ใช่ความรวดเร็ว แต่ความล่าช้าอาจไม่สามารถสังเกตเห็นได้โดยผู้ใช้หากฐานข้อมูลมีเพียงไม่กี่ตาราง การอัพเกรดจะถูกทริกเกอร์โดยการเปลี่ยนหมายเลขเวอร์ชันของฐานข้อมูลในไฟล์ res / values ​​/ strings.xml การอัปเกรดจะสามารถทำได้โดยการประมวลผลไฟล์อัพเกรด SQL ข้อมูลในฐานข้อมูลจะยังคงไม่เปลี่ยนแปลงยกเว้นเมื่อคอนเทนเนอร์ถูกลบออกเช่นวางตาราง

ตัวอย่างด้านล่างแสดงวิธีใช้ทั้งสองวิธี

นี่คือตัวอย่างไฟล์ create_database.sql มันจะถูกวางไว้ในโฟลเดอร์สินทรัพย์ของโครงการสำหรับวิธีการภายในหรือคัดลอกลงใน "Execute SQL 'ของ SQLite Manager เพื่อสร้างฐานข้อมูลสำหรับวิธีการภายนอก (หมายเหตุ: โปรดสังเกตความคิดเห็นเกี่ยวกับตารางที่ Android ต้องการ)

--Android requires a table named 'android_metadata' with a 'locale' column
CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US');
INSERT INTO "android_metadata" VALUES ('en_US');

CREATE TABLE "kitchen_table";
CREATE TABLE "coffee_table";
CREATE TABLE "pool_table";
CREATE TABLE "dining_room_table";
CREATE TABLE "card_table"; 

นี่คือตัวอย่างไฟล์ update_database.sql มันจะถูกวางไว้ในโฟลเดอร์สินทรัพย์ของโครงการสำหรับวิธีการภายในหรือคัดลอกลงใน "Execute SQL 'ของ SQLite Manager เพื่อสร้างฐานข้อมูลสำหรับวิธีภายนอก (หมายเหตุ: โปรดสังเกตว่าความคิดเห็น SQL ทั้งสามประเภทจะถูกละเว้น โดยตัวแยกวิเคราะห์ sql ที่รวมอยู่ในตัวอย่างนี้)

--CREATE TABLE "kitchen_table";  This is one type of comment in sql.  It is ignored by parseSql.
/*
 * CREATE TABLE "coffee_table"; This is a second type of comment in sql.  It is ignored by parseSql.
 */
{
CREATE TABLE "pool_table";  This is a third type of comment in sql.  It is ignored by parseSql.
}
/* CREATE TABLE "dining_room_table"; This is a second type of comment in sql.  It is ignored by parseSql. */
{ CREATE TABLE "card_table"; This is a third type of comment in sql.  It is ignored by parseSql. }

--DROP TABLE "picnic_table"; Uncomment this if picnic table was previously created and now is being replaced.
CREATE TABLE "picnic_table" ("plates" TEXT);
INSERT INTO "picnic_table" VALUES ('paper');

นี่คือรายการที่จะเพิ่มลงในไฟล์ /res/values/strings.xml สำหรับหมายเลขเวอร์ชันฐานข้อมูล

<item type="string" name="databaseVersion" format="integer">1</item>

นี่คือกิจกรรมที่เข้าถึงฐานข้อมูลจากนั้นใช้ ( หมายเหตุ: คุณอาจต้องการเรียกใช้รหัสฐานข้อมูลในเธรดแยกต่างหากถ้าใช้ทรัพยากรเป็นจำนวนมาก )

package android.example;

import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;

/**
 * @author Danny Remington - MacroSolve
 * 
 *         Activity for demonstrating how to use a sqlite database.
 */
public class Database extends Activity {
     /** Called when the activity is first created. */
     @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        DatabaseHelper myDbHelper;
        SQLiteDatabase myDb = null;

        myDbHelper = new DatabaseHelper(this);
        /*
         * Database must be initialized before it can be used. This will ensure
         * that the database exists and is the current version.
         */
         myDbHelper.initializeDataBase();

         try {
            // A reference to the database can be obtained after initialization.
            myDb = myDbHelper.getWritableDatabase();
            /*
             * Place code to use database here.
             */
         } catch (Exception ex) {
            ex.printStackTrace();
         } finally {
            try {
                myDbHelper.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                myDb.close();
            }
        }

    }
}

นี่คือคลาสตัวช่วยฐานข้อมูลที่ฐานข้อมูลถูกสร้างขึ้นหรืออัพเดตหากจำเป็น (หมายเหตุ: Android ต้องการให้คุณสร้างคลาสที่ขยาย SQLiteOpenHelper เพื่อทำงานกับฐานข้อมูล Sqlite)

package android.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * @author Danny Remington - MacroSolve
 * 
 *         Helper class for sqlite database.
 */
public class DatabaseHelper extends SQLiteOpenHelper {

    /*
     * The Android's default system path of the application database in internal
     * storage. The package of the application is part of the path of the
     * directory.
     */
    private static String DB_DIR = "/data/data/android.example/databases/";
    private static String DB_NAME = "database.sqlite";
    private static String DB_PATH = DB_DIR + DB_NAME;
    private static String OLD_DB_PATH = DB_DIR + "old_" + DB_NAME;

    private final Context myContext;

    private boolean createDatabase = false;
    private boolean upgradeDatabase = false;

    /**
     * Constructor Takes and keeps a reference of the passed context in order to
     * access to the application assets and resources.
     * 
     * @param context
     */
    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, context.getResources().getInteger(
                R.string.databaseVersion));
        myContext = context;
        // Get the path of the database that is based on the context.
        DB_PATH = myContext.getDatabasePath(DB_NAME).getAbsolutePath();
    }

    /**
     * Upgrade the database in internal storage if it exists but is not current. 
     * Create a new empty database in internal storage if it does not exist.
     */
    public void initializeDataBase() {
        /*
         * Creates or updates the database in internal storage if it is needed
         * before opening the database. In all cases opening the database copies
         * the database in internal storage to the cache.
         */
        getWritableDatabase();

        if (createDatabase) {
            /*
             * If the database is created by the copy method, then the creation
             * code needs to go here. This method consists of copying the new
             * database from assets into internal storage and then caching it.
             */
            try {
                /*
                 * Write over the empty data that was created in internal
                 * storage with the one in assets and then cache it.
                 */
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        } else if (upgradeDatabase) {
            /*
             * If the database is upgraded by the copy and reload method, then
             * the upgrade code needs to go here. This method consists of
             * renaming the old database in internal storage, create an empty
             * new database in internal storage, copying the database from
             * assets to the new database in internal storage, caching the new
             * database from internal storage, loading the data from the old
             * database into the new database in the cache and then deleting the
             * old database from internal storage.
             */
            try {
                FileHelper.copyFile(DB_PATH, OLD_DB_PATH);
                copyDataBase();
                SQLiteDatabase old_db = SQLiteDatabase.openDatabase(OLD_DB_PATH, null, SQLiteDatabase.OPEN_READWRITE);
                SQLiteDatabase new_db = SQLiteDatabase.openDatabase(DB_PATH,null, SQLiteDatabase.OPEN_READWRITE);
                /*
                 * Add code to load data into the new database from the old
                 * database and then delete the old database from internal
                 * storage after all data has been transferred.
                 */
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }

    }

    /**
     * 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 {
        /*
         * Close SQLiteOpenHelper so it will commit the created empty database
         * to internal storage.
         */
        close();

        /*
         * Open the database in the assets folder as the input stream.
         */
        InputStream myInput = myContext.getAssets().open(DB_NAME);

        /*
         * Open the empty db in interal storage as the output stream.
         */
        OutputStream myOutput = new FileOutputStream(DB_PATH);

        /*
         * Copy over the empty db in internal storage with the database in the
         * assets folder.
         */
        FileHelper.copyFile(myInput, myOutput);

        /*
         * Access the copied database so SQLiteHelper will cache it and mark it
         * as created.
         */
        getWritableDatabase().close();
    }

    /*
     * This is where the creation of tables and the initial population of the
     * tables should happen, if a database is being created from scratch instead
     * of being copied from the application package assets. Copying a database
     * from the application package assets to internal storage inside this
     * method will result in a corrupted database.
     * <P>
     * NOTE: This method is normally only called when a database has not already
     * been created. When the database has been copied, then this method is
     * called the first time a reference to the database is retrieved after the
     * database is copied since the database last cached by SQLiteOpenHelper is
     * different than the database in internal storage.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        /*
         * Signal that a new database needs to be copied. The copy process must
         * be performed after the database in the cache has been closed causing
         * it to be committed to internal storage. Otherwise the database in
         * internal storage will not have the same creation timestamp as the one
         * in the cache causing the database in internal storage to be marked as
         * corrupted.
         */
        createDatabase = true;

        /*
         * This will create by reading a sql file and executing the commands in
         * it.
         */
            // try {
            // InputStream is = myContext.getResources().getAssets().open(
            // "create_database.sql");
            //
            // String[] statements = FileHelper.parseSqlFile(is);
            //
            // for (String statement : statements) {
            // db.execSQL(statement);
            // }
            // } catch (Exception ex) {
            // ex.printStackTrace();
            // }
    }

    /**
     * Called only if version number was changed and the database has already
     * been created. Copying a database from the application package assets to
     * the internal data system inside this method will result in a corrupted
     * database in the internal data system.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        /*
         * Signal that the database needs to be upgraded for the copy method of
         * creation. The copy process must be performed after the database has
         * been opened or the database will be corrupted.
         */
        upgradeDatabase = true;

        /*
         * Code to update the database via execution of sql statements goes
         * here.
         */

        /*
         * This will upgrade by reading a sql file and executing the commands in
         * it.
         */
        // try {
        // InputStream is = myContext.getResources().getAssets().open(
        // "upgrade_database.sql");
        //
        // String[] statements = FileHelper.parseSqlFile(is);
        //
        // for (String statement : statements) {
        // db.execSQL(statement);
        // }
        // } catch (Exception ex) {
        // ex.printStackTrace();
        // }
    }

    /**
     * Called everytime the database is opened by getReadableDatabase or
     * getWritableDatabase. This is called after onCreate or onUpgrade is
     * called.
     */
    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
    }

    /*
     * Add your public helper methods to access and get content from the
     * database. You could return cursors by doing
     * "return myDataBase.query(....)" so it'd be easy to you to create adapters
     * for your views.
     */

}

นี่คือคลาส FileHelper ที่มีเมธอดสำหรับการคัดลอกไบต์ไฟล์และแยกไฟล์ sql

package android.example;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.channels.FileChannel;

/**
 * @author Danny Remington - MacroSolve
 * 
 *         Helper class for common tasks using files.
 * 
 */
public class FileHelper {
    /**
     * Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy
     * of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then
     * it will be replaced with a copy of <i><b>fromFile</b></i>. The name and
     * path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both
     * <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - InputStream for the file to copy from.
     * @param toFile
     *            - InputStream for the file to copy to.
     */
    public static void copyFile(InputStream fromFile, OutputStream toFile) throws IOException {
        // transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[1024];
        int length;

        try {
            while ((length = fromFile.read(buffer)) > 0) {
                toFile.write(buffer, 0, length);
            }
        }
        // Close the streams
        finally {
            try {
                if (toFile != null) {
                    try {
                        toFile.flush();
                    } finally {
                        toFile.close();
                    }
            }
            } finally {
                if (fromFile != null) {
                    fromFile.close();
                }
            }
        }
    }

    /**
     * Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy
     * of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then
     * it will be replaced with a copy of <i><b>fromFile</b></i>. The name and
     * path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both
     * <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - String specifying the path of the file to copy from.
     * @param toFile
     *            - String specifying the path of the file to copy to.
     */
    public static void copyFile(String fromFile, String toFile) throws IOException {
        copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));
    }

    /**
     * Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy
     * of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then
     * it will be replaced with a copy of <i><b>fromFile</b></i>. The name and
     * path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both
     * <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - File for the file to copy from.
     * @param toFile
     *            - File for the file to copy to.
     */
    public static void copyFile(File fromFile, File toFile) throws IOException {
        copyFile(new FileInputStream(fromFile), new FileOutputStream(toFile));
    }

    /**
     * Creates the specified <i><b>toFile</b></i> that is a byte for byte a copy
     * of <i><b>fromFile</b></i>. If <i><b>toFile</b></i> already existed, then
     * it will be replaced with a copy of <i><b>fromFile</b></i>. The name and
     * path of <i><b>toFile</b></i> will be that of <i><b>toFile</b></i>. Both
     * <i><b>fromFile</b></i> and <i><b>toFile</b></i> will be closed by this
     * operation.
     * 
     * @param fromFile
     *            - FileInputStream for the file to copy from.
     * @param toFile
     *            - FileInputStream for the file to copy to.
     */
    public static void copyFile(FileInputStream fromFile, FileOutputStream toFile) throws IOException {
        FileChannel fromChannel = fromFile.getChannel();
        FileChannel toChannel = toFile.getChannel();

        try {
            fromChannel.transferTo(0, fromChannel.size(), toChannel);
        } finally {
            try {
                if (fromChannel != null) {
                    fromChannel.close();
                }
            } finally {
                if (toChannel != null) {
                    toChannel.close();
                }
            }
        }
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - String containing the path for the file that contains sql
     *            statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(String sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(new FileReader(sqlFile)));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - InputStream for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(InputStream sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(new InputStreamReader(sqlFile)));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - Reader for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(Reader sqlFile) throws IOException {
        return parseSqlFile(new BufferedReader(sqlFile));
    }

    /**
     * Parses a file containing sql statements into a String array that contains
     * only the sql statements. Comments and white spaces in the file are not
     * parsed into the String array. Note the file must not contained malformed
     * comments and all sql statements must end with a semi-colon ";" in order
     * for the file to be parsed correctly. The sql statements in the String
     * array will not end with a semi-colon ";".
     * 
     * @param sqlFile
     *            - BufferedReader for the file that contains sql statements.
     * 
     * @return String array containing the sql statements.
     */
    public static String[] parseSqlFile(BufferedReader sqlFile) throws IOException {
        String line;
        StringBuilder sql = new StringBuilder();
        String multiLineComment = null;

        while ((line = sqlFile.readLine()) != null) {
            line = line.trim();

            // Check for start of multi-line comment
            if (multiLineComment == null) {
                // Check for first multi-line comment type
                if (line.startsWith("/*")) {
                    if (!line.endsWith("}")) {
                        multiLineComment = "/*";
                    }
                // Check for second multi-line comment type
                } else if (line.startsWith("{")) {
                    if (!line.endsWith("}")) {
                        multiLineComment = "{";
                }
                // Append line if line is not empty or a single line comment
                } else if (!line.startsWith("--") && !line.equals("")) {
                    sql.append(line);
                } // Check for matching end comment
            } else if (multiLineComment.equals("/*")) {
                if (line.endsWith("*/")) {
                    multiLineComment = null;
                }
            // Check for matching end comment
            } else if (multiLineComment.equals("{")) {
                if (line.endsWith("}")) {
                    multiLineComment = null;
                }
            }

        }

        sqlFile.close();

        return sql.toString().split(";");
    }

}

ฉันใช้รหัสด้านบนเพื่ออัพเกรด db ของฉัน "upgrade_database.sql มีคำสั่ง insert. บางส่วนของค่ามีเซมิโคลอนเช่นแทรกลงใน table_a ค่า ('ss', 'ddd', 'aaaa; aaa');" เมื่อฉันเรียกใช้ ฉันสังเกตเห็นข้างต้นกล่าวถึงการแทรกไม่ได้รับการเรียกเนื่องจาก semicoln ในค่าใด ๆ ides วิธีการแก้ไขนี้
Sam

5
มีตัวเลือกที่สามคือ - คัดลอก db จากเว็บ ฉันทำไปแล้วและมันก็ค่อนข้างรวดเร็วสำหรับ 4 เมกะไบต์เดซิเบล นอกจากนี้ยังแก้ปัญหาด้วย 2.3 ซึ่งวิธีแรก (copy db) ไม่ทำงาน
Jack BeNimble

2
Danny And Austyn - ทางออกของคุณสมบูรณ์แบบ ฉันมีปัญหากับวิธีการชงที่บ้านของฉันและสะดุดกับคุณ มันยอดเยี่ยมจริงๆ ขอบคุณที่สละเวลาให้
George Baker

4
ฉันชอบคำตอบนี้กับผู้ที่ได้รับการโหวตสูงสุดและเป็นที่ยอมรับ มันมีข้อมูลทั้งหมดในที่เดียว (ไม่เห็นส่วนเชื่อมโยงนี้) และกล่าวถึงเฉพาะ Android บางอย่างที่ฉันไม่มีความคิดอยู่ (เช่นสร้างตาราง "android_metadata") นอกจากนี้ยังมีตัวอย่างที่เขียนในรายละเอียดที่ดีซึ่งเป็นข้อดี มันเกือบจะเป็นโซลูชันการคัดลอกวางซึ่งไม่ได้ดีเสมอไป แต่คำอธิบายระหว่างโค้ดนั้นยอดเยี่ยม
Igor Čordaš

ฉันกำลังใช้วิธีการเดียวกัน แต่ฉันประสบปัญหาหนึ่งวิธีที่เราสามารถคัดลอกข้อมูลที่มีอยู่ทั้งหมดจากเก่าไปยังไฟล์ db ใหม่ในวิธีที่ง่ายขึ้น
Pankaj

130

SQLiteAssetHelperห้องสมุดทำให้งานนี้ง่ายจริงๆ

มันง่ายที่จะเพิ่มเป็นการพึ่งพา gradle (แต่ Jar ยังมีให้สำหรับ Ant / Eclipse) และพร้อมกับเอกสารที่สามารถพบได้ที่:
https://github.com/jgilfelt/android-sqlite-asset-helper

หมายเหตุ:โครงการนี้ไม่ได้รับการบำรุงรักษาตามที่ระบุไว้ในลิงค์ Github ด้านบน

ตามที่อธิบายไว้ในเอกสาร:

  1. เพิ่มการพึ่งพาไปยังไฟล์ build gradle ของโมดูลของคุณ:

    dependencies {
        compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
    }
  2. assets/databasesคัดลอกฐานข้อมูลลงในไดเรกทอรีสินทรัพย์ในไดเรกทอรีย่อยที่เรียกว่า ตัวอย่างเช่น
    assets/databases/my_database.db

    (เป็นทางเลือกคุณอาจบีบอัดฐานข้อมูลในไฟล์ zip เช่นassets/databases/my_database.zipนี้ไม่จำเป็นเนื่องจาก APK ถูกบีบอัดโดยรวมแล้ว)

  3. สร้างคลาสตัวอย่างเช่น:

    public class MyDatabase extends SQLiteAssetHelper {
    
        private static final String DATABASE_NAME = "my_database.db";
        private static final int DATABASE_VERSION = 1;
    
        public MyDatabase(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
    }

ดาวน์โหลด android-sqlite-asset-helper.jar ต้องการข้อมูลรับรองอะไรบ้าง
Pr38y

1
หากคุณใช้การไล่ระดับแล้วคุณเพียงแค่เพิ่มการอ้างอิง
Suragch

คุณจะรับข้อมูลจากฐานข้อมูลได้อย่างไร
Machado

มันง่ายยิ่งกว่ากับ Android Studio และ gradle ตรวจสอบลิงค์!
bendaf

5
โปรดทราบว่าไลบรารีนี้ถูกยกเลิกพร้อมอัปเดตล่าสุด 4 ปีที่ผ่านมา
กิจกรรมลด

13

โซลูชันของฉันไม่ใช้ห้องสมุดบุคคลที่สามหรือบังคับให้คุณเรียกวิธีการที่กำหนดเองในSQLiteOpenHelperคลาสย่อยเพื่อเริ่มต้นฐานข้อมูลในการสร้าง นอกจากนี้ยังดูแลการอัพเกรดฐานข้อมูลด้วย ทั้งหมดที่จะต้องทำคือการ SQLiteOpenHelpersubclass

วิชาบังคับก่อน:

  1. ฐานข้อมูลที่คุณต้องการจัดส่งพร้อมแอพ มันควรมีตาราง 1x1 ที่android_metadataมีชื่อซึ่งมีแอตทริบิวต์ที่localeมีค่าen_USเพิ่มเติมจากตารางที่ไม่ซ้ำกับแอปของคุณ

การแบ่งคลาสย่อยSQLiteOpenHelper:

  1. SQLiteOpenHelperประเภทรอง
  2. สร้างprivateวิธีการภายในSQLiteOpenHelperคลาสย่อย วิธีนี้มีตรรกะในการคัดลอกเนื้อหาฐานข้อมูลจากไฟล์ฐานข้อมูลในโฟลเดอร์ 'asset' ไปยังฐานข้อมูลที่สร้างขึ้นในบริบทของแพ็คเกจแอปพลิเคชัน
  3. แทนที่onCreate, onUpgrade และ วิธีการของonOpenSQLiteOpenHelper

พูดพอแล้ว. SQLiteOpenHelperคลาสย่อยจะไปที่นี่:

public class PlanDetailsSQLiteOpenHelper extends SQLiteOpenHelper {
    private static final String TAG = "SQLiteOpenHelper";

    private final Context context;
    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "my_custom_db";

    private boolean createDb = false, upgradeDb = false;

    public PlanDetailsSQLiteOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    /**
     * Copy packaged database from assets folder to the database created in the
     * application package context.
     * 
     * @param db
     *            The target database in the application package context.
     */
    private void copyDatabaseFromAssets(SQLiteDatabase db) {
        Log.i(TAG, "copyDatabase");
        InputStream myInput = null;
        OutputStream myOutput = null;
        try {
            // Open db packaged as asset as the input stream
            myInput = context.getAssets().open("path/to/shipped/db/file");

            // Open the db in the application package context:
            myOutput = new FileOutputStream(db.getPath());

            // Transfer db file contents:
            byte[] buffer = new byte[1024];
            int length;
            while ((length = myInput.read(buffer)) > 0) {
                myOutput.write(buffer, 0, length);
            }
            myOutput.flush();

            // Set the version of the copied database to the current
            // version:
            SQLiteDatabase copiedDb = context.openOrCreateDatabase(
                DATABASE_NAME, 0, null);
            copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION);
            copiedDb.close();

        } catch (IOException e) {
            e.printStackTrace();
            throw new Error(TAG + " Error copying database");
        } finally {
            // Close the streams
            try {
                if (myOutput != null) {
                    myOutput.close();
                }
                if (myInput != null) {
                    myInput.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new Error(TAG + " Error closing streams");
            }
        }
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.i(TAG, "onCreate db");
        createDb = true;
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.i(TAG, "onUpgrade db");
        upgradeDb = true;
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        Log.i(TAG, "onOpen db");
        if (createDb) {// The db in the application package
            // context is being created.
            // So copy the contents from the db
            // file packaged in the assets
            // folder:
            createDb = false;
            copyDatabaseFromAssets(db);

        }
        if (upgradeDb) {// The db in the application package
            // context is being upgraded from a lower to a higher version.
            upgradeDb = false;
            // Your db upgrade logic here:
        }
    }
}

สุดท้ายเพื่อรับการเชื่อมต่อฐานข้อมูลเพียงโทรgetReadableDatabase()หรือgetWritableDatabase()บนSQLiteOpenHelperคลาสย่อยและมันจะดูแลการสร้างฐานข้อมูลคัดลอกเนื้อหาฐานข้อมูลจากไฟล์ที่ระบุในโฟลเดอร์ 'สินทรัพย์' หากฐานข้อมูลไม่อยู่

ในระยะสั้นคุณสามารถใช้SQLiteOpenHelperคลาสย่อยเพื่อเข้าถึง db ที่จัดส่งในโฟลเดอร์สินทรัพย์เช่นเดียวกับที่คุณจะใช้สำหรับฐานข้อมูลที่เริ่มต้นโดยใช้แบบสอบถาม SQL ในonCreate()วิธีการ


2
นี่เป็นโซลูชันที่หรูหราที่สุดโดยใช้ API Android มาตรฐานโดยไม่จำเป็นต้องใช้ไลบรารีภายนอก ตามบันทึกแล้วฉันไม่ได้รวมตาราง android_metadata และใช้งานได้เวอร์ชั่น Android รุ่นใหม่อาจเพิ่มโดยอัตโนมัติ
goetzc

12

จัดส่งแอปด้วยไฟล์ฐานข้อมูลใน Android Studio 3.0

การส่งแอพด้วยไฟล์ฐานข้อมูลเป็นความคิดที่ดีสำหรับฉัน ข้อดีคือคุณไม่จำเป็นต้องทำการเริ่มต้นที่ซับซ้อนซึ่งบางครั้งมีค่าใช้จ่ายมากหากชุดข้อมูลของคุณมีขนาดใหญ่

ขั้นตอนที่ 1: เตรียมไฟล์ฐานข้อมูล

เตรียมไฟล์ฐานข้อมูลของคุณให้พร้อม มันอาจเป็นได้ทั้งไฟล์. db หรือไฟล์. sqlite หากคุณใช้ไฟล์. sqlite สิ่งที่คุณต้องทำคือเปลี่ยนชื่อนามสกุลไฟล์ ขั้นตอนเหมือนกัน

ในตัวอย่างนี้ฉันเตรียมไฟล์ชื่อ testDB.db มันมีหนึ่งตารางและมีข้อมูลตัวอย่างอยู่ในนี้ ป้อนคำอธิบายรูปภาพที่นี่

ขั้นตอนที่ 2: นำเข้าไฟล์ไปยังโครงการของคุณ

สร้างโฟลเดอร์สินทรัพย์หากคุณยังไม่มี จากนั้นคัดลอกและวางไฟล์ฐานข้อมูลลงในโฟลเดอร์นี้

ป้อนคำอธิบายรูปภาพที่นี่

ขั้นตอนที่ 3: คัดลอกไฟล์ไปยังโฟลเดอร์ข้อมูลของแอป

คุณต้องคัดลอกไฟล์ฐานข้อมูลไปยังโฟลเดอร์ข้อมูลของแอปเพื่อทำการโต้ตอบเพิ่มเติมกับมัน นี่เป็นการกระทำครั้งเดียว (การกำหนดค่าเริ่มต้น) เพื่อคัดลอกไฟล์ฐานข้อมูล หากคุณเรียกรหัสนี้หลายครั้งไฟล์ฐานข้อมูลในโฟลเดอร์ข้อมูลจะถูกเขียนทับโดยไฟล์ในโฟลเดอร์ทรัพย์สิน กระบวนการเขียนทับนี้มีประโยชน์เมื่อคุณต้องการอัปเดตฐานข้อมูลในอนาคตในระหว่างการอัปเดตแอป

โปรดทราบว่าในระหว่างการอัปเดตแอปไฟล์ฐานข้อมูลนี้จะไม่ถูกเปลี่ยนแปลงในโฟลเดอร์ข้อมูลของแอพ ถอนการติดตั้งเท่านั้นที่จะลบ

ไฟล์ฐานข้อมูลจะต้องคัดลอกไปยัง/databasesโฟลเดอร์ เปิด Device File Explorer ป้อนdata/data/<YourAppName>/สถานที่ นี่คือโฟลเดอร์ข้อมูลเริ่มต้นของแอพที่กล่าวถึงข้างต้น และตามค่าเริ่มต้นไฟล์ฐานข้อมูลจะอยู่ในโฟลเดอร์อื่นที่เรียกว่าฐานข้อมูลภายใต้ไดเรกทอรีนี้

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้กระบวนการคัดลอกไฟล์ค่อนข้างจะเหมือนกับ Java ที่กำลังทำอยู่ ใช้รหัสต่อไปนี้เพื่อคัดลอกวาง นี่คือรหัสการเริ่มต้น นอกจากนี้ยังสามารถใช้เพื่ออัปเดต (โดยเขียนทับ) ไฟล์ฐานข้อมูลในอนาคต

//get context by calling "this" in activity or getActivity() in fragment
//call this if API level is lower than 17  String appDataPath = "/data/data/" + context.getPackageName() + "/databases/"
String appDataPath = context.getApplicationInfo().dataDir;

File dbFolder = new File(appDataPath + "/databases");//Make sure the /databases folder exists
dbFolder.mkdir();//This can be called multiple times.

File dbFilePath = new File(appDataPath + "/databases/testDB.db");

try {
    InputStream inputStream = context.getAssets().open("testDB.db");
    OutputStream outputStream = new FileOutputStream(dbFilePath);
    byte[] buffer = new byte[1024];
    int length;
    while ((length = inputStream.read(buffer))>0)
    {
        outputStream.write(buffer, 0, length);
    }
    outputStream.flush();
    outputStream.close();
    inputStream.close();
} catch (IOException e){
    //handle
}

จากนั้นรีเฟรชโฟลเดอร์เพื่อตรวจสอบกระบวนการคัดลอก

ป้อนคำอธิบายรูปภาพที่นี่

ขั้นตอนที่ 4: สร้างตัวช่วยเปิดฐานข้อมูล

สร้างคลาสย่อยสำหรับSQLiteOpenHelper, พร้อมกับเชื่อมต่อ, ปิด, พา ธ , ฯลฯ ฉันตั้งชื่อมันDatabaseOpenHelper

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseOpenHelper extends SQLiteOpenHelper {
    public static final String DB_NAME = "testDB.db";
    public static final String DB_SUB_PATH = "/databases/" + DB_NAME;
    private static String APP_DATA_PATH = "";
    private SQLiteDatabase dataBase;
    private final Context context;

    public DatabaseOpenHelper(Context context){
        super(context, DB_NAME, null, 1);
        APP_DATA_PATH = context.getApplicationInfo().dataDir;
        this.context = context;
    }

    public boolean openDataBase() throws SQLException{
        String mPath = APP_DATA_PATH + DB_SUB_PATH;
        //Note that this method assumes that the db file is already copied in place
        dataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.OPEN_READWRITE);
        return dataBase != null;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

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

ขั้นตอนที่ 5: สร้างคลาสระดับบนสุดเพื่อโต้ตอบกับฐานข้อมูล

นี่จะเป็นคลาสที่อ่านและเขียนไฟล์ฐานข้อมูลของคุณ นอกจากนี้ยังมีแบบสอบถามตัวอย่างเพื่อพิมพ์ค่าในฐานข้อมูล

import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class Database {
    private final Context context;
    private SQLiteDatabase database;
    private DatabaseOpenHelper dbHelper;

    public Database(Context context){
        this.context = context;
        dbHelper = new DatabaseOpenHelper(context);
    }

    public Database open() throws SQLException
    {
        dbHelper.openDataBase();
        dbHelper.close();
        database = dbHelper.getReadableDatabase();
        return this;
    }

    public void close()
    {
        dbHelper.close();
    }

    public void test(){
        try{
            String query ="SELECT value FROM test1";
            Cursor cursor = database.rawQuery(query, null);
            if (cursor.moveToFirst()){
                do{
                    String value = cursor.getString(0);
                    Log.d("db", value);
                }while (cursor.moveToNext());
            }
            cursor.close();
        } catch (SQLException e) {
            //handle
        }
    }
}

ขั้นตอนที่ 6: ทดสอบการทำงาน

ทดสอบรหัสโดยเรียกใช้บรรทัดของรหัสต่อไปนี้

Database db = new Database(context);
db.open();
db.test();
db.close();

กดปุ่ม Run และให้กำลังใจ!

ป้อนคำอธิบายรูปภาพที่นี่


1
การเริ่มต้นควรทำเมื่อไหร่? กลยุทธ์ที่คุณแนะนำคืออะไร?
Daniele B

8

ในพฤศจิกายน 2017 Google ปล่อยคงทนห้องสมุดห้อง

จากเอกสาร:

ห้องสมุดห้องติดตาให้ชั้นนามธรรมมากกว่า SQ ข้อความที่แข็งแกร่ง Lite เพื่อให้สามารถเข้าถึงฐานข้อมูลได้อย่างคล่องแคล่วในขณะที่การควบคุมอำนาจเต็มของ SQLite

ไลบรารีช่วยให้คุณสร้างแคชของข้อมูลแอปของคุณบนอุปกรณ์ที่ใช้แอพของคุณ แคชนี้ซึ่งทำหน้าที่เป็นแหล่งความจริงเดียวของแอปของคุณอนุญาตให้ผู้ใช้ดูสำเนาข้อมูลสำคัญที่สอดคล้องกันภายในแอปของคุณโดยไม่คำนึงว่าผู้ใช้มีการเชื่อมต่ออินเทอร์เน็ตหรือไม่

ฐานข้อมูลห้องมีการติดต่อกลับเมื่อสร้างหรือเปิดฐานข้อมูลเป็นครั้งแรก คุณสามารถใช้ callback สร้างเพื่อเติมฐานข้อมูลของคุณ

Room.databaseBuilder(context.applicationContext,
        DataDatabase::class.java, "Sample.db")
        // prepopulate the database after onCreate was called
        .addCallback(object : Callback() {
            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                // moving to a new thread
                ioThread {
                    getInstance(context).dataDao()
                                        .insert(PREPOPULATE_DATA)
                }
            }
        })
        .build()

รหัสจากโพสต์บล็อกนี้


ขอบคุณสิ่งนี้ใช้ได้สำหรับฉัน ตัวอย่าง Java ที่นี่
Jerry Sha

1
หากคุณต้องการจัดส่ง APK ด้วย SQLite ที่มีอยู่แล้วคุณสามารถเพิ่มลงในโฟลเดอร์สินทรัพย์และใช้แพ็คเกจนี้github.com/humazed/RoomAssetเพื่อทำการย้ายข้อมูลที่จะโหลดข้อมูลไฟล์ SQLite ลงในใหม่ วิธีนี้คุณสามารถบันทึกการเติมข้อมูลด้วยฐานข้อมูลที่มีอยู่
xarlymg89

6

จากสิ่งที่ฉันเห็นคุณควรจัดส่งฐานข้อมูลที่มีการตั้งค่าตารางและข้อมูลอยู่แล้ว อย่างไรก็ตามหากคุณต้องการ (และขึ้นอยู่กับประเภทของแอปพลิเคชั่นที่คุณมี) คุณสามารถอนุญาตให้ "อัพเกรดฐานข้อมูลตัวเลือก" จากนั้นสิ่งที่คุณทำคือดาวน์โหลดเวอร์ชันล่าสุดของ sqlite รับคำสั่งแทรก / สร้างล่าสุดของ textfile ที่โฮสต์ออนไลน์เรียกใช้คำสั่งและทำการถ่ายโอนข้อมูลจากฐานข้อมูลเก่าไปยังใหม่


6
> จากสิ่งที่ฉันเห็นคุณควรจัดส่งฐานข้อมูลที่มีการตั้งค่าตารางและข้อมูลอยู่แล้ว ใช่ แต่คุณจะทำอย่างไร
Rory

5

ในที่สุดฉันก็ทำมัน !! ฉันได้ใช้ลิงก์นี้ช่วยในการใช้ฐานข้อมูล SQLite ของคุณเองในแอปพลิเคชัน Androidแต่ต้องเปลี่ยนนิดหน่อย

  1. หากคุณมีหลายแพ็คเกจคุณควรใส่ชื่อแพ็คเกจหลักที่นี่:

    private static String DB_PATH = "data/data/masterPakageName/databases";

  2. ฉันเปลี่ยนวิธีการที่คัดลอกฐานข้อมูลจากโฟลเดอร์ในเครื่องไปยังโฟลเดอร์โปรแกรมจำลอง! มันมีปัญหาบางอย่างเมื่อไม่มีโฟลเดอร์ดังกล่าว ดังนั้นก่อนอื่นควรตรวจสอบเส้นทางและหากไม่มีอยู่ก็ควรสร้างโฟลเดอร์

  3. ในรหัสก่อนหน้านี้ copyDatabaseวิธีการที่ไม่เคยเรียกว่าเมื่อฐานข้อมูลไม่ได้อยู่และcheckDataBaseวิธีการที่ก่อให้เกิดข้อยกเว้น ดังนั้นฉันจึงเปลี่ยนรหัสเล็กน้อย

  4. หากฐานข้อมูลของคุณไม่มีนามสกุลไฟล์ห้ามใช้ชื่อไฟล์ด้วย

มันใช้งานได้ดีสำหรับฉันฉันหวังว่ามันจะเป็นประโยชน์สำหรับคุณเช่นกัน

    package farhangsarasIntroduction;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;

import android.content.Context;
import android.database.Cursor;

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;

import android.util.Log;


    public class DataBaseHelper extends SQLiteOpenHelper{

    //The Android's default system path of your application database.
    private static String DB_PATH = "data/data/com.example.sample/databases";

    private static String DB_NAME = "farhangsaraDb";

    private SQLiteDatabase myDataBase;

    private final Context myContext;

    /**
      * Constructor
      * Takes and keeps a reference of the passed context in order to access to the application assets and resources.
      * @param context
      */
    public DataBaseHelper(Context context) {

        super(context, DB_NAME, null, 1);
            this.myContext = context;

    }   

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

        boolean dbExist;
        try {

             dbExist = checkDataBase();


        } catch (SQLiteException e) {

            e.printStackTrace();
            throw new Error("database dose not exist");

        }

        if(dbExist){
        //do nothing - database already exist
        }else{

            try {

                copyDataBase();


            } catch (IOException e) {

                e.printStackTrace();
                throw new Error("Error copying database");

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


    }

    }

    /**
      * 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);
    }catch(SQLiteException e){

    //database does't exist yet.
        throw new Error("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{



            //copyDataBase();
            //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;
            File databaseFile = new File( DB_PATH);
             // check if databases folder exists, if not create one and its subfolders
            if (!databaseFile.exists()){
                databaseFile.mkdir();
            }

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



    }



    @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) {

    }

     you to create adapters for your views.

}

ได้โปรดแจ้งให้เราทราบวิธีอัปเกรดฐานข้อมูลถ้าฉันต้องการแทนที่ฐานข้อมูลเก่าด้วยใหม่ฉันจะลบฐานข้อมูลเก่าได้อย่างไร
Erum

ฉันไม่จำเป็นต้องทำเช่นนี้จนกว่าจะถึงตอนนี้ แต่ถ้าหากมีการติดตั้งแอปใหม่แล้วฐานข้อมูลใหม่ก็จะแทนที่ด้วยเช่นกัน
afsane

วิธีการลบฐานข้อมูลเก่าเพราะฉันกำลังเพิ่มฐานข้อมูลใหม่ในโฟลเดอร์สินทรัพย์แล้วฉันจะลบฐานข้อมูลเก่าออกจากโฟลเดอร์ที่ระบุได้อย่างไรมิฉะนั้นจะนำเนื้อหาของฐานข้อมูลเก่าออกมา
Erum

ฉันหวังว่านี่จะเป็นประโยชน์อย่างมากstackoverflow.com/questions/9109438/…
afsane

สมบูรณ์แบบขอบคุณ! เพียงแค่ความคิดเห็นเดียวการโยนข้อยกเว้นในการตรวจสอบฐานข้อมูลทำให้แอปปิดเนื่องจากฐานข้อมูลจะไม่อยู่ที่จุดเริ่มต้นและวิธีการไม่ดำเนินการต่อหลังจากโยนข้อยกเว้น ฉันเพียงแค่แสดงความคิดเห็นออกข้อผิดพลาดใหม่โยน ("ปริมาณฐานข้อมูลไม่อยู่"); และตอนนี้ทุกอย่างทำงานได้อย่างสมบูรณ์แบบ
Grinner

4

ขณะนี้ยังไม่มีวิธีในการสร้างฐานข้อมูล SQLite เพื่อส่งพร้อมกับ apk ของคุณ วิธีที่ดีที่สุดที่คุณสามารถทำได้คือบันทึก SQL ที่เหมาะสมเป็นทรัพยากรและเรียกใช้จากแอปพลิเคชันของคุณ ใช่สิ่งนี้นำไปสู่การทำซ้ำข้อมูล (มีข้อมูลเดิมเหมือนกันในฐานะเป็นฐานข้อมูลและเป็นฐานข้อมูล) แต่ตอนนี้ไม่มีวิธีอื่น ปัจจัยบรรเทาเดียวคือไฟล์ apk ถูกบีบอัด ประสบการณ์ของฉันคือ 908KB บีบอัดให้เหลือน้อยกว่า 268KB

กระทู้ด้านล่างมีการอภิปราย / การแก้ปัญหาที่ดีที่สุดที่ฉันได้พบกับรหัสตัวอย่างที่ดี

http://groups.google.com/group/android-developers/msg/9f455ae93a1cf152

ฉันเก็บคำสั่ง CREATE ของฉันเป็นทรัพยากรสตริงที่จะอ่านด้วย Context.getString () และรันด้วย SQLiteDatabse.execSQL ()

ฉันเก็บข้อมูลสำหรับส่วนแทรกของฉันใน res / raw / inserts.sql (ฉันสร้างไฟล์ sql, 7000+ บรรทัด) โดยใช้เทคนิคจากลิงค์ด้านบนฉันป้อนวนอ่านบรรทัดไฟล์โดยบรรทัดและเชื่อมโยงข้อมูลลงใน "INSERT INTO tbl VALUE" และทำอีก SQLiteDatabase.execSQL () ไม่มีเหตุผลในการบันทึก 7000 "INSERT INTO tbl VALUE" เมื่อพวกเขาสามารถเชื่อมต่อได้

ใช้เวลาประมาณยี่สิบวินาทีในการจำลองผมไม่ทราบว่าจะใช้เวลานานแค่ไหนในโทรศัพท์จริง แต่จะเกิดขึ้นเพียงครั้งเดียวเมื่อผู้ใช้เริ่มแอปพลิเคชั่น


3
วิธีการดึงสคริปต์ SQL จากเว็บในการเรียกใช้ครั้งแรก? วิธีนี้ไม่จำเป็นต้องทำสำเนาข้อมูล
Tamas Czinege

1
ใช่ แต่อุปกรณ์จะต้องเชื่อมต่อกับอินเทอร์เน็ต นั่นเป็นข้อเสียเปรียบอย่างร้ายแรงในบางแอพ
Dzhuneyt

อย่าทำเม็ดมีดมากกว่า 7000+ ทำแบตช์แบบแทรก 100 หรือเช่นนี้ - INSERT INTO table VALUES(...) VALUES(...) VALUES(...) ...(1 สายแทรกควรมี 100 ค่า) มันจะมีประสิทธิภาพมากขึ้นและจะลดเวลาเริ่มต้นของคุณจาก 20 วินาทีเป็น 2 หรือ 3 วินาที
Mohit Atray

4

จัดส่งฐานข้อมูลภายใน apk แล้วคัดลอกไปยัง/data/data/...จะเพิ่มขนาดของฐานข้อมูล (1 ใน apk, 1 นิ้วdata/data/...) และจะเพิ่มขนาด apk (แน่นอน) ดังนั้นฐานข้อมูลของคุณไม่ควรใหญ่เกินไป


2
มันเพิ่มขนาด apk แต่ก็ไม่เพิ่มขึ้นเป็นสองเท่า เมื่ออยู่ในสินทรัพย์มันจะถูกบีบอัดและมีขนาดเล็กกว่ามาก หลังจากคัดลอกไปยังโฟลเดอร์ฐานข้อมูลจะไม่ถูกบีบอัด
Suragch

3

Android ได้จัดเตรียมวิธีการจัดการฐานข้อมูลเวอร์ชันที่รับรู้อยู่แล้ว วิธีการนี้ได้รับการยกระดับในกรอบ BARACUS สำหรับแอปพลิเคชัน Android

มันช่วยให้คุณสามารถจัดการฐานข้อมูลตลอดทั้งวงจรชีวิตของแอพพลิเคชั่นซึ่งสามารถอัพเดทฐานข้อมูล sqlite จากเวอร์ชันก่อนหน้าใด ๆ ไปเป็นเวอร์ชันปัจจุบันได้

นอกจากนี้ยังช่วยให้คุณสามารถรัน hot-backups และ hot-recovery ของ SQLite

ฉันไม่แน่ใจ 100% แต่การกู้คืนที่ร้อนแรงสำหรับอุปกรณ์เฉพาะอาจช่วยให้คุณสามารถจัดส่งฐานข้อมูลที่เตรียมไว้ในแอปของคุณ แต่ฉันไม่แน่ใจเกี่ยวกับรูปแบบไบนารีของฐานข้อมูลซึ่งอาจเฉพาะเจาะจงกับอุปกรณ์ผู้ขายหรือรุ่นอุปกรณ์บางอย่าง

เนื่องจากสิ่งนั้นคือ Apache License 2 คุณสามารถนำส่วนใด ๆ ของรหัสกลับมาใช้ใหม่ได้ซึ่งสามารถพบได้บน gitHub

แก้ไข:

หากคุณต้องการจัดส่งข้อมูลคุณอาจพิจารณาการสร้างอินสแตนซ์และการเก็บ POJO ที่แอปพลิเคชั่นเริ่มต้นก่อน BARACUS ได้รับการสนับสนุนในตัว (ที่เก็บค่าคีย์ในตัวสำหรับข้อมูลข่าวสารการกำหนดค่าเช่น "APP_FIRST_RUN" บวกเบ็ดหลังจาก after-context-bootstrap เพื่อเรียกใช้การดำเนินการหลังการเปิดตัวในบริบท) สิ่งนี้ทำให้คุณมีข้อมูลที่มาพร้อมกับแอพของคุณอย่างแน่นหนา ในกรณีส่วนใหญ่นี้เหมาะกับกรณีการใช้งานของฉัน


3

หากข้อมูลที่ต้องการไม่ใหญ่เกินไป (ข้อ จำกัด ที่ฉันไม่ทราบจะขึ้นอยู่กับหลาย ๆ อย่าง) คุณอาจดาวน์โหลดข้อมูล (ในรูปแบบ XML, JSON, อะไรก็ได้) จากเว็บไซต์ / เว็บแอป หลังจากได้รับเรียกใช้งานคำสั่ง SQL โดยใช้ข้อมูลที่ได้รับซึ่งสร้างตารางของคุณและแทรกข้อมูล

หากแอพมือถือของคุณมีข้อมูลจำนวนมากอาจจะง่ายขึ้นในภายหลังเพื่ออัปเดตข้อมูลในแอพที่ติดตั้งด้วยข้อมูลหรือการเปลี่ยนแปลงที่แม่นยำยิ่งขึ้น


3

ฉันแก้ไขคลาสและคำตอบของคำถามและเขียนคลาสที่อนุญาตให้อัพเดตฐานข้อมูลผ่าน DB_VERSION

public class DatabaseHelper extends SQLiteOpenHelper {
    private static String DB_NAME = "info.db";
    private static String DB_PATH = "";
    private static final int DB_VERSION = 1;

    private SQLiteDatabase mDataBase;
    private final Context mContext;
    private boolean mNeedUpdate = false;

    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
        if (android.os.Build.VERSION.SDK_INT >= 17)
            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
        else
            DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
        this.mContext = context;

        copyDataBase();

        this.getReadableDatabase();
    }

    public void updateDataBase() throws IOException {
        if (mNeedUpdate) {
            File dbFile = new File(DB_PATH + DB_NAME);
            if (dbFile.exists())
                dbFile.delete();

            copyDataBase();

            mNeedUpdate = false;
        }
    }

    private boolean checkDataBase() {
        File dbFile = new File(DB_PATH + DB_NAME);
        return dbFile.exists();
    }

    private void copyDataBase() {
        if (!checkDataBase()) {
            this.getReadableDatabase();
            this.close();
            try {
                copyDBFile();
            } catch (IOException mIOException) {
                throw new Error("ErrorCopyingDataBase");
            }
        }
    }

    private void copyDBFile() throws IOException {
        InputStream mInput = mContext.getAssets().open(DB_NAME);
        //InputStream mInput = mContext.getResources().openRawResource(R.raw.info);
        OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);
        byte[] mBuffer = new byte[1024];
        int mLength;
        while ((mLength = mInput.read(mBuffer)) > 0)
            mOutput.write(mBuffer, 0, mLength);
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    public boolean openDataBase() throws SQLException {
        mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);
        return mDataBase != null;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion > oldVersion)
            mNeedUpdate = true;
    }
}

ใช้คลาส

ในคลาสกิจกรรมให้ประกาศตัวแปร

private DatabaseHelper mDBHelper;
private SQLiteDatabase mDb;

ในวิธีการ onCreate เขียนรหัสต่อไปนี้

mDBHelper = new DatabaseHelper(this);

try {
    mDBHelper.updateDataBase();
} catch (IOException mIOException) {
    throw new Error("UnableToUpdateDatabase");
}

try {
    mDb = mDBHelper.getWritableDatabase();
} catch (SQLException mSQLException) {
    throw mSQLException;
}

หากคุณเพิ่มไฟล์ฐานข้อมูลลงในโฟลเดอร์ res / raw ให้ใช้การปรับเปลี่ยนคลาสต่อไปนี้

public class DatabaseHelper extends SQLiteOpenHelper {
    private static String DB_NAME = "info.db";
    private static String DB_PATH = "";
    private static final int DB_VERSION = 1;

    private SQLiteDatabase mDataBase;
    private final Context mContext;
    private boolean mNeedUpdate = false;

    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
        if (android.os.Build.VERSION.SDK_INT >= 17)
            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
        else
            DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
        this.mContext = context;

        copyDataBase();

        this.getReadableDatabase();
    }

    public void updateDataBase() throws IOException {
        if (mNeedUpdate) {
            File dbFile = new File(DB_PATH + DB_NAME);
            if (dbFile.exists())
                dbFile.delete();

            copyDataBase();

            mNeedUpdate = false;
        }
    }

    private boolean checkDataBase() {
        File dbFile = new File(DB_PATH + DB_NAME);
        return dbFile.exists();
    }

    private void copyDataBase() {
        if (!checkDataBase()) {
            this.getReadableDatabase();
            this.close();
            try {
                copyDBFile();
            } catch (IOException mIOException) {
                throw new Error("ErrorCopyingDataBase");
            }
        }
    }

    private void copyDBFile() throws IOException {
        //InputStream mInput = mContext.getAssets().open(DB_NAME);
        InputStream mInput = mContext.getResources().openRawResource(R.raw.info);
        OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);
        byte[] mBuffer = new byte[1024];
        int mLength;
        while ((mLength = mInput.read(mBuffer)) > 0)
            mOutput.write(mBuffer, 0, mLength);
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    public boolean openDataBase() throws SQLException {
        mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);
        return mDataBase != null;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion > oldVersion)
            mNeedUpdate = true;
    }
}

http://blog.harrix.org/article/6784


2

ฉันเขียนไลบรารีเพื่อทำให้กระบวนการนี้ง่ายขึ้น

dataBase = new DataBase.Builder(context, "myDb").
//        setAssetsPath(). // default "databases"
//        setDatabaseErrorHandler().
//        setCursorFactory().
//        setUpgradeCallback()
//        setVersion(). // default 1
build();

มันจะสร้างฐานข้อมูลจากassets/databases/myDb.dbไฟล์ นอกจากนี้คุณจะได้รับฟังก์ชั่นเหล่านั้นทั้งหมด:

  • โหลดฐานข้อมูลจากไฟล์
  • ประสานการเข้าถึงฐานข้อมูล
  • การใช้sqlite-androidโดยการสอบถามใหม่การกระจายเฉพาะของ Android ของ SQLite รุ่นล่าสุด

โคลนมันจากGitHub GitHub


2

ฉันใช้ ORMLite และโค้ดด้านล่างนี้ใช้งานได้สำหรับฉัน

public class DatabaseProvider extends OrmLiteSqliteOpenHelper {
    private static final String DatabaseName = "DatabaseName";
    private static final int DatabaseVersion = 1;
    private final Context ProvidedContext;

    public DatabaseProvider(Context context) {
        super(context, DatabaseName, null, DatabaseVersion);
        this.ProvidedContext= context;
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        boolean databaseCopied = preferences.getBoolean("DatabaseCopied", false);
        if (databaseCopied) {
            //Do Nothing
        } else {
            CopyDatabase();
            SharedPreferences.Editor editor = preferences.edit();
            editor.putBoolean("DatabaseCopied", true);
            editor.commit();
        }
    }

    private String DatabasePath() {
        return "/data/data/" + ProvidedContext.getPackageName() + "/databases/";
    }

    private void CopyDatabase() {
        try {
            CopyDatabaseInternal();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private File ExtractAssetsZip(String zipFileName) {
        InputStream inputStream;
        ZipInputStream zipInputStream;
        File tempFolder;
        do {
            tempFolder = null;
            tempFolder = new File(ProvidedContext.getCacheDir() + "/extracted-" + System.currentTimeMillis() + "/");
        } while (tempFolder.exists());

        tempFolder.mkdirs();

        try {
            String filename;
            inputStream = ProvidedContext.getAssets().open(zipFileName);
            zipInputStream = new ZipInputStream(new BufferedInputStream(inputStream));
            ZipEntry zipEntry;
            byte[] buffer = new byte[1024];
            int count;

            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                filename = zipEntry.getName();
                if (zipEntry.isDirectory()) {
                    File fmd = new File(tempFolder.getAbsolutePath() + "/" + filename);
                    fmd.mkdirs();
                    continue;
                }

                FileOutputStream fileOutputStream = new FileOutputStream(tempFolder.getAbsolutePath() + "/" + filename);
                while ((count = zipInputStream.read(buffer)) != -1) {
                    fileOutputStream.write(buffer, 0, count);
                }

                fileOutputStream.close();
                zipInputStream.closeEntry();
            }

            zipInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return tempFolder;
    }

    private void CopyDatabaseInternal() throws IOException {

        File extractedPath = ExtractAssetsZip(DatabaseName + ".zip");
        String databaseFile = "";
        for (File innerFile : extractedPath.listFiles()) {
            databaseFile = innerFile.getAbsolutePath();
            break;
        }
        if (databaseFile == null || databaseFile.length() ==0 )
            throw new RuntimeException("databaseFile is empty");

        InputStream inputStream = new FileInputStream(databaseFile);

        String outFileName = DatabasePath() + DatabaseName;

        File destinationPath = new File(DatabasePath());
        if (!destinationPath.exists())
            destinationPath.mkdirs();

        File destinationFile = new File(outFileName);
        if (!destinationFile.exists())
            destinationFile.createNewFile();

        OutputStream myOutput = new FileOutputStream(outFileName);

        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            myOutput.write(buffer, 0, length);
        }

        myOutput.flush();
        myOutput.close();
        inputStream.close();
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int fromVersion, int toVersion) {

    }
}

โปรดทราบว่ารหัสจะแยกไฟล์ฐานข้อมูลจากไฟล์ซิปในเนื้อหา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.