保存Android日志到文件

之前写过一篇日志,[《不是安卓工程师,也能看logcat日志》](/228),不仅需要adb环境还要熟悉logcat命令,感觉比较繁琐。现推出升级版,在Service中保存日志服务。同样用到命令logcat -f logpath。在这之前,已有人造过同样的轮子。只是代码更长,功能更完善。比如:监听日志文件大小,超出10M就重新生成日志文件。个人认为,这些功能有些画蛇添足。试问,有谁会从10M日志文件中去找调试信息。so,出一个简化版,方便阅读、方便理解。

1 PS命令

此PS,非PhotoShop。熟悉linux 都知道,ps 命令可以list出系统中所有正在运行的线程。

图示是在adb shell中,list出当前连接设备正在运行的所有进程信息。

grep参数做过滤操作,之前在查看指定pid的日志中有提过。(Android 7.0 开始logcat 支持pid 筛选,如:logcat --pid=进程id)。从上图中,可以看出:

进程A 创建进程B

A.USER == B.USER

A.PID == B.PPID

同样,进程A创建的logcat进程只能保存进程A 的全部日志,执行的PS命令只能list出同USER属性相同的进程。

2 保存日志

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 开始保存日志
*/
private void startSaveLog() {
try {
Runtime.getRuntime().exec("logcat -f " + logFilePath);
//获取新生成的logcat 进程信息
logcatInfo = getLogcatProInfo(getProcessInfoStr("logcat"));
} catch (IOException e) {
e.printStackTrace();
}
}

3 清除日志缓存

1
2
3
4
5
6
7
8
9
10
11
/**
* 清除日志缓存
*/
private void clearLogCache() {
try {
//清除日志缓存
Runtime.getRuntime().exec("logcat -c");
} catch (IOException e) {
e.printStackTrace();
}
}

在开始保存日志前调用,清除之前已经输出的日志。

4 关闭进程

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 删除logcat进程,退出保存日志
*/
private void killLogcat() {
if (logcatInfo == null)
return;

try {
Runtime.getRuntime().exec("kill " + logcatInfo.pid);
} catch (IOException e) {
e.printStackTrace();
}
}

可以在任意位置发送广播,停止保存日志。

5 广播控制日志保存和关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 /**
* 保存日志操作广播
*/
class SaveLogReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(KILL_LOGCAT_ACTION)) {
killLogcat();
} else if (intent.getAction().equals(START_LOGCAT_ACTION)) {
clearLogCache();
startSaveLog();
}
}
}

同样,可以在任意地方重开始,继续保存日志。这样可以有效避免保存了无用的日志信息,使日志内容又臭又长。

6 获取当前应用的进程信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
appProInfo = new ProcessInfo(getProcessInfoStr(getPackageName()).get(0));

/**
* 根据名称获取线程信息
*
* @param name
* @return 返回线程信息的字符串格式
*/
private ArrayList<String> getProcessInfoStr(String name) {

ArrayList<String> strs = new ArrayList<String>();
try {
java.lang.Process process = Runtime.getRuntime().exec("ps");
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
if (line.endsWith(name)) {
strs.add(line);
}
}
br.close();
isr.close();
is.close();

br = null;
isr = null;
is = null;

} catch (IOException e) {
e.printStackTrace();
}
return strs;
}

/**
* 进程信息,PS命令获取
*/
class ProcessInfo {
private String user;
private String pid;
private String ppid;
private String vsize;
private String rss;
private String wchan;
private String pc;
private String name;

public ProcessInfo(String info) {
if (info == null)
return;
//info 可能连续包含多个空格,因此用split方法无效。
ArrayList<String> item = new ArrayList<String>();
for (int i = 0; i < info.length(); i++) {
if (info.charAt(i) == ' ') {
continue;
}
int j = i + 1;
while (j < info.length() && info.charAt(j++) != ' ') ;
item.add(info.substring(i, j - 1));
i = j - 1;
}

user = item.get(0);
pid = item.get(1);
ppid = item.get(2);
vsize = item.get(3);
rss = item.get(4);
wchan = item.get(5);
pc = item.get(6);
name = item.get(8);
}

}

7 使用方法

  1. 在AndroidManife.xml里注册Service
  2. 启动Service,开始保存日志
  3. 发送广播,停止保存日志
  4. 发送广播,继续保存日志

最后附上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

/**
* 保存日志服务
* Created by flueky on 17/8/3 下午3:25.
* e-mail:flueky@sina.com
* FilePath App:com.flueky.framework.android
*/

public class SaveLogService extends Service {

private String logFoldPath;//日志文件夹路径
private String logFilePath;//日志文件路径
public static final String KILL_LOGCAT_ACTION = "com.flueky.action.kill_logcat";
public static final String START_LOGCAT_ACTION = "com.flueky.action.start_logcat";

private ProcessInfo appProInfo;//应用进程信息
private ProcessInfo logcatInfo;//日志进程信息
private SaveLogReceiver saveLogReceiver;

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();

logFoldPath = getExternalCacheDir().getAbsolutePath() + "/../log";

saveLogReceiver = new SaveLogReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(KILL_LOGCAT_ACTION);
filter.addAction(START_LOGCAT_ACTION);
registerReceiver(saveLogReceiver, filter);
}


/**
* 校验日志目录文件夹
*
* @return
*/
private boolean checkLogFolder() {
File file = new File(logFoldPath);
if (!file.exists()) {
return file.mkdirs();
}
return true;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
appProInfo = new ProcessInfo(getProcessInfoStr(getPackageName()).get(0));
//清除日志缓存
clearLogCache();
//开始保存日志
startSaveLog();
return super.onStartCommand(intent, flags, startId);

}


@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(saveLogReceiver);
}

/**
* 根据名称获取线程信息
*
* @param name
* @return 返回线程信息的字符串格式
*/
private ArrayList<String> getProcessInfoStr(String name) {

ArrayList<String> strs = new ArrayList<String>();
try {
java.lang.Process process = Runtime.getRuntime().exec("ps");
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
if (line.endsWith(name)) {
strs.add(line);
}
}
br.close();
isr.close();
is.close();

br = null;
isr = null;
is = null;

} catch (IOException e) {
e.printStackTrace();
}
return strs;
}


/**
* 获取保存日志的进程信息
*
* @param logcatProcessInfos
* @return
*/
private ProcessInfo getLogcatProInfo(ArrayList<String> logcatProcessInfos) {
if (logcatProcessInfos == null)
return null;
for (String infoStr : logcatProcessInfos) {
ProcessInfo info = new ProcessInfo(infoStr);
if (info.user.equals(appProInfo.user)) {
return info;
}
}
return null;
}

/**
* 删除logcat进程,退出保存日志
*/
private void killLogcat() {
if (logcatInfo == null)
return;

try {
Runtime.getRuntime().exec("kill " + logcatInfo.pid);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 清除日志缓存
*/
private void clearLogCache() {
try {
//清除日志缓存
Runtime.getRuntime().exec("logcat -c");
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 开始保存日志
*/
private void startSaveLog() {
try {
if (checkLogFolder())
logFilePath = logFoldPath + "/HH-" + new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime()) + ".log";
Runtime.getRuntime().exec("logcat -f " + logFilePath);
logcatInfo = getLogcatProInfo(getProcessInfoStr("logcat"));
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 进程信息,PS命令获取
*/
class ProcessInfo {
private String user;
private String pid;
private String ppid;
private String vsize;
private String rss;
private String wchan;
private String pc;
private String name;

public ProcessInfo(String info) {
if (info == null)
return;
ArrayList<String> item = new ArrayList<String>();
for (int i = 0; i < info.length(); i++) {
if (info.charAt(i) == ' ') {
continue;
}
int j = i + 1;
while (j < info.length() && info.charAt(j++) != ' ') ;
item.add(info.substring(i, j - 1));
i = j - 1;
}

user = item.get(0);
pid = item.get(1);
ppid = item.get(2);
vsize = item.get(3);
rss = item.get(4);
wchan = item.get(5);
pc = item.get(6);
name = item.get(8);
}

@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(user).
append(" ").
append(pid).
append(" ").
append(ppid).
append(" ").
append(vsize).
append(" ").
append(rss).
append(" ").
append(wchan).
append(" ").
append(pc).
append(" ").
append(name);

return buffer.toString();
}
}

/**
* 保存日志操作广播
*/
class SaveLogReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(KILL_LOGCAT_ACTION)) {
killLogcat();
} else if (intent.getAction().equals(START_LOGCAT_ACTION)) {
clearLogCache();
startSaveLog();
}
}
}

}

觉得有用?那打赏一个呗。[去打赏](/donate/)

Author: flueky
Link: http://example.com/230/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.