天天看點

Android開發:解決如何擷取setting.db資料庫中system表中的全部資料

老樣子,我們還是先來了解一下settings.db資料庫中system表存放的是什麼。

從字面意思我們就可以看出,settings.system為系統資料庫,裡面存放的資料大多為系統的一些配置資料,包括還有一些應用存放的其他資料資訊。

你也可以在項目中把自己的資料存放在系統資料庫内,這樣就可以達到資料永久存放的目的,即使你的應用被解除安裝了,這些資料依然不會被删除。大多數我們采用Settings.System類中的get方法來擷取相對應Key下的資料,具體的代碼如下:

Settings.System.getString(getContentResolver(),"volurme_music");
           

例如這裡我擷取的是name值為:“volurme_music”下的資料。如果你想擷取的是你自己自定義的資料,那麼這裡的name參數就寫你設定的name值。

這裡我們就可以看出一個問題,使用get方法我們一次隻能擷取一個資料,并且前提我們必須知道對應的name值才能擷取。如果此時有一個業務邏輯,要求你擷取system表中所有的資料,以便可以快速比對到一部分特殊name批量修改他們的對應資料,這下你該怎麼辦??

你很快就會想到去看Settings.System類中的相關方法,看看有沒有傳回全部資料的方法,很遺憾并沒有。你有想着去看get的源碼,很遺憾你也是一無所獲。這下徹底懵逼了!怎麼辦?

我們這裡還是拿RE檔案管理器來當例子。

通過RE檔案管理器我們可以打開并檢視system表,system表中每一條資料都羅列了出來,很顯然RE檔案管理器肯定不是通過Settings.System類的get方法一個個擷取的,因為它連裡面都有什麼name值都不知道!但是它确實擷取了system表的全部資料并且展示了出來,這說明,肯定存在一種方法可以擷取system表中的全部資料,隻不過不是使用正常的get方法擷取,還是要巧妙的來。

思路分析:

system為settings.db資料庫中的一張表,安卓中的資料庫為SQLite,既然system是資料庫中的表,那麼我們可不可以通過SQLite的查詢語句來查詢system表呢?

答案是肯定可以的!是資料庫就可以使用查詢語句!

OK,思路來了,我們着手就做,我們的目标是可以查詢system資料表。

立刻第一個困難就來了。首先我們明确知道,settings.db資料的路徑為

/data/data/com.android.providers.settings/databases/settings.db
           

這是又是一個底層的系統資料庫,我們開發處在應用層,沒辦法去通路這個資料庫。這下怎麼辦?

看過我上一篇部落格的小夥伴此時估計就已經有思路了,沒有看過的小夥伴可以去看一下,畢竟在這篇部落格中也會講到上篇部落格中的知識點,上篇部落格的連結為:作業系統檔案目錄/system

很顯然答案是:複制資料庫到我們應用層可以觸及的區域。

同樣還是adb指令的操作,在做有關系統底層的開發工作,adb指令将會是你經常使用到的指令。

我們這裡使用adb指令把系統資料庫settings.db複制到我們項目目錄下,具體路徑為:

/data/data/com.example.test/databases
           

其中/data/data/..../databases中間寫你自己項目的名,我在這裡是com.example.test,databases檔案夾是項目的資料庫檔案夾,項目有關的資料庫都會存放在這個檔案夾裡并讀取,是以我們需要把settings.db資料庫複制到該檔案夾下。

當然這些adb指令都是在代碼中執行的,執行代碼為:

public static void sqliteSystem(){

        exusecmd("mount -o rw,remount /data/data/com.android.providers.settings/databases");
        exusecmd("cd /data/data/com.android.providers.settings/databases/");
        exusecmd("chmod 777 settings.db");

        exusecmd("cp /data/data/com.android.providers.settings/databases/settings.db /data/data/com.example.est/databases");
    }
           

具體的adb指令翻譯執行代碼不再展示,有需要的請看我上一篇部落格。

這裡我們主要看一下adb指令。首先是修改了/data/data/com.android.providers.settings/databases檔案的操作權限,改為了可讀寫,然後cd指令進入到/data/data/com.android.providers.settings/databases檔案夾目錄下,settings.db資料庫就在此目錄下。接着我們給settings.db資料庫檔案r管理者權限,下一步就可以開心的執行複制指令了,把/data/data/com.android.providers.settings/databases目錄下的settings.db資料庫檔案複制到/data/data/com.example.est/databases目錄下,下面我們就可以執行資料庫查詢語句,代碼為:

MyDatabaseHelper myDatabaseHelper=new MyDatabaseHelper(this,"settings.db",null,1);
        SQLiteDatabase db=myDatabaseHelper.getWritableDatabase();
        Cursor cursor=db.query("system",null,null,null,null,null,null);
        if (cursor.moveToFirst()){
            do {
                
                int id=cursor.getInt(cursor.getColumnIndex("_id"));
                String name=cursor.getString(cursor.getColumnIndex("name"));
                String value=cursor.getString(cursor.getColumnIndex("value"));

                Log.i("id+name+value+++",""+id+"    "+name+"    "+value);

            }while (cursor.moveToNext());
        }

        cursor.close();
           

運作一下,結果運作出錯!

趕快去看一看RE檔案管理器中檔案,我們發現原來是複制出錯了....

首先說一下,MyDatabaseHelper 類是一個我自己自定義的類,繼承自SQLiteOpenHelper,在這個自定義類裡面我沒有寫入任何操作,隻是單純的繼承一下。

下面我們開始找錯誤,通過RE檔案管理器我們可以看出,我們沒有把settings.db資料庫成功的複制到/data/data/com.example.est/databases目錄下,原因就是因為在我們開始執行複制指令的時候,/data/data/com.example.est/目錄下還沒有databases這個檔案夾!是以複制失敗!

那麼這個databases檔案夾到底是怎麼被建立出來的呢?我們知道資料庫檔案都是被放在/databases檔案夾下面,是以它的出現隻和資料庫被建立有關,當我們執行代碼:

MyDatabaseHelper myDatabaseHelper=new MyDatabaseHelper(this,"settings.db",null,1);
        SQLiteDatabase db=myDatabaseHelper.getWritableDatabase();
           

在上面兩句代碼中,程式會去系統緩存檔案目錄下查找/databases檔案夾中名字為settings.db的資料庫,當然如果不存在/databases檔案夾就會建立一個,然後檔案夾中再建立一個名字為settings.db的資料庫。如果你在自定義的類MyDatabaseHelper 中的onCreate()方法執行了建立資料庫表的SQL語句,那麼在這裡程式就會接着在資料庫settings.db中再建立一張表出來。可是我在onCreate()的方法裡什麼都沒有做,程式自然也不會去建立一張新的資料表,也就是說settings.db資料庫中是空的,自然在執行下面查找表的時候會出錯,報錯資訊為找不到名字為“system”的資料表!

既然知道了bug所在,那麼我們接着就去修改我們的代碼,解決bug!

解決的關鍵就在于我們一定要在/databases檔案夾被建立出來的時候,才能執行我們的adb指令去複制資料庫檔案。

那麼我們該怎麼去寫這個算法呢?

我這裡給出一個可行的解決方式,當然你們也可以嘗試更好的解決方法。

我這裡采取的是try。

具體代碼:

try {
          
           MyDatabaseHelper myDatabaseHelper=new MyDatabaseHelper(this,"settings.db",null,1);
           
           SQLiteDatabase db=myDatabaseHelper.getWritableDatabase();
           
           Cursor cursor=db.query("system",null,null,null,null,null,null);
           
       }catch (Exception e){
           e.printStackTrace();

           RootCmd.sqliteSystem();

       }
           

 我們平時開發經常性見到try和catch關鍵字,這是為了避免程式崩潰而使用。我們基本都用來做調試工作,再找Bug的時候,在可能出錯的代碼加上try和catch關鍵字,然後列印出相應的報錯資訊以友善我們對代碼進一步改進。平常開發守則中也是教導我們,在catch中盡量不要寫入大量的邏輯算法,畢竟會很影響性能。但是執行一點小小的操作還是可以的,比如在這裡我們寫入了一句adb指令,執行檔案複制操作。

以上代碼中,程式走到Cursor cursor=db.query("system",null,null,null,null,null,null);會報錯,報錯資訊為找不到“system”資料表,那麼此時我們就可以确定/databases檔案夾已經被建立了出來,同時settings.db資料庫也被建立了。那麼我們就可以放心的大膽的去執行檔案複制指令,把系統檔案目錄下的settings.db資料庫複制到我們自己的項目目錄下。

運作一下,我們再次通過RE檔案管理器去看看,發現複制成功!偷梁換柱~

好了,接下來我們就可以讀取“system”表了,因為它已經存在于我們的項目目錄下。

在這裡,執行的代碼還是有講究!畢竟這個settings.db資料庫我們是搬過來的,沒有經過正八經的代碼操作建立,是以讀取這種複制過來的資料還是要格外注意,部落客也是嘗試了好多次,才終于找到了正确讀取方法,具體讀取代碼為:

SQLiteDatabase database = SQLiteDatabase.openDatabase("/data/data/com.example.test/databases/settings.db", null, SQLiteDatabase.OPEN_READONLY);
        
        Cursor cursor=database.query("system",null,null,null,null,null,null);
        
        if (cursor.moveToFirst()){
            do {
                int id=cursor.getInt(cursor.getColumnIndex("_id"));
                String name=cursor.getString(cursor.getColumnIndex("name"));
                String value=cursor.getString(cursor.getColumnIndex("value"));

                Log.i("id+name+value+++",""+id+"    "+name+"    "+value);
            }while (cursor.moveToNext());
        }

        cursor.close();
           

這裡我們和平常的資料庫讀取有所不同,首先我們調用SQLiteDatabase類的openDatabase()方法擷取一個SQLiteDatabase 的執行個體,在openDatabase()方法中需要注意,我們這裡填入資料庫的全路徑,這裡我填入的是/data/data/com.example.test/databases/settings.db,後面的兩個參數固定。

然後我們就可以使用這個SQLiteDatabase的執行個體去執行一個SQLite的查詢語句,讀出資料後執行周遊就可以了!

這樣我們擷取系統資料庫中system表中全部資料成功!

在這裡我的講解是一步一步進行的,具體的項目開發過程中,讀取system表中所有資料要求一口氣全部執行下來,是以隻需要把以上的這些代碼連貫起來那就可以了。

關于catch中執行adb指令,這裡在給大家一個更好的解決方案:

在catch中你可以把adb指令替換别的操作,例如我們在這裡開啟一個線程,讓這個線程去執行adb指令,或者開啟一個背景服務service來執行adb指令,然後執行完畢後再調用資料庫讀取操作,這樣效率會更高,也更加的安全。

千萬不要以為catch中不能寫操作,隻能列印錯誤資訊,try-catch被建立出來是為了使程式有了補救的可能性,不至于一遇到錯誤就崩掉。是以catch中是可以寫入補救操作的,單純的列印錯誤資訊是它的低級功能。

好了,本文到這裡就結束了,有需要引用本文的地方請标明出處,謝謝!