为了账号安全,请及时绑定邮箱和手机立即绑定

使用现有数据库在 Android 应用中显示数据

使用现有数据库在 Android 应用中显示数据

慕尼黑的夜晚无繁华 2022-06-04 16:19:07
我是 android 开发的新手。我已经创建了 SQLite 数据库并将其保存在 Android Studio 的资产文件夹中。我的应用程序必须使用现有数据库而不是创建新数据库。我面临的问题是,当我想在屏幕上显示数据时,它会在执行 SQL 语句的光标处引发错误。请帮忙。数据库名是test.db,表名是MASTER。这是我的 DataBaseHelper 类public class DataBaseHelper extends SQLiteOpenHelper {    private static String TAG = "DataBaseHelper"; // Tag just for the LogCat window    //destination path (location) of our database on device    private static String DB_PATH = "";    private static String DB_NAME ="test,db";// Database name    private SQLiteDatabase mDataBase;    private final Context mContext;    public DataBaseHelper(Context context)    {        super(context, DB_NAME, null, 1);// 1? Its database Version        if(android.os.Build.VERSION.SDK_INT >= 17){            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";        }        else        {            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";        }        this.mContext = context;    }    public void createDataBase() throws IOException    {        //If the database does not exist, copy it from the assets.        boolean mDataBaseExist = checkDataBase();        if(!mDataBaseExist)        {            this.getReadableDatabase();            this.close();            try            {                //Copy the database from assests                copyDataBase();                Log.e(TAG, "createDatabase database created");            }            catch (IOException mIOException)            {                throw new Error("ErrorCopyingDataBase");            }        }    }    //Check that the database exists here: /data/data/your package/databases/Da Name    private boolean checkDataBase()    {        File dbFile = new File(DB_PATH + DB_NAME);        //Log.v("dbFile", dbFile + "   "+ dbFile.exists());        return dbFile.exists();    }
查看完整描述

2 回答

?
料青山看我应如是

TA贡献1772条经验 获得超8个赞

我相信您的主要问题是您使用过private static String DB_NAME ="test,db";// Database name

代替private static String DB_NAME ="test.db";// Database name

也就是说,您编写了逗号,而不是句点.,因此将找不到资产文件夹中的数据库文件,因此不会被复制。

第一次运行文件test,db时,由于使用this.getReadableDatabase();,将创建数据库,这将导致创建数据库,该数据库将为空,因此对于后续运行将不会尝试从资产文件夹作为数据库存在,因此由于数据库为空,因此访问该表的尝试失败,因为该表不存在。

  • 注意 getRedableDatabase 在大多数情况下实际上会得到一个可写的数据库

    创建和/或打开数据库。这将与 getWritableDatabase() 返回的对象相同,除非某些问题(例如磁盘已满)需要以只读方式打开数据库。在这种情况下,将返回一个只读数据库对象。如果问题得到解决,未来对 getWritableDatabase() 的调用可能会成功,在这种情况下,只读数据库对象将被关闭,并且将来会返回读/写对象。 获取可读数据库

我相信 usinggetReadableDatabase仅用于规避最初数据库文件夹不存在的问题,因此尝试从资产复制文件失败,因为父文件夹不存在。更好的解决方案是不使用getReadableDatabase,而是检查目录是否存在,如果不存在则创建它。

当使用 Android 9+ 作为默认值时,这种使用getReabableDatabase引入了更大的问题,然后使用 WAL(预写日志记录),这会产生额外的文件(以 -shm 和 -wal 为后缀的数据库名称)。

因此使用:-


//Check that the database exists here: /data/data/your package/databases/Da Name

private boolean checkDataBase()

{

    File dbFile = new File(DB_PATH + DB_NAME);

    if (dbFile.exists()) return true;

    if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();

    return false;

}

getReabableDatabase即使由于资产文件不存在而导致随后的副本失败,也无需使用以及实际创建数据库文件所产生的复杂性。


为了更加小心并应对 -shm 和 -wal 文件可能无意中存在的可能性,那么上述内容甚至可以扩展到:-


private boolean checkDataBase()

{

    File dbFile = new File(DB_PATH + DB_NAME);

    if (dbFile.exists()) return true;

    if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();

    if (new File(DB_PATH + DB_NAME + "-shm").exists())

        new File(DB_PATH + DB_NAME + "-shm").delete();

    if ((new File(DB_PATH + DB_NAME + "-wal")).exists())

        new File(DB_PATH + DB_NAME + "-wal").delete();

    return false;

}

通常DB_PATH = context.getApplicationInfo().dataDir + "/databases/";不建议使用,而是建议使用更具体DB_PATH = mContext.getDatabasePath(DB_NAME).getPath();的,因为不需要硬编码文件分隔符和文件夹名称。


以下可能是更好的整体数据库助手:-


public class DataBaseHelper extends SQLiteOpenHelper {

    private static String TAG = "DataBaseHelper"; // Tag just for the LogCat window

    //destination path (location) of our database on device

    private static String DB_PATH = "";

    private static String DB_NAME ="test.db";// Database name //<<<<<<<<<< CHANGED TO FIX PRIMARY ISSUE

    private SQLiteDatabase mDataBase;

    private final Context mContext;


    public DataBaseHelper(Context context)

    {

        super(context, DB_NAME, null, 1);// 1? Its database Version

        this.mContext = context;

        DB_PATH = mContext.getDatabasePath(DB_NAME).getPath();

    }


    public void createDataBase() throws IOException

    {

        //If the database does not exist, copy it from the assets.


        boolean mDataBaseExist = checkDataBase();

        if(!mDataBaseExist)

        {

            //this.getReadableDatabase(); //<<<<<<<<<< REMOVED (commented out)

            //this.close(); //<<<<<<<<<< REMOVED ()commented out

            try

            {

                //Copy the database from assests

                copyDataBase();

                Log.e(TAG, "createDatabase database created");

            }

            catch (IOException mIOException)

            {

                mIOException.printStackTrace(); //<<<<<<<<<< might as well include the actual cause in the log

                throw new Error("ErrorCopyingDataBase");

            }

        }

    }


    //Check that the database exists here: /data/data/your package/databases/Da Name

    private boolean checkDataBase()

    {

        File dbFile = new File(DB_PATH); //<<<<<<<<<< just the path used

        if (dbFile.exists()) return true; //<<<<<<<<<< return true of the db exists (see NOTE001)

        if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();

        if (new File(DB_PATH + "-shm").exists())

            new File(DB_PATH + "-shm").delete();

        if ((new File(DB_PATH + "-wal")).exists())

            new File(DB_PATH + "-wal").delete();

        return false;

    }


    /** NOTE001

     *  Just checking the file does leave scope for a non sqlite file to be copied from the assets folder

     *  and be copied resulting in an exception. The above could be extended to apply additional checks

     *  if considered required e.g. checking the first sixteen bytes for The header string: "SQLite format 3\000"

     */


    //Copy the database from assets

    private void copyDataBase() throws IOException

    {

        InputStream mInput = mContext.getAssets().open(DB_NAME);

        String outFileName = DB_PATH; //<<<<<<<<<< just the path used

        OutputStream mOutput = new FileOutputStream(outFileName);

        byte[] mBuffer = new byte[1024];

        int mLength;

        while ((mLength = mInput.read(mBuffer))>0)

        {

            mOutput.write(mBuffer, 0, mLength);

        }

        mOutput.flush();

        mOutput.close();

        mInput.close();

    }


    //Open the database, so we can query it

    public boolean openDataBase() throws SQLException

    {

        String mPath = DB_PATH;

        //Log.v("mPath", mPath);

        mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.CREATE_IF_NECESSARY);

        //mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);

        return mDataBase != null;

    }


    /**

     * Note this can be added and the line uncommented (see below) to disable WAL logging which

     * from Anroid 9 (Pie) is the default

     */

    @Override

    public void onConfigure(SQLiteDatabase db) {

        super.onConfigure(db);

        // db.disableWriteAheadLogging(); //<<<<<<<<<< uncomment if you want to not use WAL but use the less efficient joutnal mode.

    }


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


    }

}

注意查看代码中的注释

额外/测试

如果使用上述(删除应用程序的数据或卸载应用程序以删除空数据库后)但资产文件夹中没有合适的文件(test,db未更改以进行测试),那么上述将导致更多解释: -


02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err: java.io.FileNotFoundException: test,db

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.openAsset(Native Method)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:313)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:287)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.DataBaseHelper.copyDataBase(DataBaseHelper.java:75)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.DataBaseHelper.createDataBase(DataBaseHelper.java:42)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.TestAdapter.createDatabase(TestAdapter.java:29)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.MainActivity$1.onClick(MainActivity.java:23)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.view.View.performClick(View.java:4780)

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.view.View$PerformClick.run(View.java:19866)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Handler.handleCallback(Handler.java:739)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Looper.loop(Looper.java:135)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5254)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)

02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

02-05 10:17:03.514 5502-5502/mjt.so54513838 D/AndroidRuntime: Shutting down VM

02-05 10:17:03.514 5502-5502/mjt.so54513838 E/AndroidRuntime: FATAL EXCEPTION: main

    Process: mjt.so54513838, PID: 5502

    java.lang.Error: ErrorCopyingDataBase

        at mjt.so54513838.DataBaseHelper.createDataBase(DataBaseHelper.java:48)

        at mjt.so54513838.TestAdapter.createDatabase(TestAdapter.java:29)

        at mjt.so54513838.MainActivity$1.onClick(MainActivity.java:23)

        at android.view.View.performClick(View.java:4780)

        at android.view.View$PerformClick.run(View.java:19866)

        at android.os.Handler.handleCallback(Handler.java:739)

        at android.os.Handler.dispatchMessage(Handler.java:95)

        at android.os.Looper.loop(Looper.java:135)

        at android.app.ActivityThread.main(ActivityThread.java:5254)

        at java.lang.reflect.Method.invoke(Native Method)

        at java.lang.reflect.Method.invoke(Method.java:372)

        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

如果应用程序在没有任何更改的情况下再次运行,那么上述情况也会发生,而不是更令人困惑的table not found。


注意在运行上述代码之前,或者即使只是将 test,db 更改为 test.db,也必须删除数据库文件。这可以通过删除/清除应用程序的数据或卸载应用程序来轻松实现。

以上内容已在 Android 5.0 (lollipop) (API 22) 和 Andorid 9 (Pie)(API 28) 上进行了测试,生成的 Toast 显示表(尽管为方便起见,表已从 MASTER 更改为 sqlite_master(保存使用现有数据库文件创建数据库文件))。


查看完整回答
反对 回复 2022-06-04
?
慕容3067478

TA贡献1773条经验 获得超3个赞

将您的数据路径直接设置为字符串,希望它会起作用


 private final static String DATABASE_PATH ="/data/data/com.yourpackagename/databases/";

public SQLiteDatabase openDatabase() throws SQLException

    {   String myPath = DATABASE_PATH + "DB_NAME";myDataBase = SQLiteDatabase.openOrCreateDatabase(myPath, null, null);

        return myDataBase;

    }`


查看完整回答
反对 回复 2022-06-04
  • 2 回答
  • 0 关注
  • 163 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信