Использование существующей базы данных для отображения данных в приложении Android - PullRequest
0 голосов
/ 04 февраля 2019

Привет, я новичок в разработке для 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();
    }

    //Copy the database from assets
    private void copyDataBase() throws IOException
    {
        InputStream mInput = mContext.getAssets().open(DB_NAME);
        String outFileName = DB_PATH + DB_NAME;
        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 + DB_NAME;
        //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;
    }

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

    }
}

Это мой класс TestAdapter

public class TestAdapter
    {
        protected static final String TAG = "DataAdapter";

        private final Context mContext;
        private SQLiteDatabase mDb;
        private DataBaseHelper mDbHelper;

        public TestAdapter(Context context)
        {
            this.mContext = context;
            mDbHelper = new DataBaseHelper(mContext);
        }

        public TestAdapter createDatabase() throws SQLException
        {
            try
            {
                mDbHelper.createDataBase();
            }
            catch (IOException mIOException)
            {
                Log.e(TAG, mIOException.toString() + "  UnableToCreateDatabase");
                throw new Error("UnableToCreateDatabase");
            }
            return this;
        }

        public TestAdapter open() throws SQLException
        {
            try
            {
                mDbHelper.openDataBase();
                mDbHelper.close();
                mDb = mDbHelper.getReadableDatabase();
            }
            catch (SQLException mSQLException)
            {
                Log.e(TAG, "open >>"+ mSQLException.toString());
                throw mSQLException;
            }
            return this;
        }

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

        public Cursor getTestData() {
            try
            {
                String sql ="SELECT * FROM MASTER;";

                Cursor mCur = mDb.rawQuery(sql, null);
                if (mCur!=null)
                {
                    mCur.moveToNext();
                }
                return mCur;
            }
            catch (SQLException mSQLException)
            {
                Log.e(TAG, "getTestData >>"+ mSQLException.toString());
                throw mSQLException;
            }
        }
    }

Это мой класс MainActivity

public class MainActivity extends AppCompatActivity {

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

        Button button=findViewById(R.id.submit);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                TestAdapter mDbHelper = new TestAdapter(MainActivity.this);
                mDbHelper.createDatabase();
                mDbHelper.open();
                Cursor testdata = mDbHelper.getTestData();
                Toast.makeText(MainActivity.this,testdata.getString(0),Toast.LENGTH_SHORT).show();
                mDbHelper.close();
            }
        });

    }
}

И это logcat

2019-02-04 15:47:30.227 2594-2594/com.example.myapplication E/SQLiteLog: (1) no such table: MASTER
2019-02-04 15:47:30.228 2594-2594/com.example.myapplication E/DataAdapter: getTestData >>android.database.sqlite.SQLiteException: no such table: MASTER (code 1): , while compiling: SELECT * FROM MASTER;
2019-02-04 15:47:30.228 2594-2594/com.example.myapplication D/AndroidRuntime: Shutting down VM
2019-02-04 15:47:30.242 2594-2594/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 2594
    android.database.sqlite.SQLiteException: no such table: MASTER (code 1): , while compiling: SELECT * FROM MASTER;
        at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:890)
        at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:501)
        at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
        at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
        at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)
        at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1392)
        at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1331)
        at com.example.myapplication.TestAdapter.getTestData(TestAdapter.java:63)
        at com.example.myapplication.MainActivity$1.onClick(MainActivity.java:38)
        at android.view.View.performClick(View.java:6297)
        at android.view.View$PerformClick.run(View.java:24797)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6626)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

Logcat говорит, что таблицы MASTER нет, но когда я просматривал базу данных через браузер SQLiteDB, она была там в то время.

Ответы [ 2 ]

0 голосов
/ 05 февраля 2019

Я считаю, что ваша главная проблема в том, что вы использовали 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 () может завершиться успешно, и в этом случае объект базы данных только для чтения будет закрыт, а объект чтения / записи будет возвращен в будущем. getReadableDatabase

Я считаю, что использование getReadableDatabase использовалось только для того, чтобы обойти проблему, из-за которой изначально папка database не делала 't существует, и поэтому попытка скопировать файл из ресурсов не удалась, поскольку родительская папка не существует.Лучшее решение состоит в том, чтобы не использовать getReadableDatabase, а проверить, существует ли каталог, и не создавать ли его.

При использовании getReabableDatabase возникают еще большие проблемы при использовании Android 9+ по умолчаниюзатем использовать 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)

Если приложение затем будет запущено снова, без каких-либо изменений, товышеописанное также может произойти, а не запутанная таблица не найдена .

ПРИМЕЧАНИЕ перед запуском вышеуказанного кода или даже при изменении только test, необходимо удалить файл базы данных из db в test.db,Этого легко достичь, удалив / очистив данные приложения или удалив приложение.

Выше было протестировано на Android 5.0 (леденец на палочке) (API 22) и Andorid 9 (пирог) (API 28)., с результирующей таблицей отображения Toast (хотя для удобства таблица была изменена с MASTER на sqlite_master (сохранена необходимость создания файла базы данных в качестве существующего файла базы данных)).

0 голосов
/ 04 февраля 2019

установить путь к данным непосредственно в виде строки, надеюсь, он будет работать

 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;
    }`
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...