近期项目需要一款抓取系统log的实用工具,具体的内容包括kernel中的log, cpu中的log,  memory 中的log, 以及system中的log,在Android4.1之后 认为应用读取系统的log是不安全的,所以要对apk进行系统签名才能读取系统log,如果不能进行系统签名,那么就通过相应的adb命令进行读取.

通过功能分析,做的步骤分为如下大概几步:

1.工具可以开启自启动进行抓取

2,可通过广播控制开始和停止

3.界面不可显示无图标,但是可以通过特定广播打开一个操作activity

4.操作界面可以手动操作,可以其中某几个选择抓取,可以实现 copy到U盘,手动点击上传到服务器,手动点击清除log选项.

5.具体的log抓取实现

因项目中有敏感信息 仅仅用逻辑分析为主

先来说一下对应log adb 抓取的方法

kernel中的log
#会持续输出
adb shell cat /dev/kmsg
#不会持续输出
adb shell  dmesg
#会持续输出
adb  logcat -b kernel


cpu中的log 
#会持续输出
adb shell top -m 5


memory 中的log
#不会持续输出
adb shell dumpsys meminfo


以及system中的log
#会持续输出
adb logcat

实现原理就是 使用 Runtime.getRuntime()方法执行 adb命令,但是通过如下命令是不会执行成功的,因为 exec不支持重定向功能

Runtime run = Runtime.getRuntime();  
Process proc = run.exec("adb shell top -m 5 > my.txt");

关于如上方法我们可以使用ProcessBuilder 拿到process 而 process可以通过 process.getInputStream() 可以持续读对应的流,方便IO操作. 具体的细节在下面讲.

ProcessBuilder pBuilder = new ProcessBuilder(new String[]{"sh", "-c", "adb shell top -m 5"});
 pBuilder.redirectErrorStream(true);
 pr = pBuilder.start();

 

1.工具可以开启自启动今行抓取,通过build.prop 配置信息进行控制  通过底层配置的好处是不同项目只需要在build.prop中配置不同的值 就能决定是否开启开机抓取功能,只需要一个apk应用即可实现全部兼容

 android.intent.action.BOOT_COMPLETED

<receiver android:name=".broadcastreceiver.BootBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </receiver>

     1.2 AndroidManifest.xml  权限添加

<uses-permission android:name="android.permission.READ_LOGS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.DUMP"/>
    <uses-permission android:name="android.permission.INTERNET"/>

     

     1.3在接收到开机广播 之后 读取底层配置信息 是否开启log抓取服务 ,读取buil.prop可用如下反射方法进行读取

/**
     * 读取底层配置 配置是否开机自启动抓取log
     * @param context
     * @return false 开机不启动 , true标示开机启动
     */
    private boolean isStartLogUtis(Context context) {
        String sn = null;
        try {
            ClassLoader cl = context.getClassLoader();
            Class SystemProperties = cl.loadClass("android.os.SystemProperties");
            Class[] paramTypes = new Class[1];
            paramTypes[0] = String.class;
            Method get = SystemProperties.getMethod("get", paramTypes);
            sn = (String) get.invoke(SystemProperties, new Object[]{"xxxx.logtool.start"});

            if (TextUtils.isEmpty(sn.trim())) {
                return false;
            }
            LogUtils.d("  isStartLogUtis " + sn.trim());
            return !TextUtils.isEmpty(sn.trim());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

1.4具体的广播实现

static final String ACTION = "android.intent.action.BOOT_COMPLETED";
    @Override
    public void onReceive(Context context, Intent intent) {
        // isStartLogUtis(context) 读取底层配置的值
        if (intent.getAction() == ACTION && isStartLogUtis(context)) {
            Intent service = new Intent(context, XXXXService.class);
            context.startService(service);
        }
    }


  2.可通过广播进行抓取和停止通过自定义的广播可以实现发送广播进行抓取操作,当服务开启时禁止重复开启,当服务未开启时候禁止停止服务 

     2.1查询服务的核心代码如下:

/**
     * 判断服务是否开启
     * @param context
     * @param ServiceName 服务的完整路径(例:com.zhidao.logtools.service)
     * @return
     */
    public boolean isServiceRunning(Context context, String ServiceName) {
        if (TextUtils.isEmpty(ServiceName)) {
            return false;
        }
        ActivityManager myManager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ArrayList<ActivityManager.RunningServiceInfo> runningService =
                (ArrayList<ActivityManager.RunningServiceInfo>)
                        myManager.getRunningServices(100);
        for (int i = 0; i < runningService.size(); i++) {
            if (runningService.get(i).service.getClassName().toString()
                    .equals(ServiceName)) {
                return true;
            }
        }
        return false;
    }

3.界面不可显示无图标,但是可以通过特定广播打开一个操作activity

     3.1隐藏桌面图标可以使用在AndroidManifest.xml 将所有的activity标签中的android.intent.category.LAUNCHER注释掉即可

<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <!--<category android:name="android.intent.category.LAUNCHER" />-->
            </intent-filter>
        </activity>

  注释掉上面的内容 Android Studio会报错,那是应为Android Studio找不到 主Activity所导致,两个方法解决

         3.1.1 在Androidstudio 点击 app--> edit configurations...--> Launch Options --> Launch 选着 Specified Activity ,下面的                          Activity 选择你进入的activity就行

         3.1.2 使用gradle命令 在跟目录执行 ./gradlew assembleDebug 即可打包

adb shell am start -n 包名/.XXXActivity

4.功能的UI搭建,不在这里细讲

5.具体的log抓取实现

   5,1抓取log是一个耗时操作,需要在后台运行,因此将抓取动作放到后台运行 但是又因为其属于耗时操作 放到子线程

   5.2 子线程的具体实现

       5.2.1 抓取 cpu的log cpu的log是持续输出的

mProcess = Runtime.getRuntime().exec("top -m 5");
            InputStreamReader inputStreamReader = new InputStreamReader(mProcess.getInputStream());
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            if ((line = bufferedReader.readLine()) != null) {
                mCpuInfoWriter.write(line);
                mCpuInfoWriter.write("\n");
                if (line.startsWith("User")) {
                    mCpuInfoWriter.write(mDateFormat.format(new Date()));
                    mCpuInfoWriter.write("\n");
                }
                mCpuInfoWriter.flush();
            }

            5.2.2 抓取kenel实现  

List<String> commandList = new ArrayList<String>();
            commandList.add("logcat");
            commandList.add("-b");
            commandList.add("kernel");
            process = Runtime.getRuntime().exec(commandList.toArray(new String[commandList.size()]));
            InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            mKenelInfoWriter.write(mDateFormat.format(new Date()));
            mKenelInfoWriter.write("\n");
            while ((line = bufferedReader.readLine()) != null) {
                mKenelInfoWriter.write(line);
                mKenelInfoWriter.write("\n");
                mKenelInfoWriter.flush();
            }

             5.2.3抓取 mainlog 

try {
            process = Runtime.getRuntime().exec("logcat");
            InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            mMainInfoWriter.write(mDateFormat.format(new Date()));
            mMainInfoWriter.write("\n");
            while ((line = bufferedReader.readLine()) != null) {
                mMainInfoWriter.write(line);
                mMainInfoWriter.write("\n");
                mMainInfoWriter.flush();
            }

         5.2.4抓取Memorylog  因为 Memorydums信息不是一直等待,每次只有一次输出,因此五秒钟循环一次

while (true) {
                process = Runtime.getRuntime().exec("dumpsys meminfo");
                InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                String line = null;
                mMemoryInfoWriter.write(mDateFormat.format(new Date()));
                mMemoryInfoWriter.write("\n");
                while ((line = bufferedReader.readLine()) != null) {
                    mMemoryInfoWriter.write(line);
                    mMemoryInfoWriter.write("\n");
                    mMemoryInfoWriter.flush();
                }
                Thread.sleep(5000);//
            }

          5.2.5具体的Thread实现 将上述的方法直接替换到如下类中即可

/**
 * 录制 System 线程类
 * @author chencl
 */
public class RecordSystemLogcatTask extends Thread {
    private Process process = null;
    private OutputStreamWriter mSystemInfoWriter = null;
    private static SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
    public static final String LOG_SDCARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
            + "LogInfo" +File.separator+ mDateFormat.format(new Date());
    public RecordSystemLogcatTask() {
        // TODO Auto-generated constructor stub
        if (mSystemInfoWriter == null) {
            try {
                mSystemInfoWriter = new OutputStreamWriter(new FileOutputStream(LOG_SDCARD_PATH + File.separator + mDateFormat.format(new Date()) + "_system.log"));
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        try {
             //TODO
            //将上述方法放到本处
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}