2012-01-06 11 views
12

Se si desidera precompilare un database (SQLite) in Android, non è così facile come si potrebbe pensare.Il modo più veloce ed efficiente per precompilare il database in Android

Quindi ho trovato this tutorial che viene spesso indicato anche qui su Stack Overflow.

Ma non mi piace molto questo modo di pre-compilare il database poiché si prende il controllo dal gestore database e si creano i file da soli. Preferirei non toccare il file system e lasciare che il gestore del database faccia tutto da solo.

Così che cosa ho pensato che si potrebbe fare è creare il database in onCreate del gestore di database() come al solito ma poi caricare un file (sql) da/attivi che contiene le istruzioni per compilare i valori:

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

Ma chiamare execSQL() nel gestore onCreate() non funziona. Sembra che il file/assets non deve avere più di 1 MB e execSQL() esegue solo la prima istruzione (Mike - Tiger).

Cosa farebbe pre-compilare il database?

+0

hai letto la documentazione di execSQL? ... anway ... è possibile archiviare i dati in json o eseguire il db temporaneo pre-polimerizzato e copiarlo nel database creato da hander – Selvin

+0

Naturalmente, l'ho letto. Suggeriscono di utilizzare un oggetto ContentValues ​​e inserirlo nel database utilizzando il metodo insert (...). Ma execSQL (...) non è sbagliato. E - qualunque approccio tu scelga - non farà molto alla velocità se sceglierai un'alternativa. – caw

+0

Creare un database in anticipo e copiarlo è esattamente ciò che ho descritto nella domanda;) E JSON non sarà più veloce del mio approccio con il file di istruzioni .sql precostruito. A causa della conversione, sarà anche più lento. – caw

risposta

5

suggerisco il seguente:

  1. Wrap tutta la tua INSERT logica in una transazione (BEGIN... COMMIT, o tramite il beginTransaction() ... endTransaction() API)
  2. come già suggerito, utilizzare le API di legare e riciclare oggetti.
  3. Non creare alcun indice fino a dopo questo inserimento di massa è completo.

Inoltre un'occhiata a Faster bulk inserts in sqlite3?

+0

Grazie per questi brevi consigli! Sembrano velocizzare il processo di copia delle voci del database estremamente. – caw

0

ye, le risorse potrebbero avere un limite di dimensioni, quindi se più grande del limite, è possibile tagliare su più file.

e supporto exesql frase più SQL, qui si invia un esempio:

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"); 
      } 
     } 
    } 

sopra esempio richiedono l'INIT_FILE bisogno di ogni linea è una frase SQL.

Inoltre, se il file frasi sql è grande, è possibile creare il database fuori sito di Android (supporto a SQLite per Windows, Linux, in modo da poter creare il database nel vostro sistema operativo, e copiare il file di database per il vostro cartella delle risorse, se grande, è possibile comprimerlo)

quando l'applicazione viene eseguita, è possibile ottenere il file di database dalle risorse, diretto a salvare nella cartella del database dell'applicazione (se lo si comprime, è possibile decomprimere nel database dell'applicazione cartella)

la speranza può aiutarti -):

+0

Grazie per il vostro impegno! execSQL (...) non supporta più di una dichiarazione allo stesso tempo, ma ovviamente la suddivisione della stringa in singole istruzioni funziona. Ma sto cercando un approccio più veloce di questo. E il tuo secondo suggerimento, ovvero creare il database all'esterno di Android e copiarlo, è ciò che ho già descritto nella domanda e cosa non voglio fare. – caw

4

La tua domanda afferma che vuoi il modo più veloce - ma non ti piace come è fatto nell'articolo - non vuoi sostituire manualmente il file DB (anche se potrebbe essere più veloce del riempimento del DB vuoto con interrogazioni).

Ho avuto esattamente gli stessi pensieri, e ho capito che compilare tramite istruzioni SQL e prepopolare può essere la soluzione migliore, ma dipende dal modo in cui si utilizzerà il DB.

Nella mia applicazione ho bisogno di avere circa 2600 righe (con 4 colonne) in DB alla prima esecuzione - sono i dati per il completamento automatico e poche altre cose. Sarà modificato piuttosto raramente (gli utenti possono aggiungere record personalizzati, ma il più delle volte - non ne hanno bisogno) ed è abbastanza grande. Il popolamento da istruzioni SQL richiede non solo molto più tempo, ma più spazio nell'APK (supponendo che memorizzerei i dati al suo interno, in alternativa potrei scaricarlo da Internet).

Questo è il caso molto semplice (l'inserimento "Big" può avvenire solo una volta e solo al primo avvio) e ho deciso di utilizzare il file DB prepopolato per copiare.Certo, potrebbe non essere il modo più bello - ma è più veloce. Voglio che i miei utenti siano in grado di utilizzare l'app il più rapidamente possibile e considerare la velocità come una priorità - e a loro piace davvero. Al contrario, dubito che sarebbero contenti quando l'app rallenterebbe perché pensavo che la soluzione più lenta e migliore fosse effettivamente migliore.

Se invece di 2600 la mia tabella avesse inizialmente ~ 50 righe, andrei con istruzioni SQL, poiché la differenza di velocità e dimensioni non sarebbe così grande.

Devi decidere quale soluzione si adatta meglio al tuo caso. Se si prevedono problemi che potrebbero sorgere dall'utilizzo dell'opzione "db prepopolato", non utilizzarla. Se non sei sicuro di questi problemi, chiedi, fornendo maggiori dettagli su come utilizzerai (e eventualmente aggiornerai) i contenuti del DB. Se non sei del tutto sicuro quale soluzione sarà più veloce, confrontala. E non aver paura di quel metodo di copia del file: può funzionare molto bene, se usato con saggezza.

+0

Grazie per questi pensieri interessanti! Ovviamente, è necessario determinare il caso d'uso prima di impostare il processo di importazione. – caw

2

ho scritto una classe DbUtils simile alla risposta precedente. Fa parte dello strumento ORM greenDAO ed è disponibile su github. La differenza è che cercherà di trovare i limiti delle istruzioni usando una semplice espressione regolare, non solo le terminazioni di riga. Se devi fare affidamento su un file SQL, dubito che ci sia un modo più veloce.

Ma, se è possibile fornire i dati in un altro formato, dovrebbe essere significativamente più veloce rispetto all'utilizzo di uno script SQL. Il trucco è usare un compiled statement. Per ciascuna riga di dati, si associano i valori analizzati all'istruzione e si esegue l'istruzione. E, naturalmente, è necessario farlo all'interno di una transazione. Vorrei raccomandare un semplice formato di file separato da delimitatore (ad esempio CSV) perché può essere analizzato più velocemente di XML o JSON.

Abbiamo fatto un po 'di performance tests per greenDAO. Per i nostri dati di test, abbiamo avuto tassi di inserimento di circa 5000 righe al secondo. E per qualche ragione, lo rate dropped to half with Android 4.0.

+0

Grazie! Il tuo suggerimento (dichiarazioni compilate, dati vincolanti, elenco CSV) è probabilmente l'approccio più efficiente se non vuoi copiare un file di database completo. – caw

0

Ho usato questo metodo. Per prima cosa crea il tuo database sqlite ci sono alcuni programmi che puoi usare Mi piace SqliteBrowser. Quindi copia il tuo file di database nella cartella delle risorse. Quindi è possibile utilizzare questo codice nel costruttore di 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 è qualcosa di simile /data/data/com.mypackage.myapp/databases/

NAME è qualunque sia il nome del database che si sceglie "mydatabase.db"

So che ci sono molti miglioramenti su questo codice ma ha funzionato così bene ed è MOLTO VELOCE. Quindi l'ho lasciato da solo. Come questo potrebbe essere ancora meglio nel metodo onCreate(). Anche controllare se il file esiste ogni volta probabilmente non è il migliore. Ad ogni modo, come ho detto, funziona, è veloce e affidabile.

+0

Grazie! Questo sembra essere molto veloce ma è quello che ho già descritto nella domanda: non voglio copiare il file di database completo, in realtà, perché ci sono diversi svantaggi. – caw

2

Puoi avere la tua torta e mangiarla anche tu. Ecco una soluzione che può sia rispettare l'uso dell'adattatore db che utilizzare anche un semplice (e molto più veloce) processo di copia per un database precompilato.

Sto utilizzando un adattatore db basato su uno degli esempi di Google. Include una classe interna dbHelper() che estende la classe SQLiteOpenHelper() di Android. Il trucco è sovrascrivere il suo metodo onCreate(). Questo metodo viene chiamato solo quando l'helper non riesce a trovare il DB a cui si sta facendo riferimento e deve creare il DB per te. Questo dovrebbe accadere solo la prima volta che viene richiamato su una determinata installazione del dispositivo, che è l'unica volta in cui si desidera copiare il DB. Quindi ignorare come questo -

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

Naturalmente assicurarsi di aver prima dichiarato e inizializzato questo flag nel DBHelper -

 private Boolean mNeedToCopyDb = false; 

Ora, nel metodo aperto del dbAdapter() è possibile verificare per vedere se hai bisogno di copiare il DB. Se lo fai, chiudi l'helper, copia il DB e infine apri un nuovo helper (vedi sotto il codice). Tutti i tentativi futuri di aprire il db utilizzando l'adattatore db troveranno il proprio DB (copiato) e quindi il metodo onCreate() della classe DbHelper interna non verrà chiamato e il flag mNeedToCopyDb rimarrà 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; 
} 

Il posto giusto un po 'di codice per fare la vostra copia del database all'interno della scheda db in un metodo denominato copyDatabase() usati in precedenza. È possibile utilizzare il valore di mDb aggiornato dalla prima istanza di DbHelper (quando ha creato il DB stub) per ottenere il percorso da utilizzare per il flusso di output quando si esegue la copia. Costruisci il tuo flusso di input come questo

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

[Nota: se il file di DB è troppo grande per copiare in un sorso poi basta suddividerlo in un paio di pezzi.]

questo funziona molto veloce e mette tutto il codice di accesso db (inclusa la copia del DB se necessario) nel tuo adattatore db.

+0

Si noti che il codice sopra apre il db con getReadableDatabase(). Se scriverete al db, utilizzate getWriteableDatabase(). Personalmente utilizzo un DB separato per i dati generati dagli utenti e mantieni la lettura di questo database di riferimento copiato. – PaulP

+0

Grazie per il suggerimento di integrare la copia del database nel gestore dato. Ma mancano alcune parti importanti, come il metodo copyDatabase() o la lettura del vecchio percorso. – caw

+0

Ottieni il vecchio percorso dal membro mDb dell'adattatore. Verrà salvato quando il DB viene aperto tramite il metodo dbAdapter.open() (consultare il codice fornito sopra). Basta usarlo nella routine di copia: dbOutputStream = new FileOutputStream (mDb.getPath()); Per quanto riguarda il codice copydatabase(), lo hai già nell'esempio a cui fai riferimento. Basta adattarlo alle tue esigenze. – PaulP

0

Se i dati non sono privati, è sufficiente ospitarli sul sito Web e scaricarli al primo avvio. In questo modo puoi tenerlo aggiornato. Fintanto che ti ricordi di prendere in considerazione la versione dell'app quando la carichi sul tuo webserver.

Problemi correlati