【问题标题】:Download SQLite database from Internet and load into Android application从 Internet 下载 SQLite 数据库并加载到 Android 应用程序中
【发布时间】:2012-04-20 23:38:26
【问题描述】:

对于我的 android 应用程序,我想使用一个大型数据库(大约 45 MB)。

一种解决方案是将(拆分的)数据库包含在 assets 文件夹中,并在第一次启动时将其复制到数据库目录。

但这会消耗两次磁盘空间 - 一次在无法删除文件的资产文件夹中,另一次在已复制到的数据库目录中。

所以我宁愿在第一次启动时从互联网(网络服务器)下载数据库。我怎么能这样做?我可以下载完整的 SQLite 文件并将其保存到数据库目录吗?还是我应该使用用于填充数据库的 JSON 数据文件?

【问题讨论】:

    标签: android sqlite download


    【解决方案1】:

    一种解决方案是将(拆分的)数据库包含在 assets 文件夹中,并在第一次启动时将其复制到数据库目录。

    不必拆分,只需压缩即可。示例见SQLiteAssetHelper

    我该怎么做?

    使用HttpUrlConnection。或者,使用HttpClient

    我可以下载完整的 SQLite 文件并保存到数据库目录吗?

    是的。使用getDatabasePath() 获取要使用的正确本地路径。

    还是我应该使用用于填充数据库的 JSON 数据文件?

    你可以,但是对于 45MB,这将是非常缓慢的。

    【讨论】:

    • 谢谢! SQLiteAssetHelper 看起来很有趣,但是,资产的最大大小是 1MB,不是吗?所以文件是否被压缩并不重要——就是这个限制。那么你会推荐使用SQLiteAssetHelper 还是从网络服务器下载?
    • @MarcoW.:“资产的最大大小是 1MB,不是吗?” -- AFAIK,这是被aapt 压缩的资产的最大大小。你可以拥有更大的资产,只要aapt 不会压缩它们,这就是SQLiteAssetHelper 使用ZIP 文件的原因。您从 Web 服务器下载的论点仍然非常有效(例如,重复空间)——我只是为遇到此问题的其他人澄清您的“拆分”参考。顺便说一句,您也可以使用DownloadManager 下载数据库,不过下载后您可以自己将其移动到最终位置。
    • @CommonsWare,您介意提供一个将数据库文件下载到 sdcard 中然后使用SQLiteAssetHelper 复制和使用它的示例吗?谢谢
    • @blueware: SQLiteAssetHelper 用于在assets/ 中打包数据库。它不适用于下载数据库文件。
    • @CommonsWare,是的,这是正确的,但如果你能帮助解决这个问题,我将不胜感激。至于SQLiteAssetHelper ,我压缩了一个数据库(9MB),它的大小缩小到(3MB)->放入Assets,但应用程序大小增加了(9MB)而不是(3MB),知道吗?
    【解决方案2】:

    有人问我最终得到了什么解决方案,这是我使用的代码(大致)。它可能不再完整,也不优雅或干净。但也许对你有帮助。

    MyActivity.java

    public class MyActivity extends Activity {
    
        private static final String SD_CARD_FOLDER = "MyApp";
        private static final String DB_DOWNLOAD_PATH = "http://www.example.org/downloads/dictionary.sqlite";
        private Database mDB = null;
        private DatabaseDownloadTask mDatabaseDownloadTask = null;
        private DatabaseOpenTask mDatabaseOpenTask = null;
    
        private class DatabaseDownloadTask extends AsyncTask<Context, Integer, Boolean> {
    
            @Override
            protected void onPreExecute() {
                mProgressDialog = new ProgressDialog(MyActivity.this);
                mProgressDialog.setTitle(getString(R.string.please_wait));
                mProgressDialog.setMessage(getString(R.string.downloading_database));
                mProgressDialog.setIndeterminate(false);
                mProgressDialog.setMax(100);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setCancelable(false);
                mProgressDialog.show();
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            }
    
            @Override
            protected Boolean doInBackground(Context... params) {
                try {
                    File dbDownloadPath = new File(Database.getDatabaseFolder());
                    if (!dbDownloadPath.exists()) {
                        dbDownloadPath.mkdirs();
                    }
                    HttpParams httpParameters = new BasicHttpParams();
                    HttpConnectionParams.setConnectionTimeout(httpParameters, 5000);
                    HttpConnectionParams.setSoTimeout(httpParameters, 5000);
                    DefaultHttpClient client = new DefaultHttpClient(httpParameters);
                    HttpGet httpGet = new HttpGet(DB_DOWNLOAD_PATH);
                    InputStream content = null;
                    try {
                        HttpResponse execute = client.execute(httpGet);
                        if (execute.getStatusLine().getStatusCode() != 200) { return null; }
                        content = execute.getEntity().getContent();
                        long downloadSize = execute.getEntity().getContentLength();
                        FileOutputStream fos = new FileOutputStream(Database.getDatabaseFolder()+Database.DATABASE_NAME+".sqlite");
                        byte[] buffer = new byte[256];
                        int read;
                        long downloadedAlready = 0;
                        while ((read = content.read(buffer)) != -1) {
                            fos.write(buffer, 0, read);
                            downloadedAlready += read;
                            publishProgress((int) (downloadedAlready*100/downloadSize));
                        }
                        fos.flush();
                        fos.close();
                        content.close();
                        return true;
                    }
                    catch (Exception e) {
                        if (content != null) {
                            try {
                                content.close();
                            }
                            catch (IOException e1) {}
                        }
                        return false;
                    }
                }
                catch (Exception e) {
                    return false;
                }
            }
    
            protected void onProgressUpdate(Integer... values) {
                if (mProgressDialog != null) {
                    if (mProgressDialog.isShowing()) {
                        mProgressDialog.setProgress(values[0]);
                    }
                }
            }
    
            @Override
            protected void onPostExecute(Boolean result) {
                if (mProgressDialog != null) {
                    mProgressDialog.dismiss();
                    mProgressDialog = null;
                }
                if (result.equals(Boolean.TRUE)) {
                    Toast.makeText(MyActivity.this, getString(R.string.database_download_success), Toast.LENGTH_LONG).show();
                    mDatabaseOpenTask = new DatabaseOpenTask();
                    mDatabaseOpenTask.execute(new Context[] { MyActivity.this });
                }
                else {
                    Toast.makeText(getApplicationContext(), getString(R.string.database_download_fail), Toast.LENGTH_LONG).show();
                    finish();
                }
            }
    
        }
    
        private class DatabaseOpenTask extends AsyncTask<Context, Void, Database> {
    
            @Override
            protected Database doInBackground(Context ... ctx) {
                try {
                    String externalBaseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
                    // DELETE OLD DATABASE ANFANG
                    File oldFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+SD_CARD_FOLDER);
                    File oldFile = new File(oldFolder, "dictionary.sqlite");
                    if (oldFile.exists()) {
                        oldFile.delete();
                    }
                    if (oldFolder.exists()) {
                        oldFolder.delete();
                    }
                    // DELETE OLD DATABASE ENDE
                    File newDB = new File(Database.getDatabaseFolder()+"dictionary.sqlite");
                    if (newDB.exists()) {
                        return new Database(ctx[0]);
                    }
                    else {
                        return null;
                    }
                }
                catch (Exception e) {
                    return null;
                }
            }
    
            @Override
            protected void onPreExecute() {
                mProgressDialog = ProgressDialog.show(MainActivity.this, getString(R.string.please_wait), "Loading the database! This may take some time ...", true);
            }
    
            @Override
            protected void onPostExecute(Database newDB) {
                if (mProgressDialog != null) {
                    mProgressDialog.dismiss();
                    mProgressDialog = null;
                }
                if (newDB == null) {
                    mDB = null;
                    AlertDialog.Builder downloadDatabase = new AlertDialog.Builder(MyActivity.this);
                    downloadDatabase.setTitle(getString(R.string.downloadDatabase));
                    downloadDatabase.setCancelable(false);
                    downloadDatabase.setMessage(getString(R.string.wantToDownloadDatabaseNow));
                    downloadDatabase.setPositiveButton(getString(R.string.download), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            mDatabaseDownloadTask = new DatabaseDownloadTask();
                            mDatabaseDownloadTask.execute();
                        }
                    });
                    downloadDatabase.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            finish();
                        }
                    });
                    downloadDatabase.show();
                }
                else {
                    mDB = newDB;
                }
            }
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            if (mDatabaseDownloadTask != null) {
                if (mDatabaseDownloadTask.getStatus() != AsyncTask.Status.FINISHED) {
                    mDatabaseDownloadTask.cancel(true);
                }
            }
            if (mDatabaseOpenTask != null) {
                if (mDatabaseOpenTask.getStatus() != AsyncTask.Status.FINISHED) {
                    mDatabaseOpenTask.cancel(true);
                }
            }
            if (mProgressDialog != null) {
                mProgressDialog.dismiss();
                mProgressDialog = null;
            }
            if (mDB != null) {
                mDB.close();
                mDB = null;
            }
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                Toast.makeText(getApplicationContext(), getString(R.string.sd_card_not_found), Toast.LENGTH_LONG).show();
                finish();
            }
            mDatabaseOpenTask = new DatabaseOpenTask();
            mDatabaseOpenTask.execute(new Context[] { this });
        }
    
    }
    

    Database.java

    公共类数据库扩展 SQLiteOpenHelper {

    private static final String DATABASE_NAME = "dictionary";
    private String DATABASE_PATH = null;
    private static final int DATABASE_VERSION = 1;
    private static final String PACKAGE_NAME = "com.my.package";
    private SQLiteDatabase db;
    
    public Database(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        DATABASE_PATH = getDatabaseFolder()+DATABASE_NAME+".sqlite";
        db = getWritableDatabase();
    }
    
    public static String getDatabaseFolder() {
        return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Android/data/"+PACKAGE_NAME+"/databases/";
    }
    
    @Override
    public synchronized SQLiteDatabase getWritableDatabase() {
        try {
            if (db != null) {
                if (db.isOpen()) {
                    return db;
                }
            }
            return SQLiteDatabase.openDatabase(DATABASE_PATH, null, SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
        }
        catch (Exception e) {
            return null;
        }
    }
    
    @Override
    public synchronized void close() {
         if (db != null) {
             db.close();
             db = null;
         }
         super.close();
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) { }
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
    

    }

    【讨论】:

    • 重命名数据库有什么特殊原因吗?文件 tempFile = new File(Database.getDatabaseFolder()+"temp.sqlite"); tempFile.renameTo(new File(Database.getDatabaseFolder()+"dictionary.sqlite"));
    • @LomaRoma 你说得对,现在这是多余的。但是,如果不重命名文件,则必须在将数据库文件写入磁盘时更改名称。我已经编辑了答案。请注意,该 sn-p 中还有其他几部分可能需要改进,甚至是多余的。
    • 我在下载一个 sqlite 数据库并在应用程序中使用它时遇到问题。基本上,SQLite 会抛出一个错误,即数据库已损坏,但事实并非如此。当我想到它时,看起来当数据库更改而不打开时会引发损坏错误(例如删除旧数据库文件+下载新文件)可能是这样吗?你有这些问题吗?
    【解决方案3】:

    如果您的数据库那么大,我认为 JSON 方法会更好。

    我不能 100% 确定,但我相信当您发布应用程序更新时,您的设备会下载整个应用程序。如果您将一个 45mb 的文件与您的应用程序捆绑在一起,这意味着每次您推送更新时,您的用户都会被下载一个 45mb 的文件所困扰。不是个好主意。

    您可以做的是在您的应用程序中包含结构正确且没有数据的数据库。当用户打开应用程序时,它可以连接到您的 Web 服务器并获取 JSON 数据以填充数据库。这样,当用户更新您的应用程序时,他们就不会因下载新的大文件而陷入困境。更新不会清除现有数据库。

    您甚至可以想出一些东西并通过 JSON 获取部分数据库,直到用户拥有一切。这样,如果您正在执行一个怪物查询并且他们失去了与互联网的连接,那么不会发生太糟糕的事情。

    【讨论】:

    • 谢谢!当然,不应每次都包含大型数据库文件的更新方面很重要。所以我绝对应该使用一个在第一次运行时下载所有必要数据的小应用程序。但正如 Commonsware 所暗示的,JSON 似乎比仅仅下载完整的 SQLite 数据库文件要慢。
    猜你喜欢
    • 1970-01-01
    • 2014-04-16
    • 2017-05-06
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-17
    相关资源
    最近更新 更多