SettingsProvider简单分析

本文详细介绍了Android系统中SettingsProvider的工作原理,包括其数据分类、存储方式及与SystemProperties的区别,探讨了SettingsProvider在Android6.0后的重构,以及数据如何从settings.db迁移到XML文件。同时,文章还分析了SettingsProvider的启动过程、核心代码和接口封装,最后讲解了如何操作SettingsProvider进行数据查询和插入。

SettingsProvider顾名思义是一个提供数据共享的Provider,SettingsProvider和Android系统其它Provider有很多不一样的地方:

1.SettingsProvider只接受int float String等基本类型的数据;

2.SettingsProvider由Android系统frameowrk进行了封装

3.SettingsProvider的数据由键值对组成

SettingsProvider有点类似Android的properties系统(Android属性系统):SystemProperites。SystemProperites除具有SettingsProvider以上的三个特性,SettingsProvider和SystemProperties的不同点在于:

1、数据保存方式不同:SystemProperies的数据保存属性文件中(/system/build.prop等),开机后会被加载到system properites store;SettingsProvider的数据保存在文件/data/system/users/0/settings_***.xml和数据库settings.db中;

2、作用范围不同:SettingsProvider可以实现跨进程、跨层次调用,即底层的c/c++可调用,java层也可以调用;SettingsProvider只能在java层调用

3、公开程度不同:SettingsProvider有部分功能上层第三方APP可以使用,SystemProvider上层第三方APP不可以使用。

在Android 6.0版本时,SettingsProvider被重构,Android从性能、安全等方面考虑,把SettingsProvider中原本保存在settings.db中的数据,目前全部保存在XML文件中。

数据分类

SettingsProvider主要有三种类型数据:Global、System、Secure三种类型

Global:所有的偏好设置对系统的所有用户公开,第三方有读没有写的权限

System:包含各种各样的用户偏好系统设置

Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限

 Android6.0版本之后SettingsProvider管理的用户偏好设置数据从原来的settings.db数据库文件中转移到下面的3个xml文件中:data/system/users/0/settings_global.xml
data/system/users/userid/settings_system.xml
data/system/users/userid/settings_secure.xml

主要源码

SettingsProvider的代码数量不多,主要包含如下的java文件:

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java

frameworks/base/core/java/android/provider/Settings.java

AndroidManifest.xml配置

SettingsProvider的AndroidManifest.xml文件对应和ContentProvider的配置如下:

<manifest ......
        android:sharedUserId="android.uid.system">

    <application android:allowClearUserData="false"
                 android:label="@string/app_label"
                 android:process="system"
                 ......
                 android:directBootAware="true">

        <provider android:name="SettingsProvider"
                  android:authorities="settings"
                  android:multiprocess="false"
                  android:exported="true"
                  android:singleUser="true"
                  android:initOrder="100" />
    </application>
</manifest>

上面的Manifest配置由sharedUserId可知,SettingsProvider运行在系统进程中,定义的ContentProvider实现类是SettingsProvider,Uri凭证是settings。

SettingsProvider的启动过程

系统启动SettingsProvider是在frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices() {
	//省略一部分代码
	//...
	
	traceBeginAndSlog("InstallSystemProviders");
	mActivityManagerService.installSystemProviders();
	// Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
	SQLiteCompatibilityWalFlags.reset();
	traceEnd();
	
	//省略一部分代码
	//...
}

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public final void installSystemProviders() {
	// 1、获取系统中所有的Provider,最终通过调用包管理PackageManagerService中的
//queryContentProviders()方法来查询所有的Provider
    List<ProviderInfo> providers;
    synchronized (this) {
        ProcessRecord app = mProcessNames.get("system", SYSTEM_UID);
        providers = generateApplicationProvidersLocked(app);
        if (providers != null) {
            for (int i=providers.size()-1; i>=0; i--) {
                ProviderInfo pi = (ProviderInfo)providers.get(i);
                if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
                    Slog.w(TAG, "Not installing system proc provider " + pi.name
                            + ": not system .apk");
                    providers.remove(i);
                }
            }
        }
    }
	// 2、调用ActivityThread中的installSystemProviders来完成Provider的安装,包括
//SettingsProvider
    if (providers != null) {
        mSystemThread.installSystemProviders(providers);
    }

    synchronized (this) {
        mSystemProvidersInstalled = true;
    }

    mConstants.start(mContext.getContentResolver());
    mCoreSettingsObserver = new CoreSettingsObserver(this);
    mFontScaleSettingObserver = new FontScaleSettingObserver();
    mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
    GlobalSettingsToPropertiesMapper.start(mContext.getContentResolver());

	// 3、Provider启动完成
    // Now that the settings provider is published we can consider sending
    // in a rescue party.
    RescueParty.onSettingsProviderPublished(mContext);

    //mUsageStatsService.monitorPackages();
}

其中第二步中,installSystemProviders()会启动SettingsProvider。启动SettingsProvider即运行SettingsProvider,首先调用OnCreate()方法。

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java

@Override
public boolean onCreate() {
    Settings.setInSystemServer();

    // fail to boot if there're any backed up settings that don't have a non-null validator
    ensureAllBackedUpSystemSettingsHaveValidators();
    ensureAllBackedUpGlobalSettingsHaveValidators();
    ensureAllBackedUpSecureSettingsHaveValidators();

    synchronized (mLock) {
        mUserManager = UserManager.get(getContext());
        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
        mPackageManager = AppGlobals.getPackageManager();
        mHandlerThread = new HandlerThread(LOG_TAG,
                Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mSettingsRegistry = new SettingsRegistry();
    }
    mHandler.post(() -> {
        //注册广播接收,关心设备用户变化以及APP卸载的广播
        registerBroadcastReceivers();
        startWatchingUserRestrictionChanges();
    });
    ServiceManager.addService("settings", new SettingsService(this));
    return true;
}

重点代码分析,上述代码中首先实例化一个HandlerThread的实例mHandlerThread,优先级是Process.THREAD_PRIORITY_BACKGROUND。关键的部分是:mSettingsRegistry = new SettingsRegistry()。

SettingsRegistry是SettingsProvider.java的内部类,先分析构造方法

    public SettingsRegistry() {
        mHandler = new MyHandler(getContext().getMainLooper());
        // 1、对xml的修改做版本管理
        mGenerationRegistry = new GenerationRegistry(mLock);
        // 2、创建Backup,做系统备份
        mBackupManager = new BackupManager(getContext());
        // 3、迁移所有的系统设置数据
        migrateAllLegacySettingsIfNeeded();
        syncSsaidTableOnStart();
    }

migrateAllLegacySettingsIfNeeded()方法,从命名上是迁移settings数据。

private void migrateAllLegacySettingsIfNeeded() {
            synchronized (mLock) {
                final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
                File globalFile = getSettingsFile(key);
                if (SettingsState.stateFileExists(globalFile)) {
                    return;
                }

                mSettingsCreationBuildId = Build.ID;

                final long identity = Binder.clearCallingIdentity();
                try {
                    //获取系统中所有的用户(多用户,一般user0)
                    List<UserInfo> users = mUserManager.getUsers(true);

                    final int userCount = users.size();
                    for (int i = 0; i < userCount; i++) {
                        final int userId = users.get(i).id;
                        //关键代码,通过DatabaseHelper类创建数据库settings.db,并使用默认设置 
                        //对数据库表数据初始化
                        DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
                        SQLiteDatabase database = dbHelper.getWritableDatabase();
                        migrateLegacySettingsForUserLocked(dbHelper, database, userId);
                        //关键代码,生成和初始化xml文件
                        // Upgrade to the latest version.
                        UpgradeController upgrader = new UpgradeController(userId);
                        upgrader.upgradeIfNeededLocked();

                        // Drop from memory if not a running user.
                        if (!mUserManager.isUserRunning(new UserHandle(userId))) {
                            removeUserStateLocked(userId, false);
                        }
                    }
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
            }
        }

上面的代码首先是调用了makeKey()方法,所谓makeKey()就是和上文中的数据分类小章节中提到的System、Global和Secure三种key。然后调用getSettingsFile()方法获取到一个File对象的实例,如下:

        private File getSettingsFile(int key) {
            if (isGlobalSettingsKey(key)) {
                final int userId = getUserIdFromKey(key);
                return new File(Environment.getUserSystemDirectory(userId),
                        SETTINGS_FILE_GLOBAL);
            } else if (isSystemSettingsKey(key)) {
                final int userId = getUserIdFromKey(key);
                return new File(Environment.getUserSystemDirectory(userId),
                        SETTINGS_FILE_SYSTEM);
            } else if (isSecureSettingsKey(key)) {
                final int userId = getUserIdFromKey(key);
                return new File(Environment.getUserSystemDirectory(userId),
                        SETTINGS_FILE_SECURE);
            } else if (isSsaidSettingsKey(key)) {
                final int userId = getUserIdFromKey(key);
                return new File(Environment.getUserSystemDirectory(userId),
                        SETTINGS_FILE_SSAID);
            } else {
                throw new IllegalArgumentException("Invalid settings key:" + key);
            }
        }

上面的代码中对Global、System、Secure分别生成一个File对象实例,它们的File对象分别对应的文件是:

  • /data/system/users/0/settings_global.xml
  • /data/system/users/0/settings_system.xml
  • /data/system/users/0/settings_secure.xml
  • 在8.0以后,每位使用者所安装的每个 APP (package) 都会产生一组独立的 ID (SSAID),并将描述档案放置 “/data/system/users/0/settings_ssaid.xml"。

在migrateAllLegacySettingsIfNeeded()方法,实例化一股DatabaseHelper,DatabaseHelper是SQLiteOpenHelper的子类。

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java

class DatabaseHelper extends SQLiteOpenHelper {

	private static final String DATABASE_NAME = "settings.db";

	public DatabaseHelper(Context context, int userHandle) {
      super(context, dbNameForUser(userHandle), null, DATABASE_VERSION);
      mContext = context;
      mUserHandle = userHandle;
  	}

	static String dbNameForUser(final int userHandle) {
     // The owner gets the unadorned db name;
     if (userHandle == UserHandle.USER_SYSTEM) {
         return DATABASE_NAME;
     } else {
         // Place the database in the user-specific data tree so that it's
         // cleaned up automatically when the user is deleted.
         File databaseFile = new File(
                 Environment.getUserSystemDirectory(userHandle), DATABASE_NAME);
         // If databaseFile doesn't exist, database can be kept in memory. It's safe because the
         // database will be migrated and disposed of immediately after onCreate finishes
         if (!databaseFile.exists()) {
             Log.i(TAG, "No previous database file exists - running in in-memory mode");
             return null;
         }
         return databaseFile.getPath();
     }
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
	   //创建System数据表
	   db.execSQL("CREATE TABLE system (" +
	               "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
	               "name TEXT UNIQUE ON CONFLICT REPLACE," +
	               "value TEXT" +
	               ");");
	   db.execSQL("CREATE INDEX systemIndex1 ON system (name);");
	
		// 1、创建表,createSecureTable,Secure数据表
	   createSecureTable(db);
	
	   // 2、创建表,createGlobalTable,Global数据表
	   // Only create the global table for the singleton 'owner/system' user
	   if (mUserHandle == UserHandle.USER_SYSTEM) {
	       createGlobalTable(db);
	   }
	
	   db.execSQL("CREATE TABLE bluetooth_devices (" +
	               "_id INTEGER PRIMARY KEY," +
	               "name TEXT," +
	               "addr TEXT," +
	               "channel INTEGER," +
	               "type INTEGER" +
	               ");");
	
	   db.execSQL("CREATE TABLE bookmarks (" +
	               "_id INTEGER PRIMARY KEY," +
	               "title TEXT," +
	               "folder TEXT," +
	               "intent TEXT," +
	               "shortcut INTEGER," +
	               "ordering INTEGER" +
	               ");");
	
	   db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);");
	   db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);");
	
	   // Populate bookmarks table with initial bookmarks
	   boolean onlyCore = false;
	   try {
	       onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService(
	               "package")).isOnlyCoreApps();
	   } catch (RemoteException e) {
	   }
	   if (!onlyCore) {
	       loadBookmarks(db);
	   }
	
		// 3、默认音量数据填充数据库
	   // Load initial volume levels into DB
	   loadVolumeLevels(db);
	
		// 4、默认数据填充数据库
	   // Load inital settings values
	   loadSettings(db);
	}
}

loadSettings()这个方法和loadVolumeLevels()方法类似,都是加载很多默认值写入到数据库中,这些默认值很大一部分被定义在文件frameworks/base/packages/SettingsProvider/res/values/defaults.xml中,也有一些来自其它地方。总之,loadVolumeLevels()和loadSettings()的作用就是在手机第一次启动时,把手机编好设置的默认值写入到数据库settings.db中。

Settings.db数据迁移到xml中

继续回到SettingsRegistry中的migrateAllLegacySettingsIfNeeded方法,在migrateAllLegacySettingsIfNeeded中接下来会执行migrateLegacySettingsForUserLocked方法

private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) {
     //1、处理System数据
     // Move over the system settings.
     final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); 
     //生成对应的SettingsState对象保存在mSettingsStates中,生成setting_system.xml
     ensureSettingsStateLocked(systemKey);
     SettingsState systemSettings = mSettingsStates.get(systemKey);
     //数据迁移,将settings.db的数据迁移到settingsStates.get(systemKey);
     migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
     systemSettings.persistSyncLocked();
     
     //2、处理Secure数据
     // Move over the secure settings.
     // Do this after System settings, since this is the first thing we check when deciding
     // to skip over migration from db to xml for a secondary user.
     final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
     //生成对应的SettingsState对象保存在mSettingsStates中,生成settings_secure.xml
     ensureSettingsStateLocked(secureKey);
     SettingsState secureSettings = mSettingsStates.get(secureKey);
     migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
     //数据迁移,将settings.db的数据迁移到settings_secure.xml中
     ensureSecureSettingAndroidIdSetLocked(secureSettings);
     secureSettings.persistSyncLocked();  

     //3、处理Global数据   
     // Move over the global settings if owner.
     // Do this last, since this is the first thing we check when deciding
     // to skip over migration from db to xml for owner user.
     if (userId == UserHandle.USER_SYSTEM) {
         final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);
         //生成对应的SettingsState对象保存在mSettingsStates中,生成settings_global.xml
         ensureSettingsStateLocked(globalKey);
         SettingsState globalSettings = mSettingsStates.get(globalKey);
         //数据迁移,将settings.db的数据迁移到settings_global.xml中
         migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
         // If this was just created
         if (mSettingsCreationBuildId != null) {
             globalSettings.insertSettingLocked(Settings.Global.DATABASE_CREATION_BUILDID,
                     mSettingsCreationBuildId, null, true,
                     SettingsState.SYSTEM_PACKAGE_NAME);
         }
         globalSettings.persistSyncLocked();
     }
     //数据迁移完成后,是否删除settings.db,或者做一个备份
     // Drop the database as now all is moved and persisted.
     if (DROP_DATABASE_ON_MIGRATION) {
         dbHelper.dropDatabase();
     } else {
         dbHelper.backupDatabase();
     }
  }

ensureSettingsStateLocked是一个非常重要的方法,实例化一个SettingsState对象,
这个对象指向文件data/system/users/0/xx.xml文件,然后将settingsState放置在对象mSettingsStates中。如果是工程版本的系统,吧数据库settings.db重命名为settings.db-backup,如果是非工程版本的系统,把数据库文件删除,也会删除日志settings.db-journal.

SettnigsProvider启动时会创建settings.db数据库,然后把所有的默认设置项写入到数据库,接着会把数据库中所有的设置项从数据库转移到xml文件中,随后便会对数据库执行删除操作。为什么会有这么一个过程,这些过程是否可以移除创建数据库这一步?其实这个过程是为了兼容之前的版本而设计,在SettingsProvider被Android重构后,SettingsProvider中数据库相关的代码Android已经停止更新。

  //ensureSettingsStateLocked是一个重要的方法,给每一个数据类型key,
  //创建一个对应的SettingsState对象并保存在mSettingsStates
  private void ensureSettingsStateLocked(int key) {
      if (mSettingsStates.get(key) == null) {
          final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
          SettingsState settingsState = new SettingsState(getContext(), mLock,
                  getSettingsFile(key), key, maxBytesPerPackage, mHandlerThread.getLooper());
          mSettingsStates.put(key, settingsState);
      }
  }

之后会执行migrateLegacySettingsLocked方法

private void migrateLegacySettingsLocked(SettingsState settingsState,
        SQLiteDatabase database, String table) {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(table);

    Cursor cursor = queryBuilder.query(database, ALL_COLUMNS,
            null, null, null, null, null);

    try {
        ......

        while (!cursor.isAfterLast()) {
            String name = cursor.getString(nameColumnIdx);
            String value = cursor.getString(valueColumnIdx);
            settingsState.insertSettingLocked(name, value,
                    SettingsState.SYSTEM_PACKAGE_NAME);
            cursor.moveToNext();
        }
    } finally {
        cursor.close();
    }
}

上面这个方法,查询数据库中System所有的设置,然后在while循环中把每个值的信息作为insertSettingLocked()的参数。

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java

public boolean insertSettingLocked(String name, String value, String packageName) {

    Setting oldState = mSettings.get(name);
    String oldValue = (oldState != null) ? oldState.value : null;

    if (oldState != null) {
        ......
    } else {
        Setting state = new Setting(name, value, packageName);
        mSettings.put(name, state);
    }
    ......
}

上面的方法把每一个设置项封装到Setting中,接着把state放置到ArrayMap<String,Setting>的实例mSettings中。

那么,从方法ensureSettingsStateLocked()到insertSettingsLocked()方法,这个过程表明,有一个对象SettingsState,指向文件/data/system/users/0/settings_system.xml,持有变量mSettings,而mSettings持有封装了设置项的name,value,packageName的对象Setting,也就是settings_system.xml文件中的所有设置项间接被SettingsState持有。

在migrateLegacySettingsForUserLocked中,migrateLegacySettingsLocked方法执行完毕后,调用systemSettings.persistSyncLocked(),systemSettings是SettingsState的实例,代表的是settings_system.xml的所有设置项,persistSyncLocked()方法就是把systemSettings持有的所有的设置项从内存中固化到文件settings_system.xml中。

封装SettingsProvider接口

SettingsProvider是向整个Android系统提供用户偏好设置的程序,framework有一个类Settings.java对使用SettingsProvider进行了封装。

frameworks/base/core/java/android/provider/Settings.java

public final class Settings {
    public static final String AUTHORITY = "settings";
    public static final class Global extends NameValueTable {
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
        ......
    }
    
    public static final class Secure extends NameValueTable {
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/secure");
        ......
    }
    
    public static final class System extends NameValueTable {
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/system");
        ......
    }
    
    private static class NameValueCache {
        private final Uri mUri;
        private final HashMap<String, String> mValues = new HashMap<String, String>();
        public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
            ......
        }
        public boolean putStringForUser(ContentResolver cr, String name, String value,
                final int userHandle) {
            ......
        }
        private IContentProvider lazyGetProvider(ContentResolver cr) {
            IContentProvider cp = null;
            synchronized (NameValueCache.this) {
                cp = mContentProvider;
                if (cp == null) {
                    cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
                }
            }
            return cp;
        }
}

上面的代码中,分别声明了Global、Secure、System三个静态内部类,分别对应SettingsProvider中的Global、Secure、System三种数据类型。Global、Secure、System三个静态内部类会分别持有自己NameValueCache的实例变量,每个NameValueCache持有指向SettingsProvider中的SettingsProvider.java的AIDL远程调用IContentProvider,读者可以阅读《Android System Server大纲之ContentService和ContentProvider原理剖析》了解ConatentProvider的这个过程。因此,查询数据需要经过NameValueCache的getStringForUser()方法,插入数据需要经过putStringForUser()方法。同时,NameValueCache还持有一个变量mValues,用于保存查询过的设置项,以便下下次再次发起查询时,能够快速返回。

操作SettingsProvider

由于Settings.java对使用SettingsProvider进行了封装,所以,使用起来相当简单简洁。由于Global、Secure、System三种数据类型的使用是几乎相同,所以本文就只以Global为例对查询插入数据的过程进行分析。

查询数据

从SettingsProvider的Global中查询数据,查询是否是飞行模式使用方法如下:

String globalValue = Settings.Global.getString(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON);

分析一下getString()方法

frameworks/base/core/java/android/provider/Settings.java

public static String getString(ContentResolver resolver, String name) {
    return getStringForUser(resolver, name, UserHandle.myUserId());
}

/** @hide */
public static String getStringForUser(ContentResolver resolver, String name,
        int userHandle) {
    //因为在Android系统的更新中,保存在Global、Secure、System三种类型的数据存放位置有变化
    //需要添加判断兼容老版本的使用方法。
    if (MOVED_TO_SECURE.contains(name)) {
        Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
                + " to android.provider.Settings.Secure, returning read-only value.");
        return Secure.getStringForUser(resolver, name, userHandle);
    }
    return sNameValueCache.getStringForUser(resolver, name, userHandle);
}

NameValueCache.getStringForUser()方法,如下

public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
    final boolean isSelf = (userHandle == UserHandle.myUserId());
    if (isSelf) {
        ......
            } else if (mValues.containsKey(name)) {
                return mValues.get(name);
        ......
    IContentProvider cp = lazyGetProvider(cr);

    // Try the fast path first, not using query().  If this
    // fails (alternate Settings provider that doesn't support
    // this interface?) then we fall back to the query/table
    // interface.
    if (mCallGetCommand != null) {
        try {
            ......
            Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
            if (b != null) {
                String value = b.getString(Settings.NameValueTable.VALUE);
                ......
                        mValues.put(name, value);
                    }
                return value;
            }
        } catch (RemoteException e) {
            // Not supported by the remote side?  Fall through
            // to query().
        }
    }
    Cursor c = null;
    try {
        c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
                     new String[]{name}, null, null);
        String value = c.moveToNext() ? c.getString(0) : null;
        synchronized (NameValueCache.this) {
            mValues.put(name, value);
        }
        return value;
        ......
     if (c != null) c.close();
    }
}
}

NameValueCache是Settings.java中的内部类,

首先从缓存mValues变量中去找,如果没有查询到,就调用SettingsProvider的call()接口,如果call()接口也没有查询到,再调用query()接口。这里用的是call()接口,下文就以call()接口往下分析。cp.call()会调用到SettingsProvider的call()方法,读者可以阅读《Android System Server大纲之ContentService和ContentProvider原理剖析》了解ConatentProvider的这个过程。注意参数mCallGetCommand。

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java

public Bundle call(String method, String name, Bundle args) {
    final int requestingUserId = getRequestingUserId(args);
    switch (method) {
        case Settings.CALL_METHOD_GET_GLOBAL: {
            Setting setting = getGlobalSetting(name);
            return packageValueForCallResult(setting, isTrackingGeneration(args));
        }

        case Settings.CALL_METHOD_GET_SECURE: {
            Setting setting = getSecureSetting(name, requestingUserId);
            return packageValueForCallResult(setting, isTrackingGeneration(args));
        }
        ......
    }

    return null;
}

上层传过来的参数的command是mCallGetCommand,即Settings.CALL_METHOD_GET_GLOBAL,所以调用getGlobalSettings(),getGlobalSetting()返回的Setting setting是封装了设置项name、value等信息的,查看getGlobalSetting()方法:

private Setting getGlobalSetting(String name) {
    // Get the value.
    synchronized (mLock) {
        return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
                UserHandle.USER_SYSTEM, name);
    }
}

通过mSettingsRegistry.getSettingLocked()继续寻找:

public Setting getSettingLocked(int type, int userId, String name) {
    final int key = makeKey(type, userId);

    SettingsState settingsState = peekSettingsStateLocked(key);
    return settingsState.getSettingLocked(name);
}

通过peekSettingsStateLocked(key)寻找SettingsState:

private SettingsState peekSettingsStateLocked(int key) {
    SettingsState settingsState = mSettingsStates.get(key);
    if (settingsState != null) {
        return settingsState;
    }

    ensureSettingsForUserLocked(getUserIdFromKey(key));
    return mSettingsStates.get(key);
}

获取到SettingsState settingsState,回到getSettingLocked(),调用settingsState.getSettingLocked(name):

public Setting getSettingLocked(String name) {
    if (TextUtils.isEmpty(name)) {
        return mNullSetting;
    }
    Setting setting = mSettings.get(name);
    if (setting != null) {
        return new Setting(setting);
    }
    return mNullSetting;
}

到getSettingsLocked()最终获取到Setting setting。

插入数据

从SettingsProvider的Global中插入数据,插入飞行模式的使用方法如下:

boolean isSuccess = Settings.System.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);

和查询代码一样,代码非常简洁,整个查询过程几乎类似

frameworks/base/core/java/android/provider/Settings.java

public static boolean putInt(ContentResolver cr, String name, int value) {
    return putString(cr, name, Integer.toString(value));
}
public static boolean putString(ContentResolver resolver,
        String name, String value) {
    return putStringForUser(resolver, name, value, UserHandle.myUserId());
}
public static boolean putStringForUser(ContentResolver resolver,
        String name, String value, int userHandle) {
    ......
    return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}

和查询一样,继续看putStringForUser():

public boolean putStringForUser(ContentResolver cr, String name, String value,
        final int userHandle) {
    try {
        Bundle arg = new Bundle();
        arg.putString(Settings.NameValueTable.VALUE, value);
        arg.putInt(CALL_METHOD_USER_KEY, userHandle);
        IContentProvider cp = lazyGetProvider(cr);
        cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
    } catch (RemoteException e) {
        Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
        return false;
    }
    return true;
}

frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java

public Bundle call(String method, String name, Bundle args) {
    final int requestingUserId = getRequestingUserId(args);
    switch (method) {
        ......

        case Settings.CALL_METHOD_PUT_GLOBAL: {
            String value = getSettingValue(args);
            insertGlobalSetting(name, value, requestingUserId, false);
            break;
        }

        case Settings.CALL_METHOD_PUT_SECURE: {
            String value = getSettingValue(args);
            insertSecureSetting(name, value, requestingUserId, false);
            break;
        }

        ......
    }

    return null;
}

首先调用getSettingValue(args)获取对应的设置项,接着insertGlobalSetting()方法:

private boolean insertGlobalSetting(String name, String value, int requestingUserId,
        boolean forceNotify) {
    return mutateGlobalSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
            forceNotify);
}

直接调用了mutateGlobalSetting()方法:

private boolean mutateGlobalSetting(String name, String value, int requestingUserId,
        int operation, boolean forceNotify) {
    // Make sure the caller can change the settings - treated as secure.
    enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);

    // If this is a setting that is currently restricted for this user, do not allow
    // unrestricting changes.
    if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId, value)) {
        return false;
    }

    // Perform the mutation.
    synchronized (mLock) {
        switch (operation) {
            case MUTATION_OPERATION_INSERT: {
                return mSettingsRegistry
                        .insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM,
                                name, value, getCallingPackage(), forceNotify);
            }

            ......
        }
    }
    return false;
}

首先进行权限检查,然后调用mSettingsRegistry.insertSettingLocked()方法:

public boolean insertSettingLocked(int type, int userId, String name, String value,
        String packageName, boolean forceNotify) {
    final int key = makeKey(type, userId);

    SettingsState settingsState = peekSettingsStateLocked(key);
    final boolean success = settingsState.insertSettingLocked(name, value, packageName);

    if (forceNotify || success) {
        notifyForSettingsChange(key, name);
    }
    return success;
}

新增数据保存到数据库

以新增一个"intercept_back"来获取back键是否被禁用,数据类型是 System integer类型。

定义变量:

frameworks/base/core/java/android/provider/Settings.java

public static final class System extends NameValueTable {
..
...
		/**
		*Enable / disable back key interface
		*0 = enable
		*1 = disable
		*@hide
		*/
		public static final String INTERCEPT_BACK = "intercept_back";
        public static final String[] SETTINGS_TO_BACKUP = {
            STAY_ON_WHILE_PLUGGED_IN,   // moved to global
            WIFI_USE_STATIC_IP,
            WIFI_STATIC_IP,
            WIFI_STATIC_GATEWAY,
            WIFI_STATIC_NETMASK,
            ......
            SHOW_BATTERY_PERCENT,
			INTERCEPT_BACK  //add by wangjin in 2019/01/09
        };

}

注意:

在这一步一定要加/** @hide */(一定是/** */格式),不然编译会报以下错误:

Checking API: checkpublicapi-current
out/target/common/obj/PACKAGING/public_api.txt:20: error 5: Added public field android.Manifest.permission.BACKUP
out/target/common/obj/PACKAGING/public_api.txt:82: error 5: Added public field android.Manifest.permission.INVOKE_CARRIER_SETUP
out/target/common/obj/PACKAGING/public_api.txt:106: error 5: Added public field android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
out/target/common/obj/PACKAGING/public_api.txt:116: error 5: Added public field android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
 
******************************
You have tried to change the API from what has been previously approved.
 
To make these errors go away, you have two choices:
   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
      errors above.
 
   2) You can update current.txt by executing the following command:
         make update-api
 
      To submit the revised current.txt to the main Android repository,
      you will need approval.

设置默认值

vendor/mediatek/proprietary/packages/apps/SettingsProvider/res/values/defaults.xml  (mtk)

<integer name="intercept_back">0</integer>

加载该值

private void loadSystemSettings(SQLiteDatabase db) {
   loadIntegerSetting(stmt,Settings.System.INTERCEPT_BACK, R.integer.intercept_back);
}

 

<think>我们正在处理一个Android开发中与设置存储相关的警告问题。警告信息是:"SettingsProvider warning: You shouldn't keep your settings in the secure settings. This will soon become an error"。这意味着在未来的版本中,当前的做法将会导致错误。 根据引用[1]和[2]的内容,这个异常是在Android M(6.0)及更高版本中,当应用尝试使用`Settings.System`存储本属于安全设置(secure settings)的数据时抛出的。在Android中,设置分为三种类型:`System`、`Secure`和`Global`。其中: - `System`:用于存储应用程序的偏好设置,这些设置不会同步,且在恢复出厂设置时会被清除。 - `Secure`:用于存储设备的安全相关设置,这些设置只有系统应用或具有特定权限的应用才能修改。 - `Global`:用于存储全局设置(通常与硬件相关)。 警告信息表明,开发者试图将应该存储在系统设置(System)中的数据存储到了安全设置(Secure)中,而这是不被允许的。在未来的版本中,这种行为将会被阻止并抛出错误。 ### 解决方案 1. **检查设置存储位置**:确认你的设置数据是否确实需要存储在安全设置中。通常,只有涉及设备安全性的设置(如默认锁屏、默认输入法等)才应该存储在安全设置中。应用级别的设置应该使用`Settings.System`或应用自己的SharedPreferences。 2. **迁移到正确的存储位置**: - 如果是应用级别的设置,应该使用`SharedPreferences`来存储。 - 如果是系统级别的设置(需要跨应用共享),并且不需要安全级别的保护,则使用`Settings.System`。 - 如果设置确实是安全相关的,并且你的应用拥有相应的权限(如系统应用或拥有签名权限),则可以继续使用`Settings.Secure`,但需要确保你的应用有资格这样做。 3. **修改代码**: - 将原本使用`Settings.Secure`存储的应用设置改为使用`SharedPreferences`或`Settings.System`(如果设置需要系统级共享)。 例如,原本使用`Settings.Secure`的代码: ```java // 不安全的做法(将普通设置存储在安全设置中) Settings.Secure.putInt(getContentResolver(), "my_setting", 1); ``` 应改为使用`SharedPreferences`(推荐用于应用私有设置): ```java SharedPreferences prefs = getSharedPreferences("my_prefs", Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt("my_setting", 1); editor.apply(); ``` 或者,如果设置需要被其他应用读取(系统级设置),则使用`Settings.System`(注意:需要声明`WRITE_SETTINGS`权限): ```java // 在AndroidManifest.xml中添加权限 // <uses-permission android:name="android.permission.WRITE_SETTINGS"/> // 注意:在Android 6.0及以上,WRITE_SETTINGS权限属于危险权限,需要动态申请 if (Settings.System.canWrite(context)) { Settings.System.putInt(getContentResolver(), "my_setting", 1); } else { // 请求用户授权 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); intent.setData(Uri.parse("package:" + context.getPackageName())); startActivity(intent); } ``` 4. **处理兼容性**: - 如果你的应用需要兼容旧版本(Android M之前),则需要在运行时判断系统版本,在旧版本上使用`Settings.Secure`,而在新版本上使用正确的存储方式(但这种情况很少见,因为通常应用设置不应该存储在系统设置中,更不应该在安全设置中)。 ### 注意事项 - 在Android中,`Settings.System`和`Settings.Secure`的数据库是分开的。系统设置表是`system`,安全设置表是`secure`。 - 从Android M开始,Google加强了设置存储的管理,不允许将普通设置放入安全设置中,因为安全设置需要更高的权限才能修改。 - 使用`SharedPreferences`是最简单且推荐的方式存储应用私有设置,除非设置需要被其他应用访问(这时才考虑使用`Settings.System`,并注意权限问题)。 ### 引用说明 根据引用[1]和[2],这个异常是由于在Android M及更高版本中,将普通设置存储到安全设置中导致的。因此,我们需要将设置迁移到正确的位置[^1][^2]。 §§相关问题§§ 1. Android中系统设置(System)和安全设置(Secure)有什么区别? 2. 如何正确使用SharedPreferences存储应用设置? 3. Android中WRITE_SETTINGS权限的申请流程是怎样的? 4. 如何为不同Android版本实现设置存储的兼容性?</think>### Android SettingsProvider 警告 "You shouldn't keep your settings in the secure settings" 解决方案 #### 问题分析 该警告发生在Android 6.0(Marshmallow)及更高版本中,当应用尝试将普通应用设置存储在安全设置(Secure Settings)区域时触发[^1][^2]。根本原因是Android系统对设置存储进行了严格分区: 1. **设置存储分区**: - `System Settings`:普通应用设置,通过`Settings.System`访问 - `Secure Settings`:设备安全相关设置,通过`Settings.Secure`访问 - `Global Settings`:系统级全局设置 2. **违规操作**: ```java // 错误示例:将普通设置存入安全区域 Settings.Secure.putString(getContentResolver(), "my_app_setting", "value"); ``` 3. **风险后果**: - Android 12+ 已将此警告升级为错误 - 可能导致设置无法保存或读取 - 应用在Android 12+设备上崩溃 --- ### 完整解决方案 #### 1. 迁移到正确存储位置 ```java // 迁移前(错误) Settings.Secure.putString(getContentResolver(), "theme_preference", "dark"); // 迁移后(正确) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 使用系统设置区域 Settings.System.putString(getContentResolver(), "theme_preference", "dark"); } else { // 旧版本回退方案 SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); prefs.edit().putString("theme_preference", "dark").apply(); } ``` #### 2. 使用SharedPreferences(推荐) ```java // 在Application类中初始化 public class MyApp extends Application { private static SharedPreferences sPrefs; @Override public void onCreate() { super.onCreate(); sPrefs = getSharedPreferences("app_settings", MODE_PRIVATE); } public static void saveSetting(String key, String value) { sPrefs.edit().putString(key, value).apply(); } } ``` #### 3. 安全设置的特殊处理 若确实需要访问安全设置(需系统权限): ```xml <!-- AndroidManifest.xml 添加权限 --> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/> ``` ```java // 检查权限 if (Settings.Secure.canWrite(context)) { Settings.Secure.putString(resolver, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, value); } else { // 引导用户到系统设置页 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); intent.setData(Uri.parse("package:" + context.getPackageName())); startActivity(intent); } ``` #### 4. 数据库迁移脚本 ```java public void migrateOldSettings(Context context) { ContentResolver resolver = context.getContentResolver(); // 从Secure读取旧设置 String oldValue = Settings.Secure.getString(resolver, "old_setting_key"); if (oldValue != null) { // 迁移到System区域 Settings.System.putString(resolver, "new_setting_key", oldValue); // 清除旧设置 Settings.Secure.putString(resolver, "old_setting_key", null); } } ``` #### 5. 兼容旧版本Android ```java public static void putAppSetting(ContentResolver resolver, String key, String value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Settings.System.putString(resolver, key, value); } else { // Android 5.1及以下使用兼容模式 try { Settings.System.putString(resolver, key, value); } catch (SecurityException e) { // 回退到SharedPreferences context.getSharedPreferences("legacy_prefs", 0) .edit().putString(key, value).apply(); } } } ``` --- ### 验证与测试方法 1. **日志检查**: ```bash adb logcat | grep -E 'SettingsProvider|Settings' ``` 2. **设置项验证**: ```bash # 检查系统设置 adb shell settings get system [your_setting_key] # 检查安全设置(需root) adb shell settings get secure [your_setting_key] ``` 3. **兼容性测试矩阵**: | Android版本 | 测试方案 | |------------|----------| | 5.0-5.1 (Lollipop) | 使用System设置 + SharedPreferences回退 | | 6.0-11 (Marshmallow+) | 严格使用System设置 | | 12+ (Snow Cone) | 完全禁用Secure设置存储 | --- ### 预防措施 1. **代码规范检查**: ```gradle // build.gradle 添加Lint检查 android { lintOptions { warning "SecureSettings" error "SecureSettings" } } ``` 2. **架构设计原则**: ```mermaid graph TD A[应用设置] --> B{需要系统级共享?} B -->|是| C[Settings.System] B -->|否| D[SharedPreferences] C --> E{涉及设备安全?} E -->|是| F[申请 WRITE_SECURE_SETTINGS 权限] E -->|否| G[直接使用 Settings.System] ``` 3. **持续监控**: ```java // 在Application中检测违规使用 public void onContentChanged() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (isUsingSecureSettings()) { reportToAnalytics("SecureSettings_Usage"); } } } ``` > **关键提示**:Android 12+ 已将此警告变为强制错误,建议在2023年底前完成所有迁移工作[^1][^2]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值