目錄介紹
- 1.業(yè)務需求
- 2.目前有那些做法及問題
- 3.自定義log工具類婚苹,一邊打印日志一邊寫入文件
- 4.在application開啟線程池進行日志打印
- 5.寫一個service將系統(tǒng)日志寫到文件
關于鏈接
1.業(yè)務需求
- 要求將app的系統(tǒng)日志都寫入到指定的文件目錄下的文件中
- 要求寫入文件達到一定大小后自動切換到下一個文件夾
- 要求app在運行時泽论,持續(xù)將日志寫到文件中
- 要求可以清除7天或者n天之前的日志文件
- 要求有sd卡寂呛,寫日志到sd卡文件;沒有則寫到內存卡;如果從無sd卡到有sd卡确徙,那么可以切換路徑
- 關于代碼地址可以參考:https://github.com/yangchong211/YCAudioPlayer
2.目前有那些做法及問題
- 1.自定義log工具類,在輸出日志的時候执桌,可以將輸出日志寫到文件夾鄙皇。不過只能記錄priority5中狀態(tài)日志
- 2.在application開啟線程池,然后調用PrintToFileUtil類(自己寫的工具類)中setPrint方法仰挣。存在問題:app一直開啟狀態(tài)下伴逸,無法持續(xù)打印,還未找到原因
- 3.開啟app時膘壶,打開service服務错蝴,當app進程不殺死時,或者service不銷毀時颓芭,會一直運行在后臺顷锰,那么也可以處理打印日志邏輯。并且通過廣播可以監(jiān)聽sd狀態(tài)來達到切換日志路徑的目的
3.自定義log工具類亡问,一邊打印日志一邊寫入文件
- 3.1 直接調用代碼如下所示:
public static void e(String msg) {
if (isDebug){
Log.e(TAG, msg);
PrintToFileUtil.input2File(msg,"path");
}
}
- 3.2 工具類如下所示
/**
* 將內容直接寫過文件中官紫,自己設置路徑
* 這個是一邊打印日志,一邊將日志寫入到file
* 不建議直接new一個子線程做寫入邏輯州藕,建議開啟線程池束世,避免到處開啟線程損耗性能
* @param input 寫入內容
* @param filePath 路徑
* @return
*/
static boolean input2File(final String input, final String filePath) {
if (sExecutor == null) {
sExecutor = Executors.newScheduledThreadPool(5);
}
Future<Boolean> submit = sExecutor.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
BufferedWriter bw = null;
try {
// 構造給定文件名的FileWriter對象,并使用布爾值指示是否追加寫入的數據床玻。
FileWriter fileWriter = new FileWriter(filePath, true);
bw = new BufferedWriter(fileWriter);
bw.write(input);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
try {
return submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return false;
}
4.在application開啟線程池進行日志打印
/**
* 遇到問題:
* 1.什么時候調用這個方法良狈?
* 2.為什么調用這個方法會造成程序無響應,卡在什么地方笨枯?
* 3.如何避免造成線程堵塞
* 4.當打印到一定日志后,如何切換到下一個.txt文件遇西?
* @param logPath
*/
public static void setPrint(String logPath){
if (sExecutor == null) {
sExecutor = Executors.newScheduledThreadPool(5);
}
//以xx月xx日為命名文件名馅精,這樣每天的文件都是不一樣的
Date now = new Date(System.currentTimeMillis());
String format = FORMAT.format(now);
String date = format.substring(0, 5);
final String newLogPath = logPath +date;
Future<Boolean> submit = sExecutor.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
/*核心代碼*/
while(captureLogThreadOpen){
/*命令的準備*/
ArrayList<String> getLog = new ArrayList<>();
getLog.add("logcat");
getLog.add("-d");
getLog.add("-v");
getLog.add("time");
ArrayList<String> clearLog = new ArrayList<>();
clearLog.add("logcat");
clearLog.add("-c");
try{
//抓取當前的緩存日志
Process process = Runtime.getRuntime().exec(getLog.toArray(new String[getLog.size()]));
//獲取輸入流
BufferedReader buffRead = new BufferedReader(new InputStreamReader(process.getInputStream()));
//清除是為了下次抓取不會從頭抓取
Runtime.getRuntime().exec(clearLog.toArray(new String[clearLog.size()]));
String str = null;
//打開文件
File logFile = new File(newLogPath + "log.txt");
//true表示在寫的時候在文件末尾追加
FileOutputStream fos = new FileOutputStream(logFile, true);
//換行的字符串
String newline = System.getProperty("line.separator");
//Date date = new Date(System.currentTimeMillis());
//String time = format.format(date);
//Log.i(TAG, "thread");
//打印設備信息
fos.write(printDeviceInfo().getBytes());
while((str=buffRead.readLine())!=null){ //循環(huán)讀取每一行
//Runtime.getRuntime().exec(clearLog.toArray(new String[clearLog.size()]));
//Log.i(TAG, str);
@SuppressLint("SimpleDateFormat")
Date date = new Date(System.currentTimeMillis());
String time = FORMAT.format(date);
//加上年
fos.write((time + str).getBytes());
//換行
fos.write(newline.getBytes());
logCount++;
//大于100000行就退出
if(logCount>100000){
captureLogThreadOpen = false;
fos.close();
break;
}
}
fos.close();
String[] strings = clearLog.toArray(new String[clearLog.size()]);
Runtime.getRuntime().exec(strings);
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
}
return true;
}
});
try {
submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
5.寫一個service將系統(tǒng)日志寫到文件
- 5.1 文檔要點
- 1.支持自定義設置日志寫入的路徑和文件名稱;
- 2.如果有sd卡粱檀,那么就記錄在sd卡中洲敢;否則記錄到內存卡中;
- 3.如果之前日志記錄在內存卡中茄蚯,后期有sd卡压彭,那么會將之前內存中的文件拷貝到SDCard中
- 4.可以監(jiān)聽sd卡裝載和卸載廣播睦优,從而切換保存日志的保存路徑
- 5.sd卡中可以設置日志保存的最長周期,目前設置為7天
- 6.當寫入日志的文件大于自定義最大值(目前是10M)壮不,可以自動切換到下一個文件夾
- 7.支持清除日志緩存汗盘,每次記錄日志之前先清除日志的緩存, 不然會在兩個日志文件中記錄重復的日志
- 8.收集的日志包括:d,v询一,e隐孽,w,c
- 9.支持超出最長保存日期后自動刪除文件邏輯健蕊,刪除邏輯是:取以日期命名的文件名稱與當前時間比較
- 10.app開啟的時候打開該服務service菱阵,那么service在運行的時候會一直記錄日志并且寫到文件,除非殺死服務
- 11.建議日志命名按照日期命名缩功,方便處理刪除邏輯
-
5.2 具體的實現代碼
- 5.2.1 在清單文件注冊
<service android:name=".service.LogService" /> <!--系統(tǒng)日志權限--> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
- 5.2.2 關于logService代碼如下
public class LogService extends Service {
//日志
private static final String TAG = "LogService";
//內存中日志文件最大值晴及,10M
private static final int MEMORY_LOG_FILE_MAX_SIZE = 10 * 1024 * 1024;
//內存中的日志文件大小監(jiān)控時間間隔,10分鐘
private static final int MEMORY_LOG_FILE_MONITOR_INTERVAL = 10 * 60 * 1000;
//sd卡中日志文件的最多保存天數
private static final int SDCARD_LOG_FILE_SAVE_DAYS = 7;
//日志文件在內存中的路徑(日志文件在安裝目錄中的路徑)
private String LOG_PATH_MEMORY_DIR;
//日志文件在sdcard中的路徑【主要這個很重要】
private String LOG_PATH_SDCARD_DIR;
//當前的日志記錄類型為存儲在SD卡下面
private final int SDCARD_TYPE = 0;
//當前的日志記錄類型為存儲在內存中
private final int MEMORY_TYPE = 1;
//當前的日志記錄類型嫡锌,默認是SD卡
private int CURR_LOG_TYPE = SDCARD_TYPE;
//如果當前的日志寫在內存中虑稼,記錄當前的日志文件名稱
private String CURR_INSTALL_LOG_NAME;
//本服務輸出的日志文件名稱,也可以是txt格式世舰,或者后綴名是.log
private String logServiceLogName = "Log.log";
@SuppressLint("SimpleDateFormat")
private SimpleDateFormat myLogSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//日志名稱格式
@SuppressLint("SimpleDateFormat")
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH時mm分ss秒");
private OutputStreamWriter writer ;
private Process process;
//喚醒
private PowerManager.WakeLock wakeLock;
//SDCard狀態(tài)監(jiān)測动雹,廣播監(jiān)聽sd卡動態(tài),比如用戶拔下sd卡
private SDStateMonitorReceiver sdStateReceiver;
private LogTaskReceiver logTaskReceiver;
/* 是否正在監(jiān)測日志文件大懈埂胰蝠;
* 如果當前日志記錄在SDard中則為false
* 如果當前日志記錄在內存中則為true*/
private boolean logSizeMoniting = false;
//日志文件監(jiān)測action
private static String MONITOR_LOG_SIZE_ACTION = "MONITOR_LOG_SIZE";
//切換日志文件action
private static String SWITCH_LOG_FILE_ACTION = "SWITCH_LOG_FILE_ACTION";
/**
* 無需綁定
* @param intent intent
* @return IBinder對象
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 銷毀時調用該方法
*/
@Override
public void onDestroy() {
super.onDestroy();
recordLogServiceLog("LogService onDestroy");
if (writer != null) {
try {
writer.close();
writer = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (process != null) {
process.destroy();
}
//注意需要注銷廣播
if(sdStateReceiver!=null){
unregisterReceiver(sdStateReceiver);
}
if(logTaskReceiver!=null){
unregisterReceiver(logTaskReceiver);
}
}
/**
* 每次開啟service時,都會調用一次該方法震蒋,用于初始化
*/
@Override
public void onCreate() {
super.onCreate();
init();
register();
deploySwitchLogFileTask();
new LogCollectorThread().start();
}
private void init(){
//日志文件在內存中的路徑(日志文件在安裝目錄中的路徑)
LOG_PATH_MEMORY_DIR = getFilesDir().getAbsolutePath() + File.separator + "log";
//本服務產生的日志茸塞,記錄日志服務開啟失敗信息
String LOG_SERVICE_LOG_PATH = LOG_PATH_MEMORY_DIR + File.separator + logServiceLogName;
//日志文件在sdcard中的路徑
LOG_PATH_SDCARD_DIR = FileUtils.getLocalRootSavePathDir("logger")+ File.separator + "log";
//創(chuàng)建log文件夾
createLogDir();
try {
//true 表示可以接著寫入
FileOutputStream fos = new FileOutputStream(LOG_SERVICE_LOG_PATH, true);
writer = new OutputStreamWriter(fos);
} catch (FileNotFoundException e) {
Log.e(TAG, e.getMessage(), e);
}
//獲取PowerManager管理者
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
if (pm != null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
//當前的日志記錄類型
CURR_LOG_TYPE = getCurrLogType();
Log.i(TAG, "LogService onCreate");
}
private void register(){
IntentFilter sdCarMonitorFilter = new IntentFilter();
sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
sdCarMonitorFilter.addDataScheme("file");
sdStateReceiver = new SDStateMonitorReceiver();
registerReceiver(sdStateReceiver, sdCarMonitorFilter);
IntentFilter logTaskFilter = new IntentFilter();
logTaskFilter.addAction(MONITOR_LOG_SIZE_ACTION);
logTaskFilter.addAction(SWITCH_LOG_FILE_ACTION);
logTaskReceiver = new LogTaskReceiver();
registerReceiver(logTaskReceiver,logTaskFilter);
}
/**
* 獲取當前應存儲在內存中還是存儲在SDCard中
* @return 如果有sd卡,就放到sd卡中查剖;沒有則放到內存卡中
*/
public int getCurrLogType(){
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return MEMORY_TYPE;
}else{
return SDCARD_TYPE;
}
}
/**
* 部署日志切換任務钾虐,每天凌晨切換日志文件
*/
private void deploySwitchLogFileTask() {
Intent intent = new Intent(SWITCH_LOG_FILE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// 部署任務
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
if (am != null) {
am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, sender);
}
recordLogServiceLog("deployNextTask success,next task time is:"+myLogSdf.format(calendar.getTime()));
}
/**
* 日志收集
* 1.清除日志緩存
* 2.殺死應用程序已開啟的Logcat進程防止多個進程寫入一個日志文件
* 3.開啟日志收集進程
* 4.處理日志文件,移動 OR 刪除
*/
class LogCollectorThread extends Thread {
LogCollectorThread(){
super("LogCollectorThread");
Log.d(TAG, "LogCollectorThread is create");
}
@SuppressLint("WakelockTimeout")
@Override
public void run() {
try {
//喚醒手機
wakeLock.acquire();
//每次記錄日志之前先清除日志的緩存, 不然會在兩個日志文件中記錄重復的日志
clearLogCache();
//運行PS命令得到進程信息
List<String> orgProcessList = getAllProcess();
//根據ps命令得到的內容獲取PID笋庄,User效扫,name等信息
List<ProcessInfo> processInfoList = getProcessInfoList(orgProcessList);
//關閉由本程序開啟的logcat進程
killLogcatPro(processInfoList);
//開始收集日志信息
createLogCollector();
//休眠,創(chuàng)建文件直砂,然后處理文件菌仁,不然該文件還沒創(chuàng)建,會影響文件刪除
Thread.sleep(1000);
//處理日志文件静暂,這里面主要是處理sd卡和內存卡切換存儲日志邏輯
handleLog();
//釋放
wakeLock.release();
} catch (Exception e) {
e.printStackTrace();
recordLogServiceLog(Log.getStackTraceString(e));
}
}
}
/**
* 每次記錄日志之前先清除日志的緩存, 不然會在兩個日志文件中記錄重復的日志
*/
private void clearLogCache() {
Process pro = null;
List<String> commandList = new ArrayList<>();
commandList.add("logcat");
commandList.add("-c");
try {
pro = Runtime.getRuntime().exec(commandList.toArray(new String[commandList.size()]));
StreamConsumer errorGobbler = new StreamConsumer(pro.getErrorStream());
StreamConsumer outputGobbler = new StreamConsumer(pro.getInputStream());
errorGobbler.start();
outputGobbler.start();
if (pro.waitFor() != 0) {
Log.e(TAG, " clearLogCache proc.waitFor() != 0");
recordLogServiceLog("clearLogCache clearLogCache proc.waitFor() != 0");
}
} catch (Exception e) {
Log.e(TAG, "clearLogCache failed", e);
recordLogServiceLog("clearLogCache failed");
} finally {
try {
if (pro != null) {
pro.destroy();
}
} catch (Exception e) {
Log.e(TAG, "clearLogCache failed", e);
recordLogServiceLog("clearLogCache failed");
}
}
}
/**
* 關閉由本程序開啟的logcat進程:
* 根據用戶名稱殺死進程(如果是本程序進程開啟的Logcat收集進程那么兩者的USER一致)
* 如果不關閉會有多個進程讀取logcat日志緩存信息寫入日志文件
* @param allPro allPro
*/
private void killLogcatPro(List<ProcessInfo> allPro) {
if(process != null){
process.destroy();
}
String packName = this.getPackageName();
//獲取本程序的用戶名稱
String myUser = getAppUser(packName, allPro);
for (ProcessInfo processInfo : allPro) {
if (processInfo.name.toLowerCase().equals("logcat") && processInfo.user.equals(myUser)) {
android.os.Process.killProcess(Integer.parseInt(processInfo.pid));
//recordLogServiceLog("kill another logcat process success,the process info is:"
// + processInfo);
}
}
}
/**
* 獲取本程序的用戶名稱
* @param packName packName
* @param allProList allProList
* @return 程序名稱
*/
private String getAppUser(String packName, List<ProcessInfo> allProList) {
for (ProcessInfo processInfo : allProList) {
if (processInfo.name.equals(packName)) {
return processInfo.user;
}
}
return null;
}
/**
* 根據ps命令得到的內容獲取PID济丘,User,name等信息
* @param orgProcessList orgProcessList
* @return 集合
*/
private List<ProcessInfo> getProcessInfoList(List<String> orgProcessList) {
List<ProcessInfo> proInfoList = new ArrayList<>();
for (int i = 1; i < orgProcessList.size(); i++) {
String processInfo = orgProcessList.get(i);
String[] proStr = processInfo.split(" ");
// USER PID PPID VSIZE RSS WCHAN PC NAME
// root 1 0 416 300 c00d4b28 0000cd5c S /init
List<String> orgInfo = new ArrayList<>();
for (String str : proStr) {
if (!"".equals(str)) {
orgInfo.add(str);
}
}
if (orgInfo.size() == 9) {
ProcessInfo pInfo = new ProcessInfo();
pInfo.user = orgInfo.get(0);
pInfo.pid = orgInfo.get(1);
pInfo.ppid = orgInfo.get(2);
pInfo.name = orgInfo.get(8);
proInfoList.add(pInfo);
}
}
return proInfoList;
}
/**
* 運行PS命令得到進程信息
* @return
* USER PID PPID VSIZE RSS WCHAN PC NAME
* root 1 0 416 300 c00d4b28 0000cd5c S /init
*/
private List<String> getAllProcess() {
List<String> orgProList = new ArrayList<>();
Process pro = null;
try {
pro = Runtime.getRuntime().exec("ps");
StreamConsumer errorConsumer = new StreamConsumer(pro.getErrorStream());
StreamConsumer outputConsumer = new StreamConsumer(pro.getInputStream(), orgProList);
errorConsumer.start();
outputConsumer.start();
if (pro.waitFor() != 0) {
Log.e(TAG, "getAllProcess pro.waitFor() != 0");
recordLogServiceLog("getAllProcess pro.waitFor() != 0");
}
} catch (Exception e) {
Log.e(TAG, "getAllProcess failed", e);
recordLogServiceLog("getAllProcess failed");
} finally {
try {
if (pro != null) {
pro.destroy();
}
} catch (Exception e) {
Log.e(TAG, "getAllProcess failed", e);
recordLogServiceLog("getAllProcess failed");
}
}
return orgProList;
}
/**
* 開始收集日志信息
* 日志包括,f摹迷,d疟赊,v,time峡碉,
*/
public void createLogCollector() {
// 日志文件名稱
String logFileName = sdf.format(new Date()) + ".log";
List<String> commandList = new ArrayList<>();
commandList.add("logcat");
commandList.add("-f");
commandList.add("-d");
//commandList.add(LOG_PATH_INSTALL_DIR + File.separator + logFileName);
commandList.add(getLogPath());
commandList.add("-v");
commandList.add("time");
// 過濾所有的i信息
commandList.add("*:I");
// 過濾所有的錯誤信息
//commandList.add("*:E");
// 過濾指定TAG的信息
// commandList.add("MyAPP:V");
// commandList.add("*:S");
try {
process = Runtime.getRuntime().exec(commandList.toArray(new String[commandList.size()]));
recordLogServiceLog("start collecting the log,and log name is:"+logFileName);
// process.waitFor();
} catch (Exception e) {
Log.e(TAG, "CollectorThread == >" + e.getMessage(), e);
recordLogServiceLog("CollectorThread == >" + e.getMessage());
}
}
/**
* 根據當前的存儲位置得到日志的絕對存儲路徑
* @return 路徑
*/
public String getLogPath(){
createLogDir();
// 日志文件名稱
String logFileName = sdf.format(new Date()) + ".log";
if(CURR_LOG_TYPE == MEMORY_TYPE){
CURR_INSTALL_LOG_NAME = logFileName;
Log.d(TAG, "Log stored in memory, the path is:"+LOG_PATH_MEMORY_DIR + File.separator + logFileName);
return LOG_PATH_MEMORY_DIR + File.separator + logFileName;
}else{
CURR_INSTALL_LOG_NAME = null;
Log.d(TAG, "Log stored in SDcard, the path is:"+LOG_PATH_SDCARD_DIR + File.separator + logFileName);
return LOG_PATH_SDCARD_DIR + File.separator + logFileName;
}
}
/**
* 處理日志文件
* 1.如果日志文件存儲位置切換到內存中近哟,刪除除了正在寫的日志文件
* 并且部署日志大小監(jiān)控任務,控制日志大小不超過規(guī)定值
* 2.如果日志文件存儲位置切換到SDCard中异赫,刪除7天之前的日志椅挣,移
* 動所有存儲在內存中的日志到SDCard中,并將之前部署的日志大小
* 監(jiān)控取消
*/
public void handleLog(){
//當前的日志記錄類型為存儲在內存中
if(CURR_LOG_TYPE == MEMORY_TYPE){
//部署日志大小監(jiān)控任務
deployLogSizeMonitorTask();
//刪除內存中的過期日志塔拳,刪除規(guī)則:除了當前的日志和離當前時間最近的日志保存其他的都刪除
deleteMemoryExpiredLog();
}else{
//將日志文件轉移到SD卡下面
moveLogfile();
//取消部署日志大小監(jiān)控任務
cancelLogSizeMonitorTask();
//刪除內存下過期的日志
deleteSDCardExpiredLog();
}
}
/**
* 部署日志大小監(jiān)控任務
*/
private void deployLogSizeMonitorTask() {
//如果當前正在監(jiān)控著鼠证,則不需要繼續(xù)部署
if(logSizeMoniting){
return;
}
logSizeMoniting = true;
Intent intent = new Intent(MONITOR_LOG_SIZE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
if (am != null) {
am.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(), MEMORY_LOG_FILE_MONITOR_INTERVAL, sender);
}
Log.d(TAG, "deployLogSizeMonitorTask() suc !");
//recordLogServiceLog("deployLogSizeMonitorTask() succ ,start time is " + calendar.getTime().toLocaleString());
}
/**
* 取消部署日志大小監(jiān)控任務
*/
private void cancelLogSizeMonitorTask() {
logSizeMoniting = false;
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(MONITOR_LOG_SIZE_ACTION);
PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
if (am != null) {
am.cancel(sender);
}
Log.d(TAG, "canelLogSizeMonitorTask() suc");
}
/**
* 檢查日志文件大小是否超過了規(guī)定大小
* 如果超過了重新開啟一個日志收集進程
*/
private void checkLogSize(){
//當文件不為空時
if(CURR_INSTALL_LOG_NAME != null && CURR_INSTALL_LOG_NAME.length()>0){
//日志文件在內存中的路徑+如果當前的日志寫在內存中,記錄當前的日志文件名稱
String path = LOG_PATH_MEMORY_DIR + File.separator + CURR_INSTALL_LOG_NAME;
File file = new File(path);
if(!file.exists()){
return;
}
Log.d(TAG, "checkLog() ==> The size of the log is too big?");
//當文件長度>=10M時靠抑,重新創(chuàng)建一個新的文件夾
if(file.length() >= MEMORY_LOG_FILE_MAX_SIZE){
Log.d(TAG, "The log's size is too big!");
new LogCollectorThread().start();
}
}
}
/**
* 創(chuàng)建日志目錄
*/
private void createLogDir() {
//日志文件在內存中的路徑的文件夾
File file = new File(LOG_PATH_MEMORY_DIR);
boolean mkOk;
if (!file.isDirectory() && file.exists()) {
mkOk = file.mkdirs();
if (!mkOk) {
//noinspection ResultOfMethodCallIgnored
file.mkdirs();
}
}
//判斷SD卡是否存在,并且是否具有讀寫權限
//創(chuàng)建SD卡路徑下的文件夾
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
file = new File(LOG_PATH_SDCARD_DIR);
if (!file.isDirectory()) {
mkOk = file.mkdirs();
if (!mkOk) {
recordLogServiceLog("move file failed,dir is not created succ");
}
}
}
}
/**
* 將日志文件轉移到SD卡下面
*/
private void moveLogfile() {
//首先判斷sd卡狀態(tài)
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//recordLogServiceLog("move file failed, sd card does not mount");
return;
}
File file = new File(LOG_PATH_SDCARD_DIR);
if (!file.isDirectory()) {
boolean mkOk = file.mkdirs();
if (!mkOk) {
//recordLogServiceLog("move file failed,dir is not created succ");
return;
}
}
file = new File(LOG_PATH_MEMORY_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
for (File logFile : allFiles) {
String fileName = logFile.getName();
if (logServiceLogName.equals(fileName)) {
continue;
}
//String createDateInfo = getFileNameWithoutExtension(fileName);
File beforeFile = new File(LOG_PATH_SDCARD_DIR + File.separator + fileName);
boolean isSuccess = copy(logFile,beforeFile);
if (isSuccess) {
//noinspection ResultOfMethodCallIgnored
logFile.delete();
//recordLogServiceLog("move file success,log name is:"+fileName);
}
}
}
}
/**
* 刪除內存下過期的日志
*/
private void deleteSDCardExpiredLog() {
File file = new File(LOG_PATH_SDCARD_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
for (File logFile : allFiles) {
String fileName = logFile.getName();
if (logServiceLogName.equals(fileName)) {
continue;
}
//去除文件的擴展類型
String createDateInfo = getFileNameWithoutExtension(fileName);
//判斷sdcard上的日志文件是否可以刪除
if (canDeleteSDLog(createDateInfo)) {
//noinspection ResultOfMethodCallIgnored
logFile.delete();
Log.d(TAG, "delete expired log success,the log path is:" + logFile.getAbsolutePath());
}
}
}
}
/**
* 判斷sdcard上的日志文件是否可以刪除
* @param createDateStr createDateStr
* @return 是否可以刪除
*/
public boolean canDeleteSDLog(String createDateStr) {
boolean canDel ;
//獲取當前時間
Calendar calendar = Calendar.getInstance();
//刪除7天之前日志
calendar.add(Calendar.DAY_OF_MONTH, -1 * SDCARD_LOG_FILE_SAVE_DAYS);
Date expiredDate = calendar.getTime();
try {
Date createDate = sdf.parse(createDateStr);
canDel = createDate.before(expiredDate);
} catch (ParseException e) {
Log.e(TAG, e.getMessage(), e);
canDel = false;
}
return canDel;
}
/**
* 刪除內存中的過期日志量九,刪除規(guī)則:
* 除了當前的日志和離當前時間最近的日志保存其他的都刪除
*/
private void deleteMemoryExpiredLog(){
File file = new File(LOG_PATH_MEMORY_DIR);
if (file.isDirectory()) {
File[] allFiles = file.listFiles();
Arrays.sort(allFiles, new FileComparator());
for (int i=0;i<allFiles.length-2;i++) { //"-2"保存最近的兩個日志文件
File _file = allFiles[i];
if (logServiceLogName.equals(_file.getName()) || _file.getName().equals(CURR_INSTALL_LOG_NAME)) {
continue;
}
//noinspection ResultOfMethodCallIgnored
_file.delete();
Log.d(TAG, "delete expired log success,the log path is:"+_file.getAbsolutePath());
}
}
}
/**
* 拷貝文件
* @param source file
* @param target file
* @return 是否拷貝成功
*/
private boolean copy(File source, File target) {
FileInputStream in = null;
FileOutputStream out = null;
try {
if(!target.exists()){
boolean createSuccess = target.createNewFile();
if(!createSuccess){
return false;
}
}
in = new FileInputStream(source);
out = new FileOutputStream(target);
byte[] buffer = new byte[8*1024];
int count;
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
return true;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
recordLogServiceLog("copy file fail");
return false;
} finally{
try {
if(in != null){
in.close();
}
if(out != null){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
recordLogServiceLog("copy file fail");
}
}
}
/**
* 這個注意是記錄錯誤的日志
* 記錄日志服務的基本信息 防止日志服務有錯,在LogCat日志中無法查找
* 此日志名稱為Log.log
* @param msg msg
*/
private void recordLogServiceLog(String msg) {
if (writer != null) {
try {
Date time = new Date();
writer.write(myLogSdf.format(time) + " : " + msg);
writer.write("\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e.getMessage(), e);
}
}
}
/**
* 去除文件的擴展類型(.log)
* @param fileName fileName
* @return 字符串
*/
private String getFileNameWithoutExtension(String fileName){
return fileName.substring(0, fileName.indexOf("."));
}
class ProcessInfo {
public String user;
private String pid;
private String ppid;
public String name;
@Override
public String toString() {
return "ProcessInfo{" +
"user='" + user + '\'' +
", pid='" + pid + '\'' +
", ppid='" + ppid + '\'' +
", name='" + name + '\'' +
'}';
}
}
class StreamConsumer extends Thread {
InputStream is;
List<String> list;
StreamConsumer(InputStream is) {
this.is = is;
}
StreamConsumer(InputStream is, List<String> list) {
this.is = is;
this.list = list;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line ;
while ((line = br.readLine()) != null) {
if (list != null) {
list.add(line);
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
/**
* 監(jiān)控SD卡狀態(tài)
* @author Administrator
*
*/
private class SDStateMonitorReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
//存儲卡被卸載
if(Intent.ACTION_MEDIA_UNMOUNTED.equals(intent.getAction())){
if(CURR_LOG_TYPE == SDCARD_TYPE){
Log.d(TAG, "SDCard is UNMOUNTED");
CURR_LOG_TYPE = MEMORY_TYPE;
new LogCollectorThread().start();
}
}else{
//存儲卡被掛載
if(CURR_LOG_TYPE == MEMORY_TYPE){
Log.d(TAG, "SDCard is MOUNTED");
CURR_LOG_TYPE = SDCARD_TYPE;
new LogCollectorThread().start();
}
}
}
}
/**
* 日志任務接收廣播
* 切換日志颂碧,監(jiān)控日志大小
* @author Administrator
*
*/
class LogTaskReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//切換日志文件action
if(SWITCH_LOG_FILE_ACTION.equals(action)){
new LogCollectorThread().start();
}else if(MONITOR_LOG_SIZE_ACTION.equals(action)){
//日志文件監(jiān)測action
checkLogSize();
}
}
}
class FileComparator implements Comparator<File> {
public int compare(File file1, File file2) {
if(logServiceLogName.equals(file1.getName())){
return -1;
}else if(logServiceLogName.equals(file2.getName())){
return 1;
}
String createInfo1 = getFileNameWithoutExtension(file1.getName());
String createInfo2 = getFileNameWithoutExtension(file2.getName());
try {
Date create1 = sdf.parse(createInfo1);
Date create2 = sdf.parse(createInfo2);
if(create1.before(create2)){
return -1;
}else{
return 1;
}
} catch (ParseException e) {
return 0;
}
}
}
}