【问题标题】:Unusual behaviour in app using existing database使用现有数据库的应用程序中的异常行为
【发布时间】:2021-11-01 04:24:14
【问题描述】:

我正在尝试使用 Assets 文件夹中提供给应用程序的现有数据库。

当我第一次运行应用程序时,我在 InputSteam 对象上收到 NullPointerException。

如果我第二次运行该应用程序,那么输入流不为空,但这次,我得到一个 SQLiteException: no such table。

非常不寻常,我想知道是否有人可以帮助找到原因。

  1. 在这个项目中,我创建了一个简单的 SQLite 数据库文件并将其存储在 Assets 文件夹中。它被称为Customers.db 并包含一个名为CustomerList 的表。表列是 ID(整数主键)、CustomerName 和 Country。

  2. 在 DatabaseHelper 对象中,loadDatabase() 方法将数据库从资产文件夹加载到手机的内存中。

  3. DatabaseHelper 方法getRecords() 返回一个客户对象数组,这些客户对象的字段列在 MainActivity 的 RecyclerView 中。为了简单的实验,第一步,getRecords()方法返回表的所有行。

第一次运行时报如下异常:

java.lang.RuntimeException:无法启动活动 ComponentInfo{com.mo.useexistingdatabasedemo/com.mo.useexistingdatabasedemo.MainActivity}:java.lang.NullPointerException:尝试调用虚拟方法'int java.io.InputStream.read (byte[], int, int)' 在空对象引用上

而第二次运行时,输入流似乎不再为空,而是报了一个sqliteexception:

java.lang.RuntimeException:无法启动活动 ComponentInfo{com.mo.useexistingdatabasedemo/com.mo.useexistingdatabasedemo.MainActivity}:android.database.sqlite.SQLiteException:没有这样的表:CustomerList(代码 1 SQLITE_ERROR):,编译时:SELECT * FROM CustomerList

在我的项目类DatabaseHelper中定义如下(它的方法是从MainActivity调用的)。

package com.mo.useexistingdatabasedemo;

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

import androidx.annotation.Nullable;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.util.ArrayList;

public class DatabaseHelper extends SQLiteOpenHelper {

    public static ArrayList<Customer> arrayList = new ArrayList<>();

    public static final String DATABASE_NAME = "Customers.db";
    public static final String TABLE_NAME = "CustomerList";
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_DIRECTORY_PATH = "data/data/com.mo" +
            ".useexistingdatabasedemo/databases";
    public static final String DATABASE_FILE_PATH = DATABASE_DIRECTORY_PATH + DATABASE_NAME;

    Context context;
    InputStream inputStream;
    OutputStream outputStream;
    Buffer buffer;


    public static final String SELECT_ALL_TABLE = "SELECT * FROM " + TABLE_NAME;

    public DatabaseHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, 2);
        this.context = context;

    }

    public void loadDatabase() {
            String path = DATABASE_DIRECTORY_PATH + DATABASE_NAME;

            File dbFile = new File(path); // the descriptor of a file in internal memory to which
            // we will write the db in the assets folder.
                // if the database isn't already in internal memory, copy it over from assets.
            if (!dbFile.exists()) {
                try {
                    inputStream = context.getAssets().open(DATABASE_NAME);
                } catch (IOException e) {
                    Log.e("IOException input stream: ",
                            "Exception opening input stream " + e.getMessage());
                }
                OutputStream outputStream = null;
                try {
                    outputStream = new FileOutputStream(path);
                } catch (FileNotFoundException e) {
                    Log.e("IOException, output stream: ",
                            "Exception creating output stream " + e.getMessage());
                }
                // create a buffer of 1024 bytes length
                byte[] buffer = new byte[1024];
                int len;

                try {
                    while ((len = inputStream.read(buffer, 0, 1024)) > 0)
                        outputStream.write(buffer, 0, len);
                    outputStream.flush();
                    inputStream.close();
                } catch (IOException e) {
                    Log.e("IOException", "Exception occurred in while block" + e.getMessage());
                }
            }
            Log.i("Load database", "The method public loadDatabase() executed succesffully");

    }


    @Override
    public void onCreate(SQLiteDatabase db) {

    }

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

    }

    public ArrayList getRecords() {
        SQLiteDatabase database = getWritableDatabase();
        Log.i("The attached database is :", database.getAttachedDbs().toString());

        Cursor cursor = database.rawQuery("SELECT * FROM " + TABLE_NAME, null);
        while (cursor.moveToNext()) {
            Customer customer = new Customer();
            customer.customerName = cursor.getColumnName(1).toString();
            customer.country = cursor.getColumnName(2).toString();
            arrayList.add(customer);
        }
        return arrayList;

    }
}

【问题讨论】:

  • 顺便说一下,数据库存储在一个名为databases的子文件夹中。资产>数据库。

标签: android database sqlite


【解决方案1】:

使用现有数据库的应用出现异常行为

问题 一点也不稀奇。您没有将数据库复制到它的预期位置,即 data/data/your_package/databases(目录)但是不需要知道甚至编码(见下文)

第一次运行,复制失败,但文件已创建。

文件存在时第二次运行,不尝试复制。所以SQLiteOpenHelper 子类找不到数据库,因为它不在预期的位置,所以它调用 onCreate 方法并创建没有任何表的数据库,因此找不到表。

修复

我建议对路径进行硬编码,而是通过上下文的getDatabasePath 方法获取路径。例如

 String dbpath = context.getDatabasePath(name).getPath();

或作为文件:-

 File  db = context.getDatabasePath(name);

获取后,我建议检查父目录是否存在,如果不存在,则发出mkdirs 以便在它不存在时创建数据库文件夹(对于较旧的 API,它不会并且它不存在会阻止复制工作但是在文件存在的情况下,我相信我看到以后的版本会创建目录)。例如类似的东西:-

    File db = context.getDatabasePath(name);
    if (!db.getParentFile().exists()) db.mkdirs();
    String dbpath = db.getPath(); // may not be needed as the File object is what is then required

Here's is an example of a pretty bulletproof SQLiteOpenHelper subclass that will copy a pre-existing database.

【讨论】:

  • 太棒了!谢谢你。如此合乎逻辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 2010-09-26
  • 2018-09-23
  • 1970-01-01
相关资源
最近更新 更多