使用RootTools实现对Android系统文件的读写


   

 作者:     

    蒋东国

 时间:

    2016年11月2日 星期三

 应用来源:

    hqt APP(测试机型:华为荣耀4X、三星 Note4/S5)

 博客地址:

     


“前几天客户说,他们部署的一款手机在录制视频的时候不能屏蔽提示音,希望能够解决这个问题。好吧,由于Google禁止开发人员关闭相机使用提示音,除了部分厂商对Android系统进行了重新定制允许用户屏蔽相机提示音,通常情况,只要调用Google官方API都无法屏蔽相机提示音。也就是说,如果我们希望自己的应用能够在使用相机时静音,就必须地绕过官方API,从Framework层去重新实现。由于时间紧迫,就找了些第三方多媒体项目,比如vitamio、ffpeg、美拍SDK等,但这些项目要么就是太大了,要么就是需要授权,而为了这么几部手机感觉有点发不来。突然想到自己以前在帮别人刷机的时候,也老是去操作这些音频文件,因此我就想如果我将与相机相关的系统音频文件删掉会不会就达到我的要求了呢?”

(1)通过RootTools的isRootAvailable()来判断终端是否具有Root权限,代码如下:


public static boolean isDeviceRooted(){
   if(RootTools.isRootAvailable()){
       return true;
    }
    return false;
}

(2) 通过RootTools的remount方法修改文件(如”/system”)的挂载方式,其中mountType的值可为”RO”为只读方式或”RW”为读写方式,代码如下:


public static boolean changeMountType(String filePath,String mountType){
   if(RootTools.remount(filePath, mountType)){
       return true;
    }
    return false;		
}


(3)通过RootTools的restartAndroid方法重启Android系统,但不是重启整个Android设备。因为该方法主要是通过杀死名zygote线程(即init线程)。当zygote线程被杀死后,会自动重启,从而实现系统重启。


public static void restartDevice(){
   try{
       RootTools.restartAndroid();
    }catch(java.util.concurrent.TimeoutException e){
       e.printTrace();
    }
}

(4)RootTools最方便之处就是深度封装了对各种文件(系统/SD卡)的大部分操作,开发者几乎只需一行代码就可以达到对系统文件的操作,比如:

复制文件:copyFile(String src,String des,booleanremountAsRw,boolean fileAttr);

删除文件:deleteFileOrDirectory(String targetFile,booleanremountAsRw);

判断文件是否存在:boolean exist(String file);

 当然,如果你热衷于使用Shell命令,来实现对系统(或SD卡存储)的文件进行相关的操作。RootTools自然也提供了相关的接口和工具类。我们可以通过RootTools的getShell方法获得一个Shell对象,然后通过Shell对象的add(Command cmd)方法执行指定的shell命令。为了方便使用,我将相关方法进行了封装,代码如下:

/**
 *@dscrible 系统文件操作工具类
 *
 * Created by jiangdongguo on 2016-11-2 上午10:48:55
 */
public classSysFileHandleUtils {
       private static final String cmdMountRo = "mount -oremount ro";
       private static final String cmdMountRw = "mount -oremount rw";
       private static final int CMD_ID_FIRST = 1;
 
       private static boolean isDeviceRooted(){
              if(RootTools.isRootAvailable()){
                     return true;
              }
              return false;
       }
             
       public static void mountFileRo(String filePath){
              runShellCmd(cmdMountRo+” ”+filePath, true);
       }
      
       public static void mountFileRw(String filePath){
              runShellCmd(cmdMountRw+” ”+filePath, true);
       }
 
       public static ArrayList<String> runShellCmd(StringcmdStr,final boolean openShellOrNot){     
              final ArrayList<String> output = newArrayList<String>();
              try {      
                     //封装要执行的shell命令,处理最后的结果
                     Command command = newCommand(CMD_ID_FIRST,cmdStr) {         
                            @Override
                            public void commandTerminated(int arg0,String msg) {
                                   Log.d("Debug", "执行结果,错误:"+openShellOrNot+"-->"+msg);
                            }                  
                            @Override
                            public void commandOutput(int arg0,String msg) {
                                   output.add(msg);
                                   Log.d("Debug", "执行结果,成功:"+openShellOrNot+"-->"+msg);
                            }                  
                            @Override
                            public void commandCompleted(int arg0,int arg1) {
                                  
                            }
                     };
                     //获得shell对象,添加shell命令
                     Shell rootShell =RootTools.getShell(openShellOrNot);
                     rootShell.add(command);
                     //检测是否命令执行完成
                     //否则休眠2s等待直到执行完才能执行下一个命令
                     if(!waitForCommand(command)){
                            return null;
                     }
              } catch (IOException e) {
                     e.printStackTrace();
                     return null;
              } catch (TimeoutException e) {
                     e.printStackTrace();
                     return null;
              } catch (RootDeniedException e) {
                     e.printStackTrace();
                     return null;
              }
              return output;
       }
      
       private static boolean waitForCommand(Command cmd) {
              while (!cmd.isFinished()) {
                     synchronized (cmd) {
                            try {
                                   if (!cmd.isFinished()) {
                                          cmd.wait(2000);
                                   }
                            } catch (InterruptedException e) {
                                   e.printStackTrace();
                            }
                     }
                     if (!cmd.isExecuting() &&!cmd.isFinished()) {
                            Log.d("Debug", "错误:命令执行失败");
                            return false;
                     }
              }
              Log.d("Debug", "命令执行成功");
              return true;
       }
}


  接下来,就是如何使用这个工具类。由于我希望能够让相机静音,就打算将以Cam、Video为前缀的所有音频文件移动到上一级目录。在移动之前需要给相关目录,如/system,授予可读写权限,否则会出现” Read-only file system”异常,当然为了系统文件安全,在移动命令执行完后再将/system目录置于只读权限。相关代码如下:


/**
 * @dscrible 通过RootTools操作移动系统音频文件
 * 
 *  Created by jiangdongguo on 2016-11-2 上午10:32:33
 */
public class SysHandleActivity extends Activity {
	private static final String cmdMoveFiles = "mv /system/media/audio/ui/Cam* 
 						/system/media/audio/ui/Video* system/media/audio/";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);	
		
		if(!SysFileHandleUtils.isDeviceRooted()){
			Toast.makeText(SysHandleActivity.this, "无root", Toast.LENGTH_SHORT).show();
			return;
		}
		
		Button handleBtn = (Button)findViewById(R.id.handle_btn);
		handleBtn.setOnClickListener(new OnClickListener() {		
			@Override
			public void onClick(View v) {
				//移动多媒体文件			
				moveMediaFiles();
			}
		});		
	}
	
	private  void moveMediaFiles() {
		new Thread(new Runnable() {				
			@Override
			public void run() {
				SysFileHandleUtils.mountFileRw("/system");
				SysFileHandleUtils.runShellCmd(cmdMoveFiles, true);
				SysFileHandleUtils.mountFileRo("/system");
			}
		}).start();
	}
}

最后,还需要在AndroidMainfest.xml中添加超级用户权限:

<uses-permissionandroid:name="android.permission.ACCESS_SUPERUSER"/>

最后的话:“说句实话,通过删除系统音频文件的方法,来解决相机提示音的问题也是最笨的方法。虽然方便、快捷,但是这需要部署终端必须先”越狱”,且RootTools无法Root Android终端。RootTools确实是一个非常不错第三方包,使用该项目我们可以很轻松的开发具有Root操作权限的应用,类似于Re文件管理器。”