最近的需求要求我的APP开放一些属性数据给其他APP访问,但不能允许其他APP修改我的数据。
在选择实现方式时,对比了content provider和sharepreference,其中,content provider的数据以数据表的形式存在,可允许外部应用进行增删改查,而sharepreference的数据以xml键值对的形式存在,可使用不同的MODE对外部应用的读写权限进行控制,写法简单。因此,最后选择使用sharepreference实现这个功能。
开始测试前,附上sharepreference的几种MODE:
- MODE_PRIVATE 指定该sharepreferences数据只能被本应用程序读写
- MODE_WORLD_READABLE 指定该sharepreference数据对其他应用程序可读但不可写
- MODE_WORLD_WRITEABLE 指定该sharepreference数据对其他应用程序可读可写
首先测试这个功能需要两个APP,一个负责写,另一个负责读,我的开发环境是Android studio,简单的建两个module即可。
APP1有四个按钮,下面这段代码是OnClickListener的内容:
switch (v.getId()) {
case R.id.save:
Log.i(TAG, "click btn save");
SharedPreferences sharedPreferences = getSharedPreferences(SP_NAME, MODE_WORLD_READABLE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("key1", "value1");
editor.putString("key2", "value2");
editor.commit();
Log.i(TAG, "save sp, over");
break;
case R.id.read:
Log.i(TAG, "click btn read");
Log.i(TAG, "read sp, mode world readable");
sharedPreferences = getSharedPreferences(SP_NAME, MODE_WORLD_READABLE);
Log.i(TAG, "key1 " + sharedPreferences.getString("key1", "no-key1"));
Log.i(TAG, "key2 " + sharedPreferences.getString("key2", "no-key2"));
Log.i(TAG, "key3 " + sharedPreferences.getString("key3", "no-key3"));
Log.i(TAG, "read sp, over");
break;
case R.id.clear1:
Log.i(TAG, "click btn clear1");
sharedPreferences = getSharedPreferences(SP_NAME, MODE_WORLD_READABLE);
editor = sharedPreferences.edit();
editor.remove("key1");
editor.putString("key2", "");
editor.commit();
Log.i(TAG, "clear 1 over");
break;
case R.id.clear2:
Log.i(TAG, "click btn clear1");
sharedPreferences = getSharedPreferences(SP_NAME, MODE_WORLD_READABLE);
editor = sharedPreferences.edit();
editor.clear();
editor.commit();
Log.i(TAG, "clear 2 over");
break;
default:
break;
}
APP2有一个按钮,只做读取APP1给出内容的操作,OnClickListener内容如下:
Context context = null;
switch (v.getId()) {
case R.id.read:
try {
context = createPackageContext("com.point.sharepreferencessource", Context.CONTEXT_IGNORE_SECURITY);//com.point.sharepreferencessource是APP1的包名
Log.i(TAG,"sp context create over");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (context != null) {
SharedPreferences sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_WORLD_READABLE);
Log.i(TAG, "read sp, mode world readable");
Log.i(TAG, "key1 " + sharedPreferences.getString("key1", "no-key1"));
Log.i(TAG, "key2 " + sharedPreferences.getString("key2", "no-key2"));
Log.i(TAG, "key3 " + sharedPreferences.getString("key3", "no-key3"));
Log.i(TAG, "read sp, over");
}
break;
default:
break;
}
这种方式不需要在manifest中添加任何权限。
代码中变量SP_NAME在APP1和APP2中必须相同。TAG可自定义,方便logcat过滤。
APP2中访问APP1的数据,需要知道APP1中存储sharepreference的文件名、key值。实际应用中需要给外部APP提供的接口文档应当写出这些内容。
sharepreference删除单个数据使用edit.remove(key);
的方法,删除全部数据可以使用edit.clear();
,而使用edit.putString(key,"");
会将这个key的value设置为空串,而不是删除。
在APP2中读取的时候发现,下一次读取需要彻底退出APP2,才能正确的读出APP1最新的数据,但APP1可以常驻后台不杀。这里确认每次点击事件中try-catch代码片都重新执行了,但不退出而是放在后台的话,读出的数据是过时的,原因未知。实际对我的需求影响不大,没有继续查。有大神了解原因的请评论,万分感谢。
另外,需要注意的一点是,sharepreference一个文件只能指定一个MODE,如果同时对一个文件进行过private访问和world readable访问,那么这个文件将被private。测试中在APP1里对同一个sharepreference文件名get过这两种之后,在APP2里将会读取不到这个sharepreference文件里的数据,报错为Attempt to read preferences file /data/data/com.***.xml without permission
,试图访问一个没有读取权限的文件。
好像写完了。
顺便发现了switch case的一个东西。
在APP1中那段switch case代码中,只有第一个case里面声明了SharePreference sharePreference = ***
,在第二个case里面不能再声明,而要直接使用sharePreference = ***
这个变量,认为switch在进行条件的比对时会把到匹配的case之前的代码片中声明过的变量全部声明一遍。这种写法理论上不规范,但是实际上可行。