首页 > 解决方案 > 安装 APK 文件时如何包含多个数据库?

问题描述

我必须为我的天文学课制作一个项目,所以我继续做了一个基本的琐事游戏。我将所有问题、答案和解释存储在多个 SQLite 数据库中(每个星球一个)。当我构建 APK 并将其安装在我的 android 模拟器上时,所有的问题、答案等。不在那里。我认为这是因为数据库不包含在 APK 文件本身中。我的问题是,安装 APK 时如何包含数据库?

我尝试在谷歌上搜索它,并且能够找到一些说将它们放在资产文件夹中的东西,但我不知道它们是什么意思。如果这就是我所要做的,那么资产文件夹在哪里?

标签: databasesqlitedownloadapk

解决方案


您所做的是将通常使用 SQlite 管理工具(例如 DB Browser for SQLite、Navicat 等)开发的预先存在的数据库复制到 assets 文件夹(如果使用SQLiteAssetHelper,则必须是 assets/databases 文件夹(请参阅自述文件))。

  • 请注意,您可能需要创建文件夹
  • 请注意,最好将文件复制到 Android Studio 之外,因为 Android Studio 可能会尝试打开该文件,这可能会导致混淆。
  • 请注意确保数据库已保存(我建议关闭该工具并重新打开它进行检查)。

数据库需要从 assets 文件夹(assets/database 文件夹)复制到合适的位置(SQLiteAssetHelper 是一种方法),但前提是数据库不存在。尽管经常推荐使用 SQLiteAssetHelper,但不幸的是它似乎不适用于 Android 9+ (Pie)。因此,您可能希望在此答案中查看从上面的代码中提取的整个 DBHelper

  • 这个答案是使用 SQLiteAssetHelper 的分步指南[从资产文件夹中读取数据库,从资产文件夹中读取数据库)。如果不使用 SQLiteAssetHelper,可以遵循步骤 1-3(创建/复制到数据库文件夹除外)

  • 您必须相应地更改 DBNAME,这需要数据库文件位于 assets 文件夹而不是 assets/databases 中。如果文件有扩展名,则 DBNAME 将是带有扩展名的文件名。

  • 该代码相对较长,因为它包含用于调试目的的日志记录。您可能希望在部署应用程序之前删除调试。

这些资产将成为 APK 的一部分(允许大小限制)。

如果您使用 SQLiteAssetHelper,则每个数据库都需要一个。根据链接的数据库助手可以多次使用,适用于多个数据库。

例子

以下示例复制 3 个预先存在的数据库,即 mydb001、mydb002 和 mydb003。前 2 个在 assets 文件夹中,第 3 个放在 assets/databases 文件夹中(用于演示)。

如果需要复制数据库,它们将被复制。如果它们以前被复制过,它们不会被重新复制。

有两个类用到了 OpenAssetDBHelper.java 和 MainActivity.java。前者是完成所有工作的类。请注意,它具有相当广泛的日志记录,因此相当冗长。值得注意的是,它最多可以处理 10 个数据库助手,但这可以通过更改public static final int MAXIMUM_HELPERS = 10;.

这已在模拟的 Android Pie 设备上进行了检查。

OpenAssetDBHelper.java

:-

public class OpenAssetDBHelper extends SQLiteOpenHelper {

    private static final String LOGTAG = "OADB-HLPR";
    public static final int MAXIMUM_HELPERS = 10;
    private String mDBPath, mAssetPath;
    private static OpenAssetDBHelper mInstance[] = new OpenAssetDBHelper[MAXIMUM_HELPERS];
    private SQLiteDatabase mDB;

    /**
     * OpenAssetDBHelper Class that will copy a predefined database
     * from the assets folder and then open it is the database;
     *
     * The copy will only be done if the database does not exist.
     *
     * Note this code is intended to be used for development and/or
     * experimentation, hence the extensive logging.
     */

    /**
     * get an OpenAssetDBHelper instance as a singleton;
     * Note! caters for up to 10 OpenAssetDBHelpers for up to 10 databases
     * as specified by the helper_index
     *
     * @param helper_index  Index to this instance/database
     *                      (0-MAXIMUM_HELPERS less 1)
     * @param context       Context for the database
     * @param database      Database name (i.e. file name)
     * @param dbversion     Database version (user_version)
     * @param assetfile     Name of the asset file to be copied to the database
     * @param asset_sub_directories String Array of the sub-directories where
     *                              the asset file is located;
     *                              MUST be in order
     * @return              The resultant OpenAssetDBHelper
     */
    public static synchronized OpenAssetDBHelper getInstance(
            int helper_index,
            Context context,
            String database,
            int dbversion,
            String assetfile,
            String[] asset_sub_directories) {
        // Checck that helper_index is within bounds
        if (helper_index > (MAXIMUM_HELPERS -1)) {
            throw new RuntimeException(
                    "Helper Index greater than " +
                            MAXIMUM_HELPERS
            );
        }
        if (helper_index < 0) {
            throw new RuntimeException(
                    "Helper Index cannot be negative, must be 0-" +
                            MAXIMUM_HELPERS
            );
        }
        // Create the respective OpenAssetDBHelper instance
        if(mInstance[helper_index] == null) {
            mInstance[helper_index] = new OpenAssetDBHelper(context,
                    database,
                    dbversion,
                    assetfile,
                    asset_sub_directories);
        }
        return mInstance[helper_index];
    }

    /**
     * Construct an OpenAssetDBHelper instance;
     * Note! can only be called within class
     *
     * @param context       the context to be used
     * @param database      the database name (equates to filename)
     * @param dbversion     the databaae version (user_version)
     * @param assetfile     The name of the asset file i.e. the pre-defined db
     * @param directories   The hierarchy of directories within the assets folder
     *                      where the asset file is located
     *                      (null or zero elements = in the assets folder)
     */
    private OpenAssetDBHelper(Context context,
                              String database,
                              int dbversion,
                              String assetfile,
                              String[] directories) {
        super(context, database, null, dbversion);
        Log.d(LOGTAG,"OpenAssetDBHelper being constructed.");

        mDBPath = context.getDatabasePath(database).getPath();
        if (assetfile == null || assetfile.length() < 1) {
            assetfile = database;
        }
        mAssetPath = buildAssetPath(directories,assetfile);

        if (!ifDatabaseExists(mDBPath)) {
            Log.d(LOGTAG,"Database " + database + " not found at " + mDBPath + " so attempting to copy from assets.");
            if (copyDatabaseFromAssets(context,mDBPath, database, mAssetPath)) {
                Log.d(LOGTAG, "Successfully Copied Database from Assets.");
            } else {
                throw new RuntimeException("No Database Available.");
            }
        }
        // Force Database open and store it
        this.mDB = this.getWritableDatabase();
        //logDatabaseTableInformation(mDB);
        Log.d(LOGTAG,"OpenAssetDBHelper constructed.");
    }


    /**
     * onCreate - This is where you would create tables;
     * Typically this is where you would alter the structure of the database;
     * Note that this is called once for the lifetime of the database.
     * @param db The SQLitedatabase
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " initiated.");
        // As Database is copied from assets nothing to do in onCreate!
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " completed.");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " initiated.");
        // Nothing to do as it's early days in the Database's lifetime.
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " completed.");
    }

    /**
     * Check to see if the Database exists,
     * if it doesn't exists then check to see if
     * the database directory exists,
     * if the directory(ies) does(do) not exist then make the directory(ies);
     *
     *
     * @param dbpath        The path to the database
     * @return              true if the database exists, else false
     */
    private boolean ifDatabaseExists(String dbpath) {
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " initiated.");
        File db = new File(dbpath);
        if(db.exists()) return true;
        File dir = new File(db.getParent());
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return false;
    }

    /**
     * Copy the Database from the assets folder
     * @param context
     * @param dbpath
     * @return
     */
    private boolean copyDatabaseFromAssets(Context context,String dbpath, String dbname, String asset) {
        String thisclass = new Object(){}.getClass().getEnclosingMethod().getName();
        Log.d(LOGTAG,thisclass + " initiated");
        InputStream assetsdb;
        OutputStream database;
        File db = new File(dbpath);
        int filesize;
        // Get the asset file
        try {
            Log.d(LOGTAG,thisclass + " attempting to find asset " + asset);
            assetsdb = context.getAssets().open(asset);
            filesize = assetsdb.available();
            Log.d(LOGTAG,thisclass + " asset " + asset +
                    " located successfully with a size of " +
                    Integer.toString(filesize)
            );
        } catch (IOException e) {
            Log.d(LOGTAG,thisclass + " Did not locate asset " + asset);
            e.printStackTrace();
            return false;
        }

        // Read the first 16 bytes from the asset file
        byte[] dbcheck = new byte[16];
        try {
            assetsdb.read(dbcheck,0,16);
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass + " failed trying to read 16 bytes to check for a valid database. ");
            e.printStackTrace();
            return false;
        }

        // Check that the asset file is an SQLite database
        String chkdb = new String(dbcheck);
        if(!chkdb.equals("SQLite format 3\u0000")) {
            Log.d(LOGTAG,thisclass + " asset " +
                    asset +
                    " is not a valid SQLite Database File (found " +
                    chkdb +
                    " at bytes 1-16 instead of SQLite format 3)");
            try {
                assetsdb.close();
            } catch (IOException e) {
                // Not worth doing anything
            }
            return false;
        }
        // Close the asset file
        try {
            assetsdb.close();
        } catch (IOException e) {
            Log.d(LOGTAG,thisclass +
                    " failed to close assets file after checking for a valid database."
            );
            return false;
        }
        // Re-open the asset file
        try {
            assetsdb = context.getAssets().open(asset);
            filesize = assetsdb.available();
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass +
                    " failed trying to re-open asset " +
                    asset +
                    " after checking for a valid database."
            );
            e.printStackTrace();
            return false;
        }

        // Read the entire asset file into a buffer
        Log.d(LOGTAG, thisclass +
                " copying asset database " +
                dbname +
                " into buffer of size " +
                filesize
        );
        byte[] buffer = new byte[filesize];
        // Close the asset file
        try {
            assetsdb.read(buffer);
            Log.d(LOGTAG,thisclass +
                    " closing asset database " + dbname
            );
            assetsdb.close();
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass +
                    " failed while copying asset database " +
                    dbname +
                    " (or closing asset database)."
            );
            e.printStackTrace();
            return false;
        }
        // Open the new database file
        try {
            Log.d(LOGTAG,thisclass + " attempting to open new database file " + dbpath);
            database = new FileOutputStream(dbpath);
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass + " failed to open new database file.");
            e.printStackTrace();
            return false;
        }
        // Write the new database file
        try {
            Log.d(LOGTAG, thisclass + " writing new database file " + dbpath);
            database.write(buffer);
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass + " failed while writing new database file " + dbpath);
            e.printStackTrace();
            return false;
        }
        // Flush the new database file
        try {
            Log.d(LOGTAG, thisclass + " flushing new database file " + dbpath);
            database.flush();
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass + " failed while flushing new database file " + dbpath);
            e.printStackTrace();
            return false;
        }
        // Close the new database file
        try {
            Log.d(LOGTAG, thisclass + " closing new database file " + dbpath);
            database.close();
        } catch (IOException e) {
            Log.d(LOGTAG, thisclass + " failed while closing new database file " + dbpath);
            e.printStackTrace();
            return  false;
        }
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " completed.");
        return true;
    }

    /**
     * Log Database table Information
     */
    private void logDatabaseTableInformation(SQLiteDatabase db) {
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " initiated.");
        String mastertable = "sqlite_master";
        String typecolumn = "type";
        String namecolumn = "name";
        String sqlcolumn = "sql";
        String[] args = new String[]{"table","android_metadata"};
        Cursor csr = db.query(mastertable,
                null,
                typecolumn + "=? AND " + namecolumn + "!=?",
                args,
                null,null,null
        );
        while (csr.moveToNext()) {
            Log.d(LOGTAG,"Database contains Table " +
                    csr.getString(csr.getColumnIndex(namecolumn)) +
                    " created by SQL " +
                    csr.getString(csr.getColumnIndex(sqlcolumn))
            );
            logTableInformation(db, csr.getString(csr.getColumnIndex(namecolumn)));
        }
        csr.close();
        Log.d(LOGTAG,new Object(){}.getClass().getEnclosingMethod().getName() + " completed.");
    }

    /**
     * Write Table information, Table name, Column Count,
     * Row Count and Column Names to the Log
     * @param table Name of the table to be reported on
     */
    private void logTableInformation(SQLiteDatabase db, String table) {
        Cursor csr = db.query(table,
                null,
                null,
                null,
                null,
                null,
                null
        );
        Log.d(LOGTAG,"Table is " +
                table +
                " Column Count = " +
                Integer.toString(csr.getColumnCount()) +
                " Row Count = " +
                Long.toString(DatabaseUtils.queryNumEntries(mDB,table))
        );
        StringBuilder columns_as_string = new StringBuilder();
        for (String s: csr.getColumnNames()) {
            columns_as_string.append(s).append(" ");
        }
        Log.d(LOGTAG, "\tColumns are :- " + columns_as_string);
        csr.close();
    }

    /**
     * Build the sub-path to the asset, according to the directories specified
     *
     * @param directories   directories underneath the assets folder where
     *                      the asset files is located, null or empty
     *                      array if file is located directly in the
     *                      assets folder;
     *                      directories must be specified in the order
     *                      in which they appear in the path.
     * @param filename      The filename of the asset
     * @return              The fill sub-path to the asset
     */
    private String buildAssetPath(String[] directories, String filename) {
        StringBuilder sb = new StringBuilder();
        final String SEPERATOR = "/";
        if (directories != null && directories.length > 0) {
            for (String s: directories) {
                sb.append(s);
                if (!s.substring(s.length()-1,s.length()).equals(SEPERATOR)) {
                    sb.append(SEPERATOR);
                }
            }
            sb.append(filename);
            return sb.toString();
        } else {
            return filename;
        }
    }

    public static OpenAssetDBHelper getHelperInstance(int instance) {
        if (instance < mInstance.length && instance >= 0) {
            return mInstance[instance];
        }
        else return mInstance[0];
    }
}
  • 代码可以大大减少,它很长,因为它旨在帮助开发和理解。

MainActivity.java

:-

公共类 MainActivity 扩展 AppCompatActivity {

public static final int DBVERSION = 1;

public static final String DBNAME1 = "mydb001"; // in assets
public static final int DB1_INDEX = 0;
public static final String DBNAME2 = "mydb002"; // in assets
public static final int DB2_INDEX = 1;
public static final String DBNAME3 = "mydb003"; //in assets/databases
public static final int DB3_INDEX = 2;

OpenAssetDBHelper[] mDBHlpr = new OpenAssetDBHelper[3];

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Example 1 - Database file located in the assets folder and
    //              database name same as file name
    mDBHlpr[0] = OpenAssetDBHelper.getInstance(DB1_INDEX,
            this,
            DBNAME1,
            DBVERSION,
            DBNAME1,
            null //<<<<<<<<<< null = db file (pre-existing db) located in the assets folder
    );
    // mydb002
    mDBHlpr[1] = OpenAssetDBHelper.getInstance(DB2_INDEX,
            this,
            DBNAME2,
            DBVERSION,
            "mydb002",
            null
    );


    // mydb003 in the assets/database folder
    mDBHlpr[2] = OpenAssetDBHelper.getInstance(DB3_INDEX,
            this,
            DBNAME3,
            DBVERSION,
            "mydb003",
            new String[]{"databases"} //<<<<<<<<<<< array of sub-folders under assets (order matters)
    );

    // Do something with each Database
    for (OpenAssetDBHelper o:mDBHlpr) {
        logDBSchemaInfo(o.getWritableDatabase());
    }
}

//Dump/Log SQLitemaster
private void logDBSchemaInfo(SQLiteDatabase db) {
    DatabaseUtils.dumpCursor(
            db.query(
                    "sqlite_master",null,null,null,null,null,null
            )
    );
}

}

结果

第一次运行的日志(在这种情况下)包括:-

2019-04-29 15:41:56.329 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 15:41:56.330 D/OADB-HLPR: Database mydb001 not found at /data/user/0/mjt.openmultipleassetdataabses/databases/mydb001 so attempting to copy from assets.
2019-04-29 15:41:56.331 D/OADB-HLPR: copyDatabaseFromAssets initiated
2019-04-29 15:41:56.331 D/OADB-HLPR: copyDatabaseFromAssets attempting to find asset mydb001
2019-04-29 15:41:56.331 D/OADB-HLPR: copyDatabaseFromAssets asset mydb001 located successfully with a size of 77824
2019-04-29 15:41:56.331 D/OADB-HLPR: copyDatabaseFromAssets copying asset database mydb001 into buffer of size 77824
2019-04-29 15:41:56.332 D/OADB-HLPR: copyDatabaseFromAssets closing asset database mydb001
2019-04-29 15:41:56.332 D/OADB-HLPR: copyDatabaseFromAssets attempting to open new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb001
2019-04-29 15:41:56.332 D/OADB-HLPR: copyDatabaseFromAssets writing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb001
2019-04-29 15:41:56.332 D/OADB-HLPR: copyDatabaseFromAssets flushing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb001
2019-04-29 15:41:56.332 D/OADB-HLPR: copyDatabaseFromAssets closing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb001
2019-04-29 15:41:56.333 D/OADB-HLPR: copyDatabaseFromAssets completed.
2019-04-29 15:41:56.333 D/OADB-HLPR: Successfully Copied Database from Assets.
2019-04-29 15:41:56.353 D/OADB-HLPR: OpenAssetDBHelper constructed.



2019-04-29 15:41:56.353 D/OADB-HLPR: OpenAssetDBHelper being constructed.
2019-04-29 15:41:56.354 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 15:41:56.354 D/OADB-HLPR: Database mydb002 not found at /data/user/0/mjt.openmultipleassetdataabses/databases/mydb002 so attempting to copy from assets.
2019-04-29 15:41:56.354 D/OADB-HLPR: copyDatabaseFromAssets initiated
2019-04-29 15:41:56.354 D/OADB-HLPR: copyDatabaseFromAssets attempting to find asset mydb002
2019-04-29 15:41:56.354 D/OADB-HLPR: copyDatabaseFromAssets asset mydb002 located successfully with a size of 77824
2019-04-29 15:41:56.354 D/OADB-HLPR: copyDatabaseFromAssets copying asset database mydb002 into buffer of size 77824
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets closing asset database mydb002
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets attempting to open new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb002
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets writing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb002
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets flushing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb002
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets closing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb002
2019-04-29 15:41:56.355 D/OADB-HLPR: copyDatabaseFromAssets completed.
2019-04-29 15:41:56.356 D/OADB-HLPR: Successfully Copied Database from Assets.
2019-04-29 15:41:56.375 D/OADB-HLPR: OpenAssetDBHelper constructed.



2019-04-29 15:41:56.375 D/OADB-HLPR: OpenAssetDBHelper being constructed.
2019-04-29 15:41:56.375 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 15:41:56.375 D/OADB-HLPR: Database mydb003 not found at /data/user/0/mjt.openmultipleassetdataabses/databases/mydb003 so attempting to copy from assets.
2019-04-29 15:41:56.375 D/OADB-HLPR: copyDatabaseFromAssets initiated
2019-04-29 15:41:56.375 D/OADB-HLPR: copyDatabaseFromAssets attempting to find asset databases/mydb003
2019-04-29 15:41:56.376 D/OADB-HLPR: copyDatabaseFromAssets asset databases/mydb003 located successfully with a size of 77824
2019-04-29 15:41:56.376 D/OADB-HLPR: copyDatabaseFromAssets copying asset database mydb003 into buffer of size 77824
2019-04-29 15:41:56.376 D/OADB-HLPR: copyDatabaseFromAssets closing asset database mydb003
2019-04-29 15:41:56.376 D/OADB-HLPR: copyDatabaseFromAssets attempting to open new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb003
2019-04-29 15:41:56.377 D/OADB-HLPR: copyDatabaseFromAssets writing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb003
2019-04-29 15:41:56.377 D/OADB-HLPR: copyDatabaseFromAssets flushing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb003
2019-04-29 15:41:56.377 D/OADB-HLPR: copyDatabaseFromAssets closing new database file /data/user/0/mjt.openmultipleassetdataabses/databases/mydb003
2019-04-29 15:41:56.377 D/OADB-HLPR: copyDatabaseFromAssets completed.
2019-04-29 15:41:56.377 D/OADB-HLPR: Successfully Copied Database from Assets.
2019-04-29 15:41:56.394 D/OADB-HLPR: OpenAssetDBHelper constructed.

后续运行

2019-04-29 16:05:29.905 D/OADB-HLPR: OpenAssetDBHelper being constructed.
2019-04-29 16:05:29.905 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 16:05:29.910 D/OADB-HLPR: OpenAssetDBHelper constructed.
2019-04-29 16:05:29.910 D/OADB-HLPR: OpenAssetDBHelper being constructed.
2019-04-29 16:05:29.910 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 16:05:29.914 D/OADB-HLPR: OpenAssetDBHelper constructed.
2019-04-29 16:05:29.914 D/OADB-HLPR: OpenAssetDBHelper being constructed.
2019-04-29 16:05:29.915 D/OADB-HLPR: ifDatabaseExists initiated.
2019-04-29 16:05:29.918 D/OADB-HLPR: OpenAssetDBHelper constructed.

推荐阅读