2012-01-06 9 views
12

Si desea rellenar previamente una base de datos (SQLite) en Android, esto no es tan fácil como uno podría pensar.La forma más rápida y eficiente de rellenar previamente la base de datos en Android

Encontré this tutorial, que a menudo también se menciona aquí en Stack Overflow.

Pero realmente no me gusta esa manera de rellenar previamente la base de datos ya que toma el control del manejador de base de datos y crea los archivos usted mismo. Preferiría no tocar el sistema de archivos y dejar que el manejador de la base de datos haga todo por sí mismo.

Así que lo que pensé que uno podría hacer es crear la base de datos en onCreate del manejador de base de datos(), como de costumbre, pero luego cargar un archivo (.sql) desde/activos que contiene las instrucciones para rellenar los valores:

INSERT INTO testTable (name, pet) VALUES ('Mike', 'Tiger'); 
INSERT INTO testTable (name, pet) VALUES ('Tom', 'Cat'); 
... 

Pero llamar a execSQL() en el controlador onCreate() realmente no funciona. Parece que el archivo/assets no debe tener más de 1 MB y el execSQL() solo ejecuta la primera declaración (Mike - Tiger).

¿Qué harías para rellenar previamente la base de datos?

+0

¿Has leído la documentación de execSQL? ... de todos modos ... puedes almacenar datos en json o hacer un pre-análisis de DB temporal y copiarlo en la base de datos creada por hander – Selvin

+0

Por supuesto, he leído eso. Sugieren usar un objeto ContentValues ​​e insertarlo en la base de datos utilizando el método insert (...). Pero execSQL (...) no está mal. Y, cualquiera que sea el enfoque que elijas, no servirá mucho a la velocidad si eliges uno alternativo. – caw

+0

Crear una base de datos de antemano y copiarla es exactamente lo que describí en la pregunta;) Y JSON no será más rápido que mi enfoque con el archivo de declaración .sql preconstruido. Debido a la conversión, será incluso más lento. – caw

Respuesta

5

Sugiero lo siguiente:

  1. Wrap toda la lógica de INSERT en una transacción (BEGIN... COMMIT, oa través de la beginTransaction() ... endTransaction() API)
  2. Como ya se ha sugerido, utilizar las API de vinculación y reciclar objetos.
  3. no generan índices hasta después de esta inserción masiva se ha completado.

Además echar un vistazo a Faster bulk inserts in sqlite3?

+0

¡Gracias por estos consejos cortos! Parecen acelerar el proceso de copia de las entradas de la base de datos extremadamente. – caw

0

ye, el activos tiene quizás un límite de tamaño, por lo que si es mayor que el límite, puede cortar en más archivos.

y apoyo exesql frase más sql, aquí darle un ejemplo:

BufferedReader br = null; 
    try { 
     br = new BufferedReader(new InputStreamReader(asManager.open(INIT_FILE)), 1024 * 4); 
     String line = null; 
     db.beginTransaction(); 
     while ((line = br.readLine()) != null) { 
      db.execSQL(line); 
     } 
     db.setTransactionSuccessful(); 
    } catch (IOException e) { 
     FLog.e(LOG_TAG, "read database init file error"); 
    } finally { 
     db.endTransaction(); 
     if (br != null) { 
      try { 
       br.close(); 
      } catch (IOException e) { 
       FLog.e(LOG_TAG, "buffer reader close error"); 
      } 
     } 
    } 

ejemplo anterior requieren el init_file necesita cada línea es una sentencia de SQL.

Además, si su archivo de sentencias SQL es grande, se puede crear la base de datos a cabo sitio del androide (soporte para SQLite para Windows, Linux, por lo que puede crear la base de datos en su sistema operativo, y copiar el archivo de base de datos para su carpeta de activos, si es grande, puede comprimirla)

cuando se ejecuta su aplicación, puede obtener el archivo de base de datos de los activos, dirigido a guardar en la carpeta de la base de datos de su aplicación (si lo comprime, puede descomprimirlo en la base de datos de la aplicación carpeta)

espero que pueda ayudarlo -):

+0

¡Gracias por tu esfuerzo! execSQL (...) no es compatible con más de una instrucción al mismo tiempo, pero dividir la cadena en declaraciones simples sí funciona, por supuesto. Pero estoy buscando un enfoque más rápido que este. Y su segunda sugerencia, es decir, crear la base de datos fuera de Android y copiarla, es lo que ya describí en la pregunta y lo que no quiero hacer. – caw

4

Su pregunta indica que desea la manera más rápida, pero no le gusta la forma en que se hace en el artículo, no desea reemplazar manualmente el archivo DB (aunque, en realidad, puede ser más rápido que llenar DB vacía con consultas).

Tuve exactamente los mismos pensamientos, y me di cuenta de que poblar a través de sentencias SQL y prepoblación puede ser la mejor solución, pero depende de la forma en que usará la base de datos.

En mi aplicación necesito tener aproximadamente 2600 filas (con 4 columnas) en DB en la primera ejecución, son los datos para la autocompletación y algunas otras cosas más. Se modificará con bastante poca frecuencia (los usuarios pueden agregar registros personalizados, pero la mayoría de las veces, no es necesario) y es bastante grande. Al poblarlo de las sentencias de SQL, no solo se requiere mucho más tiempo, sino más espacio en el APK (suponiendo que almaceno datos dentro de él, alternativamente, podría descargarlo de internet).

Este es el caso muy simple (la inserción "Grande" puede tener lugar solo una vez y solo en el primer arranque) y decidí ir copiando el archivo DB previamente rellenado.Claro, puede que no sea la mejor manera, pero es más rápido. Quiero que mis usuarios puedan usar la aplicación tan rápido como sea posible y tratar la velocidad como una prioridad, y realmente les gusta. Por el contrario, dudo que se alegrarían cuando la aplicación se ralentizaría porque pensé que la solución más lenta y más agradable es en realidad mejor.

Si en lugar de 2600 mi mesa tendría inicialmente ~ 50 filas, me gustaría ir con sentencias SQL, ya que la velocidad y la diferencia de tamaño no sería tan grande.

usted tiene que decidir qué solución se adapta mejor su caso. Si prevé algún problema que pueda surgir al usar la opción "db prepoblada", no lo use. Si no está seguro de estos problemas, pregunte y proporcione más detalles sobre cómo usará (y eventualmente, actualizará) los contenidos del DB. Si no está seguro de qué solución será más rápida, hágalo referencia. Y no le tenga miedo a ese método de copiado de archivos: puede funcionar muy bien, si se usa con prudencia.

+0

¡Gracias por estos interesantes pensamientos! Por supuesto, uno debe determinar el caso de uso antes de configurar el proceso de importación. – caw

2

escribí una clase dbUtils similar a la respuesta anterior. Es parte de la herramienta ORM greenDAO y está disponible en github. La diferencia es que tratará de encontrar límites de enunciados utilizando una expresión regular simple, no solo terminaciones de línea. Si tiene que confiar en un archivo SQL, dudo que haya una manera más rápida.

Pero, si usted puede proporcionar los datos en otro formato, debe ser significativamente más rápido que utilizando una secuencia de comandos SQL. El truco es usar un compiled statement. Para cada fila de datos, vincula los valores analizados a la declaración y ejecuta la instrucción. Y, por supuesto, debe hacer esto dentro de una transacción. Recomendaría un formato de archivo separado de delimitador simple (por ejemplo, CSV) porque se puede analizar más rápido que XML o JSON.

Hicimos algunas performance tests para greenDAO. Para nuestros datos de prueba, tuvimos índices de inserción de aproximadamente 5000 filas por segundo. Y por alguna razón, el rate dropped to half with Android 4.0.

+0

¡Gracias! Su sugerencia (declaraciones compiladas, datos de enlace, lista CSV) es probablemente el enfoque más eficiente si no desea copiar un archivo de base de datos completo. – caw

0

que utilizan este método. Primero crea tu base de datos sqlite. Hay algunos programas que puedes usar Me gusta SqliteBrowser. Luego copie su archivo de base de datos en su carpeta de activos. Entonces puede usar este código en el constructor de SQLiteOpenHelper.

final String outFileName = DB_PATH + NAME; 

     if(! new File(outFileName).exists()){ 
      this.getWritableDatabase().close(); 
      //Open your local db as the input stream 
      final InputStream myInput = ctx.getAssets().open(NAME, Context.MODE_PRIVATE); 

      //Open the empty db as the output stream 
      final OutputStream myOutput = new FileOutputStream(outFileName); 
      //final FileOutputStream myOutput = context.openFileOutput(outFileName, Context.MODE_PRIVATE); 

      //transfer bytes from the inputfile to the outputfile 
      final byte[] buffer = new byte[1024]; 
      int length; 
      while ((length = myInput.read(buffer))>0){ 
       myOutput.write(buffer, 0, length); 
      } 

      //Close the streams 
      myOutput.flush(); 
      ((FileOutputStream) myOutput).getFD().sync(); 
      myOutput.close(); 
      myInput.close(); 
     } 
     } catch (final Exception e) { 
      // TODO: handle exception 
     } 

DB_PATH es algo así como /data/data/com.mypackage.myapp/databases/

NAME es el nombre que la base de datos que elija "mydatabase.db"

Sé que hay muchas mejoras en este código, pero funcionó muy bien y es MUY RÁPIDO. Así que lo dejé solo. Al igual que esto podría ser aún mejor en el método onCreate(). También es probable que comprobar si el archivo existe cada vez no es el mejor. De todos modos, como dije, funciona, es rápido y confiable.

+0

¡Gracias! Esto parece ser muy rápido, pero es lo que ya describí en la pregunta: en realidad, no quiero copiar el archivo completo de la base de datos, porque hay varias desventajas. – caw

2

Puedes tener tu pastel y comértelo también. Aquí hay una solución que puede respetar el uso de su adaptador de base de datos y también usar un proceso de copia simple (y mucho más rápido) para una base de datos previamente poblada.

Estoy usando un adaptador de base de datos basado en uno de los ejemplos de Google. Incluye una clase interna dbHelper() que amplía la clase SQLiteOpenHelper() de Android. El truco es anular su método onCreate(). Este método solo se invoca cuando el asistente no puede encontrar el DB al que hace referencia y debe crear el DB por usted. Esto solo debería ocurrir la primera vez que se invoca en cualquier instalación de dispositivo determinada, que es la única vez que desea copiar el DB. Así anularlo como esto -

@Override 
    public void onCreate(SQLiteDatabase db) { 
     mNeedToCopyDb = true; 
    } 

Por supuesto, asegúrese de que ha declarado primero y inicializado esta bandera en el DbHelper -

 private Boolean mNeedToCopyDb = false; 

Ahora, en el método open() de su dbAdapter se puede probar para ver si necesita copiar el DB. Si lo hace, cierre el helper, copie el DB y finalmente abra un nuevo helper (vea el código a continuación). Todos los intentos futuros de abrir el archivo db utilizando el adaptador db encontrarán su base de datos (copiada) y, por lo tanto, no se invocará el método onCreate() de la clase interna DbHelper y el indicador mNeedToCopyDb seguirá siendo falso.

/** 
* Open the database using the adapter. If it cannot be opened, try to 
* create a new instance of the database. If it cannot be created, 
* throw an exception to signal the failure. 
* 
* @return this (self reference, allowing this to be chained in an 
*   initialization call) 
* @throws SQLException if the database could neither be opened nor created 
*/ 
public MyDbAdapter open() throws SQLException { 
    mDbHelper = new DatabaseHelper(mCtx); 
    mDb = mDbHelper.getReadableDatabase(); 

    if (mDbHelper.mNeedToCopyDb == true){ 
     mDbHelper.close(); 
     try { 
      copyDatabase(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } finally { 
      mDbHelper = new DatabaseHelper(mCtx); 
      mDb = mDbHelper.getReadableDatabase(); 
     } 
    } 
    return this; 
} 

Sólo tiene que colocar algo de código para hacer la copia de base de datos dentro de su adaptador db en un método llamado copyDatabase() tal como se utiliza anteriormente. Puede usar el valor de mDb que se actualizó con la primera instancia de DbHelper (cuando creó el código auxiliar) para que la ruta se use para su flujo de salida cuando realiza la copia. Construir el flujo de entrada como esta

dbInputStream = mCtx.getResources().openRawResource(R.raw.mydatabase); 

[Nota: Si el archivo de base de datos es demasiado grande para copiar de un trago luego simplemente dividirla en unas pocas piezas.]

Esto funciona muy rápido y pone todo el código de acceso db (incluida la copia del DB si es necesario) en su adaptador de db.

+0

Tenga en cuenta que el código anterior abre el archivo db con getReadableDatabase(). Si va a escribir en el db entonces use getWriteableDatabase(). Personalmente uso una base de datos separada para los datos generados por el usuario y mantengo esta base de datos de referencia copiada de solo lectura. – PaulP

+0

Gracias por la sugerencia de integrar la copia de la base de datos en el manejador determinado. Pero faltan algunas partes importantes, como el método copyDatabase() o la lectura de la ruta anterior. – caw

+0

Obtiene la ruta anterior del miembro mDb de su adaptador. Se guardará cuando se abra el DB mediante el método dbAdapter.open() (consulte el código provisto arriba). Solo use esto en su rutina de copia - dbOutputStream = new FileOutputStream (mDb.getPath()); En cuanto al código copydatabase(), ya lo tiene en el ejemplo al que hizo referencia. Simplemente adapte eso a sus necesidades. – PaulP

0

Si los datos no son privados, simplemente organícelos en su sitio web y descárguelos en la primera ejecución. De esa manera puedes mantenerlo actualizado. Siempre que recuerde tomar en cuenta la versión de la aplicación cuando la suba a su servidor web.

Cuestiones relacionadas