我们的目的是修改Android手机中某app下的配置文件(至于为什么要修改这个配置文件呢? 你猜呀!)
即 /data/data/{package_name}/shared_prefs/{xxx}.xml文件
附:
{package_name} 为某App的包名
{xxx}为某个配置文件
修改步骤:
1. 你得把手机root
2. 下载Root Explorer,定位到上面那个路径下修改即可。
(完了, xxx 不要打偶! T_T……)
实际上,由于手机屏幕略小,如果需要经常的修改文件,在软键盘上敲下一长串无意义的文件,非常非常滴不方便的说。
即使,像哥一样聪明的知道,通过把资料才电脑发送到手机QQ上,再复制到剪贴板,然后粘贴到Root Explorer打开的配置文件里,省去了手动敲的步骤了。
但是,但是每次用食指或大拇指小心翼翼的定位光标还是十分痛苦的说。如果在修改的部分附近,即使多删去一些字符也无妨,拷贝之后,再手动打回来,也比拼人品刚好定位到好。
另外一种同步文字到手机上的方法:浏览器打开notepad.cc/xxx, 把文字拷贝进去,然后用Android手机的浏览器访问这个路径即可。
后缀xxx自定义,尽量独特一些好记一些,毕竟这也是要手敲的。。。
第二种方法, 既然配置文件是XML文件,自己去写个App程序解析不就可以了吗。
对头,虽然Java有好几种方法解析XML文件的方法,但是哥懒呀!(别打头。。。)
第三种方法,看好,要放大招了。
因为Android SDK提供了读写自家App内置配置文件的方法,好像是getSharedPreferences({xxx}, MODE_PRIVATE),
那么可不可以利用这个方法呢??
为了安全,SDK并未提供可以直接访问其他App的配置文件的方法(CreatePackageContext 和 shareUserId 使用颇多限制),
所以我们可以这样做:
把要修改的配置文件拷贝到咱自己写的App的配置文件目录下,修改后再拷贝回去。 :)
当然前提是必须先root,(葵花宝典里不是说,欲练此功,必先自宫……)root不是阉割,而像开启了潜能,获得了强大力量,控制不好可能会误伤而已。
但是(“但是”,就意味着有问题呀)
其1,注意修改的时候最好把原App先退出,不然可能不会成功的。
举个例子:
假如App启动的时候调用了getSharedPreferences方法读取了配置文件,然后咱用Root Explorer去手动修改了这个配置文件的内容,如果App之后有写入配置文件的操作,会覆盖咱修改的内容的。
原因是SharedPreferences第一次读配置文件的时候,有个缓存在内存中,之后不会再去读这个配置文件的内容了,即使手动修改文件内容也不会反应到内存中。(纯属个人推测)
但是其2 注意文件权限,特别是所有者权限,读写执行权限倒无所谓。
不同的App安装时获得临时的不同的userId,如果拷贝之后,不是当前用户的所有者,用getSharedPreferences方法保存之后实际会生成一个新的文件,并不包含原来拷贝过来的文件的内容。
所以,实际步骤是:
拷贝原文件过来,然后修改目标文件的所有者为目标userId,修改内容,完毕后,再将文件拷贝回去,同样要把权限修改回去。
操作是用Java调用系统(Linux)命令来实现的。
关键代码如下:
拷贝文件的方法:
cp -f src srcBak 先拷贝一个临时文件
chown destUid:destUid srcBak 修改临时文件的所有者为目标文件的所有者, uid和gid默认是一样的
cp -pf srcBak dest 直接覆盖目标文件
至于uid的获取:使用Utils.getFileUid(),将配置文件所在的目录(/data/data/{package_name}/shared_prefs)传进去就可以获得了。
private boolean copy(String src, String dest, String destUid) {
if(!new File(src).exists()){
return false;
}
String srcBak = src + "_bak";
List<String> cmdList = new ArrayList<>();
cmdList.add("cp -f " + src + " " + srcBak);
cmdList.add("chown " + destUid +":" + destUid +" " + srcBak);
cmdList.add("cp -pf " + srcBak + " " + dest);
if(Utils.runSu(cmdList, null)){
return true;
}
return false;
}
public class Utils {
private static final String TAG = "Utils";
private static Context mContext;
public static final String DEFAULT_SU = "/system/xbin/su";
public static boolean isInitialized = false;
/**
* execute a common system command
*
* @param cmd
*/
public static void run(String cmd) {
Runtime runtime = Runtime.getRuntime();
Process process = null;
try {
process = runtime.exec(cmd);
process.waitFor();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Get a result from inputstream
*
* @param in
* @return
*/
private static String getResult(InputStream in) {
if (in == null) {
return "";
}
byte[] bytes = new byte[1024];
StringBuffer sb = new StringBuffer();
try {
while (in.read(bytes) != -1) {
sb.append(new String(bytes));
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
return "";
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Execute a system command with root permission
*
* @param cmdList
*/
public static boolean runSu(List<String> cmdList, CmdResult result) {
return runSu(cmdList, DEFAULT_SU, result);
}
public static boolean runSu(List<String> cmdList, String suPath, CmdResult result) {
File suFile = new File(suPath);
if (!suFile.exists()) {
showToast("Have your phone been rooted?\nSu not exits: " + suPath, Toast.LENGTH_LONG);
Log.w(TAG, "su not exists: " + suPath);
return false;
}
if (!suFile.canExecute()) {
Log.w(TAG, "su can not be executed: " + suPath);
return false;
}
if (cmdList == null || cmdList.size() == 0) {
return false;
}
Runtime runtime = Runtime.getRuntime();
Process process = null;
long startStamp = System.currentTimeMillis();
try {
process = runtime.exec(suPath);
DataOutputStream os = new DataOutputStream(process.getOutputStream());
for (String cmd : cmdList) {
if (!cmd.endsWith("\n")) {
cmd += "\n";
}
os.writeBytes(cmd);
Log.d(TAG, "su cmd: " + cmd);
}
os.writeBytes("exit\n");
os.flush();
os.close();
InputStream in = process.getInputStream();
in = (in!=null) ? in : process.getErrorStream();
if (result!=null) {
result.retStr = getResult(in);
result.ret = true;
Log.d(TAG, "su result: " + result.retStr);
}
if(process.waitFor() == 0){
return true;
}else {
// cost too much time, check su
if(System.currentTimeMillis() - startStamp > 3*1000) {
if(mContext != null) {
Toast.makeText(mContext, "Have your phone been rooted?", Toast.LENGTH_LONG).show();
}
}
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
/**
* Get a file owner's id
* @param path
* @return
*/
public static String getFileUid(String path) {
File file = new File(path);
if (!file.exists()) {
return "";
}
List<String> cmdList = new ArrayList<String>();
if(file.isDirectory()){
cmdList.add("ls -ld " + path);
}else {
cmdList.add("ls -l " + path);
}
CmdResult cmdResult = new CmdResult();
if( runSu(cmdList, cmdResult) ){
if (cmdResult.ret) {
String result = cmdResult.retStr;
String[] resArr = result.split(" ");
if (resArr.length > 1 && !"".equals(resArr[1])) {
return resArr[1];
}
}
}
return "";
}
public static void showToast(String text , int duration) {
if(mContext != null){
Toast.makeText(mContext, text, duration).show();
}
}
}
public class CmdResult {
public boolean ret;
public String retStr;
}
Over!