【问题标题】:Guide to use a large sqlite propulated database使用大型 sqlite 部署数据库的指南
【发布时间】:2018-08-01 12:14:06
【问题描述】:

我知道这类问题已经出现了很多(其中大多数没有有效答案),并且通过答案我没有找到我正在搜索的内容:

  1. 如果我想在我的数据库中使用图片(使用 sqlite 浏览器插入),是否有任何规范(格式、大小)或限制?所以当我在android中检索它们时它们会正常工作。 (PS:我有将近 100 张 JPEG 图片,每张大约有 1-2 Mo 大小,仅用于图片总计 150Mo)

  2. android中可以插入的数据库的大小限制是多少? (我读过关于在外部文件中插入数据库或类似的东西(因为 APK 不应该超过 50Mo),你能告诉我怎么做吗?

我为什么要问这些问题?因为我在我的数据库中使用了一张 7Mb 的 JPEG 图片并且应用程序停止了..所以我需要知道如何正确使用图片和管理数据库的大小 谢谢

import com.readystatesoftware.sqliteasset.SQLiteAssetHelper;

public class Database {
    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "URTs.db";
    private static final String DATABASE_TABLE = "OrganAnatomy";
    public static final String DATABASE_ID = "_id";
    public static final String DATABASE_GROUP_1 = "Larynx_features";
    public static final String DATABASE_CHILD_1 = "Larynx";
    public static final String DATABASE_CHILD_2 = "pictures";    

    private final Context mContext;
    private DatabaseHelper mDatabaseHelper;
    private SQLiteDatabase mDB;

    public Database(Context context) {
        mContext = context;
    }

    public void open() {
        mDatabaseHelper = new DatabaseHelper(mContext, DATABASE_NAME, null, DATABASE_VERSION);
        mDB = mDatabaseHelper.getWritableDatabase();
    }    

    public void close() {
        if (mDatabaseHelper != null) mDatabaseHelper.close();
    }

    public Cursor getDatabase() {
        String whereclause = DATABASE_CHILD_1 + " IS NOT NULL";
        return mDB.query(DATABASE_TABLE, null, whereclause, null, null, null, DATABASE_ID);
    }    

    public Cursor getID(long rowID) {
        return mDB.query(DATABASE_TABLE, null, "_id" + " = "
                + rowID , null, null, null, null);    
    }    

    public class DatabaseHelper extends SQLiteAssetHelper {    
        public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);    
        }
    }    
}

【问题讨论】:

    标签: android sqlite


    【解决方案1】:

    关于 50Mb(现在为 100Mb)的 APK 大小,您可以在 APK Expansion Files 找到有关 APK 扩展文件的信息,其中包括:-

    每次您使用 Google Play 管理中心上传 APK 时,您都有 向 APK 添加一或两个扩展文件的选项。每个文件可以 最大为 2GB,可以是您选择的任何格式,但我们建议您 在下载过程中使用压缩文件以节省带宽。 从概念上讲,每个扩展文件都扮演着不同的角色:

    主扩展文件是附加的主扩展文件 您的应用程序所需的资源。补丁扩展文件是 可选的,用于对主扩展文件的小更新。 虽然您可以随意使用这两个扩展文件,但我们 建议主扩展文件交付主要资产和 应该很少更新;补丁扩展文件应该是 更小并充当“补丁载体”,随着每个 主要版本或根据需要。

    但是,即使您的应用程序更新只需要一个新补丁 扩展文件,您仍然必须上传带有更新的新 APK 清单中的版本代码。 (Play 管理中心不允许您 将扩展文件上传到现有 APK。)

    关于在 SQLite 中存储图片,对于大于 100k 左右的图片,您应该将图片存储为文件,并将图片的路径存储在数据库中。

    有一个限制,不是 SQLite(见下文),而是 Android 光标窗口的最大大小为 2Mb,这会限制或在检索大 blob 时产生明显的不利影响。因此,为什么您的 7Mb 图像可以存储但无法检索。

    对于 SQLite,有一个限制:-

    字符串或BLOB的最大长度

    在 SQLite 中定义了字符串或 BLOB 中的最大字节数 由预处理器宏 SQLITE_MAX_LENGTH。这个的默认值 宏是 10 亿(1 亿或 1,000,000,000)。你可以 在编译时使用命令行选项提高或降低此值 像这样:

    -DSQLITE_MAX_LENGTH=123456789 当前的实现只支持长度为 231-1 或 2147483647 的字符串或 BLOB。还有一些 诸如 hex() 之类的内置函数可能在此之前就失败了。在 对安全敏感的应用程序最好不要尝试增加 最大字符串和 blob 长度。事实上,你最好降低 最大字符串和 blob 长度在 a 的范围内 如果可能的话,几百万。

    在 SQLite 的部分 INSERT 和 SELECT 处理过程中,完整的 数据库中每一行的内容被编码为单个 BLOB。所以 SQLITE_MAX_LENGTH 参数还决定了最大数量 连续字节。

    最大字符串或 BLOB 长度可以在运行时使用 sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) 接口。

    Limits In SQLite

    示例应用

    此应用程序将较小的图像存储在数据库中,但存储较大图像的路径(大小取决于public static final int MAX_FILE_SIZE = 100 * 1024;

    DatabaseHelper DBHelper.java :-

    public class DBHelper extends SQLiteOpenHelper {
        public static final String DBNAME = "images.db";
        public static final int DBVERSION = 1;
    
        // The maximum size of an image that should be stored 100K
        public static final int MAX_FILE_SIZE = 100 * 1024;
    
        public static final String TB_IMAGE = "image";
        public static final String COL_IMAGE_ID = BaseColumns._ID;
        public static final String COL_IMAGE_PATH = "image_path";
        public static final String COL_IMAGE_NAME = "image_name";
        public static final String COl_IMAGE_DESCRIPTION = "image_description";
        public static final String COL_IMAGE_SIZE = "image_size";
        public static final String COL_IMAGE_IMAGE = "image";
    
        SQLiteDatabase mDB;
    
        /**
         * Construct DBHelper, note that it will open the database and
         * thus create it if it doesn't exist
         * @param context   a context from the invoking activity
         */
        public DBHelper(Context context) {
            super(context, DBNAME, null, DBVERSION);
            mDB = this.getWritableDatabase();
        }
    
        /**
         * Create the table(s)
         * @param db
         */
        @Override
        public void onCreate(SQLiteDatabase db) {
            String crtsql = "CREATE TABLE IF NOT EXISTS " + TB_IMAGE +
                    "(" +
                    COL_IMAGE_ID + " INTEGER PRIMARY KEY, " +
                    COL_IMAGE_PATH + " TEXT UNIQUE, " +
                    COL_IMAGE_NAME + " TEXT, " +
                    COl_IMAGE_DESCRIPTION + " TEXT, " +
                    COL_IMAGE_SIZE + " INTEGER, " +
                    COL_IMAGE_IMAGE + " BLOB DEFAULT x'00'" +
                    ")";
            db.execSQL(crtsql);
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
    
        }
    
        /**
         * Return a Cursor with all the rows from the image table
         * @return  The Cursor
         */
        public Cursor getImageList() {
            return mDB.query(TB_IMAGE,null,null,null,null,null,null);
        }
    
    
        /**
         * Store an image row in the image table, noting that is the image
         *  size is small than the max size that the image will be stored as a blob
         *  otherwise a blob of 1 byte is stored due to the default value.
         * @param path          the path to the image
         * @param description   a description for the image
         * @return              the id (rowid) of the row
         */
        public long addImageFromPath(String path, String description) {
    
            ContentValues cv = new ContentValues();
            File f = new File(path);
            InputStream is;
    
            // If the file doesn't exist don't store a row
            if (!f.exists()) {
                return -1;
            }
    
            // Always store the name, description, path and size
            cv.put(COL_IMAGE_NAME,f.getName());
            cv.put(COl_IMAGE_DESCRIPTION,description);
            cv.put(COL_IMAGE_SIZE,f.length());
            cv.put(COL_IMAGE_PATH,f.getAbsolutePath());
    
            // If the size is less than the max then get the filestream
            // and convert to a byte[].
            // Note if larger then the max file size the default x'00' blob
            // will be applied
            if (f.length() < MAX_FILE_SIZE) {
                byte[] buffer = new byte[(int) f.length()];
                try {
                    is = new FileInputStream(f);
                    is.read(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                    return -1;
                }
                cv.put(COL_IMAGE_IMAGE,buffer);
            }
            // Do the insert
            return mDB.insert(TB_IMAGE,null,cv);
        }
    
        /**
         * get the image as a bitmap from the DB if stored, otherwise get it from
         * the file, according to the id.
         * @param id    the id of the row in the image table
         * @return      the bitmap to be returned (note may be empty bitmap)
         */
        public Bitmap getImage(long id) {
            byte[] ba = new byte[0];
    
            // If the image is stored in the DB then extract and return the bitmap
            if (isStoredAsImage(id)) {
                return getImageAsBitMap(id);
            }
            // If not then get the respective row from the DB
            Cursor csr = mDB.query(
                    TB_IMAGE,
                    null,
                    COL_IMAGE_ID+"=?",
                    new String[]{String.valueOf(id)},
                    null,
                    null,
                    null
            );
    
            // Prepare to convert the path to a file
            String path = ""; //<<<< default to  empty path
            File f = new File(path); //<<< default to empty file
            // If a valid row was found get the path and File from the row
            if (csr.moveToFirst()) {
                path = csr.getString(csr.getColumnIndex(COL_IMAGE_PATH));
                f = new File(path);
            }
            // done with the cursor so close it
            csr.close();
    
            // If the file exists then return the Bitmap
            if (f.exists()) {
                return BitmapFactory.decodeFile(f.getAbsolutePath());
            }
            // return an empty bitmap
            return BitmapFactory.decodeByteArray(ba,0,ba.length);
        }
    
        /**
         * Check to see if an image is stored in the DB,
         *  note assumes anything less than 8 bytes isn't an image
         * @param id    the id of the row in the image table
         * @return      true if like an image is stored, otherwise false
         */
        private boolean isStoredAsImage(long id) {
            boolean rv = true;
            byte[] ba = new byte[0];
    
            // Get the respective row from the image table
            Cursor csr = mDB.query(
                    TB_IMAGE,
                    null,
                    COL_IMAGE_ID+"=?",
                    new String[]{String.valueOf(id)},
                    null,
                    null,
                    null
            );
    
            // If a row was found get the blob into byte array ba
            // if not then ready to return false
            if (csr.moveToFirst()) {
                ba = csr.getBlob(csr.getColumnIndex(COL_IMAGE_IMAGE));
            } else {
                rv = false;
            }
            // If the byte array ba is less then 8 bytes then ready to return false
            if (ba == null || ba.length < 8) {
                rv =  false;
            }
            // done with the Cursor so close it
            csr.close();
            // return the result
            return rv;
        }
    
        /**
         * get the image (assumes isStoredAsImage is used prior to invocation)
         * @param id    the id of the respective row
         * @return      the bitmap (may be 0 length)
         */
        private Bitmap getImageAsBitMap(long id) {
            byte[] ba = new byte[0];
            Bitmap bmp;
            Cursor csr =mDB.query(
                    TB_IMAGE,
                    null,
                    COL_IMAGE_ID+"=?",
                    new String[]{String.valueOf(id)},
                    null,
                    null,
                    null
            );
            if (csr.moveToFirst()) {
                ba = csr.getBlob(csr.getColumnIndex(COL_IMAGE_IMAGE));
            }
            csr.close();
            return BitmapFactory.decodeByteArray(ba,0,ba.length);
        }
    }
    

    MainActivity MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        public static String IMAGE_STORE_PATH;
        public static final String IMAGES_DIRECTORY = "images";
        private static File images_file;
    
        ArrayAdapter<String> mAdapter;
        ListView mListView01, mListView02;
        ArrayList<String> mImages;
        CursorAdapter mCsrAdapter;
        Cursor mCsr;
        ImageView mImageView;
        DBHelper mDBHlpr;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // get the View/Viewgroup IDs
            mListView01 = this.findViewById(R.id.listview001); // File List
            mListView02 = this.findViewById(R.id.listview002); // DB List
            mImageView = this.findViewById(R.id.imageview001); // Image display
    
            // get an instance of the DBHelper
            mDBHlpr = new DBHelper(this);
    
            // Copy images from raw folder to data/data/<package>/Files/images
            // Also store all the images in the Database (or not depedning upon size)
            getImagesFile(this);
            if (getImagesCount() < 1) {
                loadRawImages();
                storeImagesToDB();
            }
            // Setup the two ListViews to display image name lists
            displayList();
            displayListFromDB();
    
            // setup the file list so that when an item is clicked the image is displayed
            mListView01.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    String imagename = mListView01.getItemAtPosition(i).toString();
                    displayImage(imagename);
                }
            });
    
            // setup the DB list so that when an item is clicked the image is displayed
            mListView02.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    displayDBImage(l);
                }
            });
        }
    
        /**
         * Store the images in the images folder to the DB giving then a calculated description
         * e.g. image1, image2 .....
         */
        private void storeImagesToDB() {
            File f = getImagesFile(this);
            File[] images = f.listFiles();
            int imagecounter = 1;
            for (File img: images) {
                mDBHlpr.addImageFromPath(img.getPath(),"image" + String.valueOf(imagecounter++));
            }
        }
    
        /**
         * return the directory/folder where the images are stored as a File
         * @param context   a valid context
         * @return          the number of images
         */
        public static File getImagesFile(Context context) {
            if (images_file == null) {
                images_file = new File(context.getFilesDir().getPath() + File.separator + IMAGES_DIRECTORY);
                if (!images_file.exists()) {
                    images_file.mkdirs();
                }
            }
            return images_file;
        }
    
        /**
         * get the number of images
         * @return the number of images
         */
        public static long getImagesCount() {
            File[] files = images_file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return file.isFile();
                }
            });
            return (long) files.length;
        }
    
        /**
         * Setup/refresh the list of images according to the images folder
         * (left ListView)
         */
        private void displayList() {
            if (mImages == null) {
                mImages = new ArrayList<>();
            } else {
                mImages.clear();
            }
            mImages.addAll(Arrays.asList(images_file.list()));
            if (mAdapter == null) {
                mAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, mImages);
                mListView01.setAdapter(mAdapter);
            } else {
                mAdapter.notifyDataSetChanged();
            }
        }
    
        /**
         * Set the image view according to the file
         * @param imageName the name of the image (as per the ListView)
         */
        private void displayImage(String imageName) {
            File img = new File(images_file.getPath() + File.separator + imageName);
            if (img.exists()) {
                Bitmap bmp = BitmapFactory.decodeFile(img.getAbsolutePath());
                mImageView.setImageBitmap(bmp);
            }
        }
    
        /**
         * Set the image view according to the image stored/referred to by the DB
         * @param id    the id of the respective row in the image table
         */
        private void displayDBImage(long id) {
            mImageView.setImageBitmap(mDBHlpr.getImage(id));
        }
    
        /**
         * Setup/refresh the list of images as obtained from the DB (right listview)
         */
        private void displayListFromDB() {
            mCsr = mDBHlpr.getImageList();
            if (mCsrAdapter == null) {
                mCsrAdapter = new SimpleCursorAdapter(
                        this,
                        android.R.layout.simple_list_item_2,
                        mCsr,
                        new String[]{DBHelper.COL_IMAGE_NAME,DBHelper.COL_IMAGE_PATH},
                        new int[]{android.R.id.text1,android.R.id.text2},
                        0
                );
                mListView02.setAdapter(mCsrAdapter);
            } else {
                mCsrAdapter.swapCursor(mCsr);
            }
        }
    
        /**
         * Load (copy from raw folder to images folder) all images
         */
        private void loadRawImages() {
            Field[] fields = R.raw.class.getFields();
            int resourceID = 0;
            String resourceName;
            for (Field fld: fields) {
                resourceName = fld.getName();
                try {
                    resourceID = fld.getInt(fld);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                Log.d("RAW FLDINFO","name=" + fld.getName() + " ID=" + String.valueOf(resourceID));
                copyResourceImageToImages(resourceID,resourceName, true);
            }
        }
    
        /**
         * Copy an image from the raw directory (app/src/main/res/raw directory) to
         *  the App's data/data/files/images folder
         * @param resourceID        ID of the resource
         * @param resourceName      name of the resource (file name less extension)
         * @param throw_exception   true if an exception should be thrown
         */
        private void copyResourceImageToImages(int resourceID, String resourceName, boolean throw_exception) {
            String tag = "CPYRSRCTOIMAGES";
            InputStream is = getResources().openRawResource(resourceID);
            File of = new File(images_file.getPath() + File.separator + resourceName + ".jpg");
            Log.d(tag,"Initiating Copy of File " + of.getName());
            int buffer_size = 1024 * 4;
            int length_to_copy = buffer_size;
            int bytesread = 0;
            long bytescopied = 0;
            OutputStream os;
            byte[] buffer = new byte[buffer_size];
            if (!of.exists()) {
                try {
                    of.createNewFile();
                } catch (IOException e) {
                    Log.d(tag,"Error Creating File " + of.getName());
                    e.printStackTrace();
                    if (throw_exception) {
                        throw new RuntimeException("Error Creating Output File" + of.getName());
                    }
                    return;
                }
            }
            try {
                os = new FileOutputStream(of);
            } catch (IOException e) {
                Log.d(tag,"Error Creating OutputStream for File " + of.getName());
                e.printStackTrace();
                if (throw_exception) {
                    throw new RuntimeException("Error Creating OutputStream for File " + of.getName());
                }
                return;
            }
            if (os == null) {
                throw new RuntimeException("OutputStream not initialised.");
            }
            try {
                while ((bytesread = is.read(buffer)) > 0 ){
                    try {
                        os.write(buffer, 0, bytesread);
                    } catch (IOException e) {
                        String msg = "Error Writing to Output File " + of.getName() + " Bytes Copied = " + bytescopied;
                        Log.d(tag, msg);
                        e.printStackTrace();
                        if (throw_exception) {
                            os.close();
                            of.delete();
                            throw new RuntimeException(msg);
                        }
                        is.close();
                        os.close();
                        of.delete();
                        return;
                    }
                    bytescopied = bytescopied + bytesread;
                }
            }catch (IOException e) {
                String msg = "Error reading Input File " + resourceName + " Bytes Copied = " + bytescopied;
                Log.d(tag,"Error Reading Input File " + resourceName);
                e.printStackTrace();
                if (throw_exception) {
                    throw new RuntimeException(msg);
                }
                try {
                    is.close();
                    os.close();
                    of.delete();
                } catch (IOException e2) {
                    e2.printStackTrace();
                }
            }
            Log.d(tag,"File " + of.getName() +" Copied - Bytes Successfully Copied = " + bytescopied);
            try {
                os.flush();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    MainActivity 的布局 activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toTopOf="@id/listview001"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="spread_inside" />
    
        <ListView
            android:id="@+id/listview001"
            android:layout_width="400dp"
            android:layout_height="300dp"
            android:background="#FFFFAAAA"
            app:layout_constraintBottom_toTopOf="@id/scrollview"
            app:layout_constraintEnd_toStartOf="@+id/listview002"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/title"
            app:layout_constraintVertical_bias="0.5">
    
        </ListView>
    
        <ListView
            android:id="@+id/listview002"
            android:layout_width="400dp"
            android:layout_height="300dp"
            android:background="#FFAAAAFF"
            app:layout_constraintBottom_toTopOf="@id/scrollview"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/listview001"
            app:layout_constraintTop_toBottomOf="@id/title"
            app:layout_constraintVertical_bias="0.5">
    
        </ListView>
    
        <ScrollView
            android:id="@+id/scrollview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/listview001"
            app:layout_constraintTop_toBottomOf="@id/listview001">
    
            <ImageView
                android:id="@+id/imageview001"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#FFAAFFAA"
                android:contentDescription="A Picture"
                app:layout_constraintRight_toLeftOf="parent" />
        </ScrollView>
    </android.support.constraint.ConstraintLayout>
    

    注意图像存储在 res/raw 文件夹中,例如:-

    数据库(来自 id 20)看起来像(突出显示的是存储在数据库中的 100k 以下的一个图像(注意文件留在磁盘上,但如果需要可以删除以节省空间)):-

    首次启动时的应用程序(无图像显示):-

    点击图片dsc0055.jpg(ListView都可以)

    【讨论】:

    • 非常感谢您的回答,一如既往的有帮助! ..只是两个愚蠢的问题:1 /如果我设法将每张图片的大小减小到小于100k ..(将它们存储在sqlite数据库中是否可以,其中100个)? ..2/将图像存储在文件中并将图像的路径存储在数据库中是什么意思? (你的意思是在桌面或应用程序文件夹中创建一个文件。如果它们只是一个floder的路径,它们将如何在应用程序中显示为图片)你能解释更多吗,因为我认为这可能是解决方案对我的问题..抱歉打扰你
    • 是的,100k 是根据35% Faster Than The Filesystem 的效率而定的。存储为文件意味着在 res/raw 文件夹或 assets 文件夹中以 jpg/png 的形式存储。将代码放入示例应用程序的答案中,该示例应用程序将小图像作为 blob 存储在 DB 中,或者将较大的图像作为路径存储。
    • Okey 为您提供了第一个答案。但对于第二个仍然无法弄清楚如何做到这一点。你是什​​么意思'will,drop in code' ..你是要发布答案还是我应该为你提供我的代码..再次抱歉
    • @ErrahbiZouhair 代码现在在那里,加上存储图像的屏幕截图。稍后将添加数据库内容的屏幕截图。
    • 不修改现有的 database.java,可能从修改预先存在的 DB 以合并新列开始,例如image_path.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2019-01-24
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    相关资源
    最近更新 更多