Delphi编写的Android程序获取Root权限实现(2015.4.15更新,支持Android 4.4)

借助谷歌,并经过本大侠施展坑、蒙、拐、骗、偷五大绝技,终于成功实现在Delphi下获取Root权限并将其扩展为一个完整功能更加完整的TQAndroidShell记录,在华为荣耀2(Android 4.2)、Nubia Z5S(Android 4.4)测试通过。参考代码如下:

unit QAndroid.Shell;

interface

{
  本源码来自QDAC项目,版权归swish(QQ:109867294)所有。
  (1)、使用许可及限制
  您可以自由复制、分发、修改本源码,但您的修改应该反馈给作者,并允许作者在必要时,
  合并到本项目中以供使用,合并后的源码同样遵循QDAC版权声明限制。
  您的产品的关于中,应包含以下的版本声明:
  本产品使用的QAndroidShell来自QDAC项目,版权归作者所有。
  (2)、技术支持
  有技术问题,您可以加入QDAC官方QQ群250530692共同探讨。
  (3)、赞助
  您可以自由使用本源码而不需要支付任何费用。如果您觉得本源码对您有帮助,您可以赞
  助本项目(非强制),以使作者不为生活所迫,有更多的精力为您呈现更好的作品:
  赞助方式:
  支付宝: guansonghuan@sina.com 姓名:管耸寰
  建设银行:
  户名:管耸寰
  账号:4367 4209 4324 0179 731
  开户行:建设银行长春团风储蓄所
}
{
  本单元实现了一个伪的Shell,可以用于在Android程序中执行Shell命令,如果需要Root权限,
  则在执行前,调用AskForRoot方法来获取用户的Root授权,如果不再需要在Root账号下执行
  命令,则调用ExitRoot方法来退出Root账号返回普通账号模式。
  1、调用TQAndroidShell.Initialize方法来初始化当前实例
  AShell.Initialize;
  2、如果需要Root权限执行某些命令,调用TQAndroidShell.AskForRoot获得Root权限,否则忽略此步骤
  if AShell.AskForRoot then
  begin
  ...
  end
  else //进入失败,手机未Root或者用户拒绝给予Root权限
  ...;
  3、调用Execute方法来执行命令行并获得返回的结果
  AShell.Execute('ls /proc -l');
  4、如果要切换回普通账号模式,调用AShell.ExitRoot来返回当前普通账号模式
  【注意】TQAndroidShell是一个记录,不需要手工释放(除非你是New生成的)

}

uses System.SysUtils, System.Diagnostics, Androidapi.Jni,
  Androidapi.JNIBridge,
  Androidapi.Jni.GraphicsContentViewText,
  Androidapi.Jni.JavaTypes,
  Androidapi.Helpers,
  FMX.Helpers.Android, FMX.Forms, FMX.Dialogs, qstring;

type
  PQAndroidShell = ^TQAndroidShell;

  TQAndroidShell = record
  private
    FRuntime: JObject;
    FProcess: JObject;
    FInRoot: Boolean;
    function GetIsRooted: Boolean;
    function InternalReadReply(AProcess: JObject; ATimeout: Cardinal): QStringW;
    /// <summary>发送一个命令行</summary>
    /// <param name="ACmdline">要发送的命令行内容</param>
    /// <returns>成功,返回true,失败,返回false</returns>
    /// <remarks>仅用于Root模式下</remarks>
    function SendCmd(const ACmdline: QStringW): Boolean;
    function ReadReply(ATimeout: Cardinal = INFINITE): QStringW;
  public
    /// <summary>初始化函数,用于初始化一个TQAndroidShell实例</summary>
    /// <returns>返回当前记录的指针</returns>
    function Initliaize: PQAndroidShell;
    /// <summary>请求Root权限以进入Root账号准备执行后续的命令</summary>
    /// <returns>成功,返回True,失败,返回False</returns>
    /// <remarks>如果失败,一般有两种可能:
    /// 1、设备未Root,此时IsRooted属性为False;
    /// 2、用户拒绝授权
    /// </remarks>
    function AskForRoot: Boolean;
    /// <summary>执行指定的命令行并等待返回</summary>
    /// <param name="ACmdline">命令行内容</param>
    /// <param name="ATimeout">等待命令执行前等待的时间,单位为毫秒</param>
    /// <returns>返回命令行的输出结果</returns>
    function Execute(const ACmdline: QStringW; ATimeout: Cardinal = INFINITE)
      : QStringW;
    /// <summary>退出Root模式</summary>
    /// <remarks>如果未处于Root模式,什么也不会发生</remarks>
    procedure ExitRoot;
    /// <summary>当前是否处于Root模式</summary>
    property InRoot: Boolean read FInRoot;
    /// <summary>当前设备是否已经Root过了</summary>
    property IsRooted: Boolean read GetIsRooted;
  end;

implementation

type
  JProcess = interface;
  JRuntime = interface;

  // ----------------------------------JProcess----------------------
  JProcessClass = interface(JObjectClass)
    ['{7BFD2CCB-89B6-4382-A00B-A7B5BB0BC7C9}']

  end;

  [JavaSignature('java/lang/Process')]
  JProcess = interface(JObject)
    ['{476414FD-570F-4EDF-B678-A2FE459EA6EB}']
    { Methods }
    procedure destroy; cdecl;
    function exitValue: integer; cdecl;
    function getErrorStream: JInputStream; cdecl;
    function getInputStream: JInputStream; cdecl;
    function getOutputStream: JOutputStream; cdecl;
    function waitFor: integer; cdecl;
  end;

  TJProcess = class(TJavaGenericImport<JProcessClass, JProcess>)
  end;

  // ----------------------------------Jruntime----------------------
  JRuntimeClass = interface(JObjectClass)
    ['{3F2E949D-E97C-4AD8-B5B9-19CB0A6A29F3}']
    { costant }
  end;

  [JavaSignature('java/lang/Runtime')]
  JRuntime = interface(JObject)
    ['{C097A7EC-677B-4BCB-A4BD-7227160750A5}']
    { Methods }
    procedure addShutdownHook(hook: JThread); cdecl;
    function availableProcessors: integer; cdecl;
    function exec(progArray, envp: array of JString): JProcess; overload; cdecl;
    function exec(progArray: JString; envp: array of JString; directory: JFile)
      : JProcess; overload; cdecl;
    function exec(progArray, envp: array of JString; directory: JFile)
      : JProcess; overload; cdecl;
    function exec(prog: JString; envp: array of JString): JProcess; cdecl;
      overload; cdecl;
    function exec(progArray: array of JString): JProcess; overload; cdecl;
    function exec(prog: JString): JProcess; cdecl; overload; cdecl;
    procedure Exit(code: integer); cdecl;
    function freeMemory: LongInt; cdecl;
    procedure gc; cdecl;
    function getLocalizedInputStream(stream: JInputStream): JInputStream; cdecl;
    function getLocalizedOutputStream(stream: JOutputStream)
      : JOutputStream; cdecl;
    function getRuntime: JRuntime; cdecl;
    procedure halt(code: integer); cdecl;
    procedure load(pathName: JString); cdecl;
    procedure loadLibrary(libName: JString); cdecl;
    function maxMemory: LongInt; cdecl;
    function RemoveShutdownHook(hook: JThread): Boolean; cdecl;
    procedure runFinalization; cdecl;
    procedure runFinalizersOnExit(run: Boolean); cdecl;
    function totalMemory: LongInt; cdecl;
    procedure traceInstructions(enable: Boolean); cdecl;
    procedure traceMethodCalls(enable: Boolean); cdecl;
  end;

  TJRuntime = class(TJavaGenericImport<JRuntimeClass, JRuntime>)
  end;

  { TQAndroidShell }

function TQAndroidShell.AskForRoot: Boolean;
begin
Result := InRoot;
if not Result then
  begin
  Result := IsRooted;
  if not Assigned(FProcess) then
    begin
    Result := False;
    if IsRooted then
      begin
      FProcess := (FRuntime as JRuntime).exec(StringToJString('su'));
      if Assigned(FProcess) then
        begin
        // 通过检查当前账号来判断下自己是否成功获取root权限
        if SendCmd('id -nu') then
          begin
          FInRoot := StrStrW(PQCharW(ReadReply), 'root') <> nil;
          Result := FInRoot;
          end;
        end;
      if not Result then
        FProcess := nil;
      end;
    end;
  end;
end;

function TQAndroidShell.Initliaize: PQAndroidShell;
begin
FRuntime := TJRuntime.Create;
FProcess := nil;
FInRoot := False;
Result := @Self;
end;

function TQAndroidShell.Execute(const ACmdline: QStringW; ATimeout: Cardinal)
  : QStringW;
var
  AProcess: JProcess;
begin
SetLength(Result, 0);
if InRoot then
  begin
  if SendCmd(ACmdline) then
    Result := ReadReply(ATimeout);
  end
else
  begin
  AProcess := (FRuntime as JRuntime).exec(StringToJString(ACmdline));
  if Assigned(AProcess) then
    Result := InternalReadReply(AProcess, ATimeout);
  end;
end;

procedure TQAndroidShell.ExitRoot;
begin
if InRoot then
  begin
  if SendCmd('exit') then
    begin
    (FProcess as JProcess).waitFor;
    FProcess := nil;
    FInRoot := False;
    end;
  end;
end;

function TQAndroidShell.GetIsRooted: Boolean;
begin
Result := FileExists('/system/bin/su') or FileExists('/system/xbin/su');
end;

function TQAndroidShell.InternalReadReply(AProcess: JObject; ATimeout: Cardinal)
  : QStringW;
var
  AError, AInput, AResultStream: JInputStream;
  AWatch: TStopWatch;
  ABuf: TJavaArray<Byte>;
begin
AError := (AProcess as JProcess).getErrorStream;
AInput := (AProcess as JProcess).getInputStream;
AWatch := TStopWatch.StartNew;
AResultStream := nil;
repeat
  if AInput.available > 0 then
    AResultStream := AInput
  else if AError.available > 0 then
    AResultStream := AError;
until (AWatch.ElapsedMilliseconds > ATimeout) or (AResultStream <> nil);
if Assigned(AResultStream) then
  begin
  ABuf := TJavaArray<Byte>.Create(AResultStream.available);
  try
    AResultStream.read(ABuf);
    Result := qstring.Utf8Decode(PQCharA(ABuf.Data), ABuf.Length);
  finally
    FreeAndNil(ABuf);
  end;
  end
else
  SetLength(Result, 0);
end;

function TQAndroidShell.ReadReply(ATimeout: Cardinal): QStringW;
begin
Result := InternalReadReply(FProcess, ATimeout);
end;

function TQAndroidShell.SendCmd(const ACmdline: QStringW): Boolean;
var
  S: QStringA;
  ABuf: TJavaArray<Byte>;
  AStream: JOutputStream;
begin
S := qstring.Utf8Encode(ACmdline + SLineBreak);
ABuf := TJavaArray<Byte>.Create(S.Length);
try
  Move(PQCharA(S)^, ABuf.Data^, S.Length);
  AStream := (FProcess as JProcess).getOutputStream;
  AStream.write(ABuf);
  AStream.flush;
  Result := True;
except
  Result := False;
end;
FreeAndNil(ABuf);
end;

end.

 【提示】应修改AndroidManifest.template.xml文件,加入ACCESS_SUPERUSER权限,虽然不加暂时也没有太大的副作用。

    <uses-sdk android:minSdkVersion="%minSdkVersion%" android:targetSdkVersion="%targetSdkVersion%" />
<%uses-permission%>
<!-- 添加此行此声明需要管理员权限 -->
	<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<!-- 添加完毕 -->
    <application android:persistent="%persistent%"

不需多说,看疗效:

Android_Root2   Andriod_Root

完整的源码下载:百度网盘

 

 

分享到: