Android开发:解决如何获取setting.db数据库中system表中的全部数据

严景焕
2023-12-01

老样子,我们还是先来了解一下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中是可以写入补救操作的,单纯的打印错误信息是它的低级功能。

好了,本文到这里就结束了,有需要引用本文的地方请标明出处,谢谢!

 类似资料: