2014-11-14 10 views
19

Ekibim, harici dosya dizinlerindeki yazılabilir moddaki veritabanlarına erişemediği için, uygulamanızın kullanılamaz hale geldiği Nexus 9'da bir hata buldu. Uygulamanın JNI'yi kullanması ve sadece kodda bir arm64-v8a sürümü içermemesi durumunda gerçekleşir.Nexus 9 SQLite dosyası için geçici çözüm Harici yönlerde yazma işlemleri?

Geçerli kuramımız, yalnızca armeabi veya armeabi-v7a kitaplıklarına sahip uygulamalarla geriye dönük olarak uyumlu olması için, arm64-v8a'nın dahil edilmemesi durumunda Nexus 9'un yerel kitaplıkların bazı alternatif sürümlerini içermesidir. Yukarıdaki operasyonları engelleyen alternatif SQLite kütüphanelerinin bazılarında bir hata var gibi görünüyor.

kimse bu sorunla ilgili herhangi bir geçici çözüm buldu? Tüm yerel kitaplıklarımızı arm64'te yeniden oluşturmak, şimdiki izimiz ve en eksiksiz çözümdür, ancak bu bize zaman kazandıracaktır (bazı kütüphanelerimiz haricidır) ve Nexus 9'umuz için uygulamayı düzeltmek için mümkünse daha hızlı bir dönüş yapmayı tercih ederiz. kullanıcılar.


Kolayca bu basit örnek proje (son Android NDK gerekir) bu konuyu görebilirsiniz.

  1. projeye aşağıda dosyaları ekleyin.
  2. Eğer yoksa, en son Android NDK'u yükleyin.
  3. Proje dizinine ndk-build'u çalıştırın.
  4. Yenileyin, kurun, yükleyin ve çalıştırın.
  5. Eğer Android.mk veya Application.mk değiştirmek yeniden ndk-build çalıştırmadan önce kütüphanelerini ve obj klasörleri silerek projeyi temizledikten
  6. . Ayrıca her bir ndk-build'dan sonra projenizi manuel olarak yenilemeniz gerekir. 01221606a

Nexus 9'daki "bozuk" yapı, dahili dosyalar ile birlikte çalıştığını, ancak harici dosyalarla çalışmadığına dikkat edin.

src/com/example/dbtester/DBTesterActivity.java

package com.example.dbtester; 

import java.io.File; 

import android.app.Activity; 
import android.content.ContentValues; 
import android.database.Cursor; 
import android.database.sqlite.SQLiteDatabase; 
import android.os.Bundle; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.Button; 
import android.widget.TextView; 

public class DBTesterActivity extends Activity { 

    protected static final String TABLE_NAME = "table_timestamp"; 

    static { 
     System.loadLibrary("DB_TESTER"); 
    } 

    private File mDbFileExternal; 

    private File mDbFileInternal; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     setContentView(R.layout.dbtester); 

     mDbFileExternal = new File(getExternalFilesDir(null), "tester_ext.db"); 
     mDbFileInternal = new File(getFilesDir(), "tester_int.db"); 

     ((Button)findViewById(R.id.button_e_add)).setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       addNewTimestamp(true); 
      } 
     }); 

     ((Button)findViewById(R.id.button_e_del)).setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       deleteDbFile(true); 
      } 
     }); 

     ((Button)findViewById(R.id.button_i_add)).setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       addNewTimestamp(false); 
      } 
     }); 

     ((Button)findViewById(R.id.button_i_del)).setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       deleteDbFile(false); 
      } 
     }); 

     ((Button)findViewById(R.id.button_display)).setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       setMessageView(getNativeMessage()); 
      } 
     }); 
    } 

    private void addNewTimestamp(boolean external) { 
     long time = System.currentTimeMillis(); 

     File file; 

     if (external) { 
      file = mDbFileExternal; 
     } else { 
      file = mDbFileInternal; 
     } 

     boolean createNewDb = !file.exists(); 

     SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, 
       SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.NO_LOCALIZED_COLLATORS 
         | SQLiteDatabase.OPEN_READWRITE); 

     if (createNewDb) { 
      db.execSQL("CREATE TABLE " + TABLE_NAME + "(TIMESTAMP INT PRIMARY KEY)"); 
     } 

     ContentValues values = new ContentValues(); 
     values.put("TIMESTAMP", time); 
     db.insert(TABLE_NAME, null, values); 

     Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null); 
     setMessageView("Table now has " + cursor.getCount() + " entries." + "\n\n" + "Path: " 
       + file.getAbsolutePath()); 
    } 

    private void deleteDbFile(boolean external) { 
     // workaround for Android bug that sometimes doesn't delete a file 
     // immediately, preventing recreation 

     File file; 

     if (external) { 
      file = mDbFileExternal; 
     } else { 
      file = mDbFileInternal; 
     } 

     // practically guarantee unique filename by using timestamp 
     File to = new File(file.getAbsolutePath() + "." + System.currentTimeMillis()); 

     file.renameTo(to); 
     to.delete(); 

     setMessageView("Table deleted." + "\n\n" + "Path: " + file.getAbsolutePath()); 
    } 

    private void setMessageView(String msg) { 
     ((TextView)findViewById(R.id.text_messages)).setText(msg); 
    } 

    private native String getNativeMessage(); 
} 

res/düzen/dbtester.xml

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:columnCount="1" > 

    <Button 
     android:id="@+id/button_e_add" 
     android:text="Add Timestamp EXT" /> 

    <Button 
     android:id="@+id/button_e_del" 
     android:text="Delete DB File EXT" /> 

    <Button 
     android:id="@+id/button_i_add" 
     android:text="Add Timestamp INT" /> 

    <Button 
     android:id="@+id/button_i_del" 
     android:text="Delete DB File INT" /> 

    <Button 
     android:id="@+id/button_display" 
     android:text="Display Native Message" /> 

    <TextView 
     android:id="@+id/text_messages" 
     android:text="Messages appear here." /> 

</GridLayout> 

jni/Android.mk

LOCAL_PATH := $(call my-dir) 

include $(CLEAR_VARS) 

LOCAL_CFLAGS += -std=c99 
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog 

LOCAL_MODULE := DB_TESTER 
LOCAL_SRC_FILES := test.c 

include $(BUILD_SHARED_LIBRARY) 

jni/Application.mk (BROKEN)

APP_ABI := armeabi-v7a 

JNI'yı/Application.mk ( ÇALIŞMA )

APP_ABI := armeabi-v7a arm64-v8a 

JNI'yı/test.c

#include <jni.h> 

JNIEXPORT jstring JNICALL Java_com_example_dbtester_DBTesterActivity_getNativeMessage 
      (JNIEnv *env, jobject thisObj) { 
    return (*env)->NewStringUTF(env, "Hello from native code!"); 
} 

AndroidManifest.Nexus 9 kırık yapı çalıştırırsanız xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="com.example.dbtester" 
    android:versionCode="10" 
    android:versionName="1.0" > 

    <uses-sdk 
     android:minSdkVersion="16" 
     android:targetSdkVersion="21" /> 

    <application> 
     <activity 
      android:name="com.example.dbtester.DBTesterActivity" 
      android:label="DB Tester" > 
      <intent-filter> 
       <action android:name="android.intent.action.MAIN" /> 

       <category android:name="android.intent.category.LAUNCHER" /> 
      </intent-filter> 
     </activity> 
    </application> 

</manifest> 

, aşağıdaki gibi LogCat içinde SQLiteLog hata mesajlarını göreceksiniz:

 SQLiteLog: (28) file renamed while open: /storage/emulated/0/Android/data/com.example.dbtester/files/tester.db 
SQLiteDatabase: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032) 

* İlginçtir ki, sen veritabanı dosyaları depolamak ise iç dosya dizini, veritabanları yazılabilir modunda erişilebilir ARE. Ancak, bazı büyük veritabanlarımız var ve hepsini iç klasörlere taşımak istemiyor. erişilen

* Dış dosya dizini {sdcard} /Android/data/com.example.dbtester ve Context.getExternalFilesDir (null) ve Context.getExternalCacheDir dahil tüm alt klasörler() klasörler vardır. Bu klasörlere erişmek için Lollipop'ta okuma/yazma izinleri artık gerekli değildir, ancak bu izinleri tamamen açık ve kapalı olarak test ettim.

+1

Harika bir soru. Eğer gün içinde bir cevap alamazsan, bunun üzerine kendi lütfumu koyacağım. – Simon

+3

xbmc/Kodi ile tam olarak aynı sorunu yaşıyoruz. Bilmemiz iyi değil, yalnız değiliz. – Koying

cevap

9

Ben önermek için herhangi bir çözüm bulamadık ama sorunu ayıklamaya ve en az fiili temel nedenini anlamaya başardı.

" vardır

Android'de 32 bit ABI'lerde, ino_t (inode sayılarını döndürmek/depolamak için tasarlanmıştır) veri türü 32 bit, struct stat (dosya için inode numaralarını döndüren) st_ino alanı ise unsigned long long'dur (64 bittir). struct stat, ino_t'da depolandığında kesilen inode sayılarını döndürebileceği anlamına gelir. Normal linux üzerinde, struct stat ve alanındaki st_ino alanı 32 bittir 32 bit modundayken, her ikisi de benzer şekilde kesilir.

Android 32 bit çekirdeği üzerinde çalıştığı sürece, bu, tüm gerçek inode sayıları zaten 32 bit olduğundan, herhangi bir sorun olmamıştır, ancak şimdi 64 bit çekirdeklerde çalışırken, çekirdek, bu inode sayılarını kullanabilir ino_t'a sığdır. Bu, sdcard bölümündeki dosyalarınız için olan şey gibi görünüyor.

sqlite mağazalar (kesildi) ve daha sonra neler istatistik döner (sqlite içinde fileHasMoved fonksiyonunu bakınız) karşılaştırır bir ino_t orijinal inode değeri - Bu burada modu salt okunur aşağılayıcı tetikler şeydir.

Genel olarak sqlite ile aşina değilim; Tek çözüm muhtemelen fileHasMoved'u aramayı denemeyen bir kod yolu bulmak olacaktır.

Ben sorunun iki olası çözümler sundu ve bir hata olarak rapor:

ya düzeltme birleştirilir, ve backported serbest bırakma şubesi ve yakında bir (henüz) firmware güncellemesine dahil edildi.

+0

Sorunu teyit sqlite sorunu çözer. – Koying

+0

Bunun için teşekkürler! Bir yama atılması yakında ortaya çıkıyor. Bu tam olarak bir çözüm olmasa da, bu sorunu açıklığa kavuşturuyor. –

+0

https://android-review.googlesource.com/115351'deki düzeltme birleştirildi (Narayan Kamath tarafından araştırılan bu büyük inode sayılarının neden daha iyi açıklandığıyla ilgili bir açıklama yapıldı). 5.0 serbest bırakma dalına (şimdiye kadar sahip olmadı) kiraz çekilir. – mstorsjo

3

DB açılamaz:

SQLiteDatabase.openOrCreateDatabase(dbFile, null); 
and 
SQLiteDatabase.openDatabase(
    dbFile.getAbsolutePath(), 
    null, 
    SQLiteDatabase.CREATE_IF_NECESSARY); 

DB açılabilir: Sadece belki şu kod işe yarayabilecek

Context.openOrCreateDatabase( 
      dbFile.getAbsolutePath(), 
      Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null); 

(MODE_ENABLE_WRITE_AHEAD_LOGGING bayrağını kullanma).

SQLiteDatabase.openDatabase(
    dbFile.getAbsolutePath(), 
    null, 
    SQLiteDatabase.MODE_ENABLE_WRITE_AHEAD_LOGGING 
    | SQLiteDatabase.CREATE_IF_NECESSARY); 

Bu bayrağı kullandığınızda neden işe yaramadığını anlamadık. * Uygulamamızı armeabi-v7a kütüphaneleri (32bit). Ne yazık ki

+0

Bunun, yukarda yayınladığım örnek uygulama ile çalıştığını doğruladım. Hala "açıkken yeniden adlandırılan dosya" hatalarını gönderir, ancak veritabanlarına yazıyor gibi görünür (ancak bir WAL günlük dosyasındaki girdileri kurtarması gerektiği gibi görünüyor). Ancak, bizim ana uygulama veritabanları çok daha karmaşık bir sistem kullanır. Henüz, tüm veritabanı çağrılarımıza WAL bayrağının eklenmesi sorunlarımızı henüz çözmedi. –

+0

Ah, sadece örnek uygulama ile çalışmak için * belirir *. Ancak tester-ext.db dosyasını bilgisayarınıza çekin ve boş bir DB dosyası göreceksiniz. Yani bu maalesef gerçek bir çözüm değil. –

+0

Ah, şüphesiz, DB dosyamız SQL viewer uygulamasını kullanarak boş görünmektedir. Ancak, Uygulama bu DB dosyasını aç ya da oku, Kayıtlar var ... Bu gizemlidir ... – Tarky