Android流媒体开发之 自定义一个完备的log模块
Android音視頻開發之 自定義一個完備的log模塊
- 前言
- 基礎知識的掌握
- Log系統
- 為什么需要自定義一個log模塊呢?
- 做什么?
- 怎么做?
- 確定成員變量:
- 初始化LogUtil
- 輸出功能的實現
- write方法
- 創建log/txt文件并且初始化IO流:
- 自動清理七天產生的log
- 壓縮
- 上傳:
- 總結:
前言
目前我自己的工作方向是基于Andriod適配層的音視頻開發,那有關這個系列的博客都是我在實際的工作中遇到的一些問題和逐漸學習的過程,并且我也將會一直持續更新下去。
基礎知識的掌握
作為一個音視頻開發方向的程序員,無論基于何種OS(Android,IOS,Mac,Window,etc…)都需要去了解和音視頻相關的基礎知識,例如音頻視頻的編解碼方式,主流編解碼器的實現原理,音視頻相關的參數等等許多。對于我而言我更傾向于遇到問題之后再去仔細的學習,畢竟在開始之前,誰都不知道自己會遇到什么樣的問題,也不能真切的體會到自己在某些方面的不足。
Log系統
Log系統是我在工作中遇到的第一個任務,在我看來無論是何種應用任何方向,一個完善的Log模塊都十分的重要。
為什么需要自定義一個log模塊呢?
你好!作為一個Android應用的研發者,在搞代碼的時候經常會使用到Log.d(w,e,i,v …) 等的語句輔助我們搞開發。無論是判斷數據流向,程序執行順序還是出現問題的時候通過logcat里面的信息對功能模塊進行調整。如果僅僅是自己悶頭自閉開發,使用系統的log和看AS的logcat的確足夠了。但是要記住我們是一個研發者,開發的app是要給用戶使用的。
做什么?
5.自動清理 :日志的數量不應該過多,防止占用用戶過多的存儲空間,只需要能確保記錄下出現崩潰情況的日志即可。
怎么做?
確定成員變量:
public class LogUtil {// 默認當前日志優先級為 2 也就是Verbose級private static int currentLevel=2;// 默認日志內容輸出到logcatprivate static boolean out2logcat = true;// 默認寫入本地文件.private static boolean is2Write = false;//用于執行IO操作的線程池 推薦使用緩存線程池。private static ThreadPoolExecutor mExecutor = null;// 日志文件的輸出路徑 eg : /storge/ andoird/emulator/0/data/packageName/fileprivate static String absFile = null;// 單個日志文件的名字private static String logFileName = null;// 標記LogUitil是否進行過初始化private static boolean initFlag = false;// IOprivate static FileOutputStream fos = null;private static OutputStreamWriter osWritter= null;private static BufferedWriter bw = null;// 壓縮文件的絕對路徑private static String sDestinationPath;// 壓縮文件的名字private static String sZipFilePathName;private static Throwable IOException;private static String sZip2FatherFilePath;// 用于格式化日期的工具private static SimpleDateFormat sSimpleDateFormat = null;private static SimpleDateFormat sSp = null; }那這些都比較好理解,會在下面功能完善的過程中逐漸使用到。寫到這里的目的是下面我就不想寫注釋了= =。
初始化LogUtil
/*** initialize util.* @param context 2 get the abs path* @param level 2 control view of log* @param out2logcat true means output log to logcat* @param isWrite2File write 2 file or not*/public static void intializeLogUtil(Context context, int level, boolean out2logcat, boolean isWrite2File) {//初始化線程池if (mExecutor == null) {mExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();}//標記log已經被初始化initFlag = true;//初始化日期工具類if (sSp == null) {sSp = new SimpleDateFormat("yyyyMMdd-HHmmss");}if (sSimpleDateFormat == null) {sSimpleDateFormat = new SimpleDateFormat("MM:dd HH:mm:ss.SSS");}//創建日志文件File externalFilesDir = context.getExternalFilesDir(null);if (externalFilesDir!=null&&externalFilesDir.exists()) {absFile = externalFilesDir.getPath();}else{absFile = Environment.getExternalStorageDirectory().getPath();}//初始化日志優先級if(level>8)currentLevel = 8;else if(level<2)currentLevel = 2;elsecurrentLevel = level;//初始化是否輸出到日志和是否輸出到文件LogUtil.out2logcat = out2logcat;is2Write = isWrite2File;// 初始化zip文件的目錄if (sZip2FatherFilePath == null) {sZip2FatherFilePath = absFile+"/lbyLogZip";}//初始化日志文件的目錄if (sLogFileDirName == null) {sLogFileDirName = absFile+"/lbyLogFile";}// 創建文件File file1 = new File(sLogFileDirName);File file = new File(sZip2FatherFilePath);if (!file.exists()) {file.mkdir();}if (!file1.exists()) {file1.mkdir();}}這里留給外部調用的初始化接口,通過傳入的各種參數初始化log工具,傳入的context用于獲取創建log文件存放的目錄,創建zip文件傳入的目錄。
輸出功能的實現
為了要像原來用Log.d等的語句一樣,用起來簡單方便,還要實現功能的完善,所以可能需要重載很多方法,比較好理解 因為d w i 等等的實現都是一樣的,所以這里只貼出一種實現的方式
/*** handle exception.* @param tag* @param msg* @param is2Write whether 2 wirte* @param throwable System exception, we care about exception much more that other's.*/public static void i(Object tag, String msg,boolean is2Write,Exception throwable){if (currentLevel > LogLevel.INFO.val)return;String strTAG ;//判斷傳入的tag的類型,如果不是string或者class,默認獲取tag 傳入的class的名字if(tag instanceof String){strTAG = tag.toString();}else if(tag instanceof Class) {strTAG = ((Class) tag).getSimpleName();}else{strTAG = tag.getClass().getName();}//判斷是否輸出到logcatif(out2logcat){Log.i(strTAG,msg);}//判斷是否輸出到文件if(is2Write){//write 方法。write(strTAG,msg,LogLevel.INFO.val,throwable);}}/*** use default write config* @param tag* @param msg*/public static void i(Object tag, String msg){//如果未傳入是否寫文件,則使用默認的配置i(tag,msg,is2Write);}/*** depend on the currentLevel .** @param tag{Object} use class name or simple string.* @param msg{String} log msg.* @param is2Write whether 2 wirte*///如果未傳入異常,則認為異常為空public static void i(Object tag, String msg,boolean is2Write){i(tag,msg,is2Write,null);}//如果未傳入寫文件,則使用默認配置public static void i(Object tag, String msg,Exception throwable){i(tag,msg,iswWrite,throwable);}(d w e v等同理)可以看到目前重載了4個方法,也就是說可以通過4個不同的入口進行Log的輸出,也保留的默認的入口方便使用,默認的入口使用在 初始化 階段已經進行了配置,傳入 異常 的情況是在進行try catch的時候傳入異常進行棧打印。保證調用log模塊的簡單和人性化
write方法
/*** when d v i e w etc . are invoked, LogUtil will write the log * info 2 local file(after setAbsPath()are invoked),* if set() haven't called , and it is needed to write the log* info into file, it will write to timeStamp_LogBeforeSetAbsPath.txt;* intializeLog() offer a default params to be the whole controler* @param tag just tag* @param msg log info* @param level log priority* @param expectation expectioin.*/private static void write(String tag,String msg,int level,Exception expectation){// 如果log被在 初始化 之前調用, (注意這里是文件 不是目錄,在初始化階段只// 設置了父目錄,并未創建單個log文件)if (logFileName==null||logFileName.isEmpty()) {if(sSp!=null) {logFileName = sSp.format(new Date(System.currentTimeMillis())) + "_LogBeforeSetAbsPath.txt";File file = new File(sLogFileDirName + "/" + logFileName);if (!file.exists()) {try {file.createNewFile();fos = new FileOutputStream(file);} catch (java.io.IOException e) {e.printStackTrace();}osWritter = new OutputStreamWriter(fos);bw = new BufferedWriter(osWritter);}}}if(bw == null) {return;}String now = sSimpleDateFormat.format(new Date(System.currentTimeMillis()));try {//輸出log到文件的log的格式bw.write(now+ " "+ Thread.currentThread().getName()+" "+level+"/"+tag+":");bw.write(msg);bw.newLine();bw.flush();osWritter.flush();fos.flush();} catch (IOException e) {e.printStackTrace();}if(expectation!=null){try {solveTheException(expectation);} catch (IOException e) {e.printStackTrace();}}}_write()_方法實現打印msg到文件內。并且為了防止單個log文件還沒有被創建而造成的異常,write會自行判斷文件是否存在,如果不存在那就先創建再寫入。
創建log/txt文件并且初始化IO流:
/*** set absPath , used in Engine.joinRoom(), when user join a room, create a new LogFile and initWritter.* and we can also create a new File in these method.* @param unixStamp not absPath , just file name. eg:"123123(format time)_roomID_uid.text"* ...*/public static boolean setAbsPath(Long unixStamp,String roomID,String userID){if(!initFlag) {return false;}fos =null;osWritter =null;bw = null;String absPath1 = sSp.format(new Date(unixStamp))+"_"+roomID+"_"+userID+".txt";if(absPath1!=null){logFileName = absPath1;File file = new File(sLogFileDirName+"/"+logFileName);if(!file.exists()) {try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}}//initialized the writters ,try {fos = new FileOutputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}osWritter = new OutputStreamWriter(fos);bw = new BufferedWriter(osWritter);return true;}return false;}這里可能會有人問為什么不再創建父目錄的時候直接將單個文件創建出來,emm,因產品而異,畢竟創建文件的時候可能會需要一些參數, 但是這些參數在調用init 方法的時候并沒有,所以說設置了,但是初始化方法需要的參數 也就是context ,可能在創建單個文件的時候沒有,所以分為兩步。如果對文件名沒有過多的要求的話, 自然也可以合二為一。不過文件名對于我來說其實挺重要的,因為log文件可能會很多,也就是說一個用戶在登陸應用直到退出應用的時候雖然只產生一個,但是如果用戶一天5次進入應用呢就會產生5個文件,如果用戶的量有10w個呢,就會有太多太多的log文件需要去找,打包的目的也是為了能夠快速的定位到問題用戶的zip文件(里面裝的全都是用戶幾天內使用應用的產生的log),并且還要在多個log中找到用戶出現問題那一次所產生的log,那如果不對文件名進行規范,或者容易查找的話,簡直大海撈針,比debug還痛苦。
所以我這里的策略就是規范命名規則 使用timeStamp_deviceModule (20180909_1111_RedMiNote3),這樣如果用戶告訴我大概在哪一天,使用的是什么手機,我就可以迅速的在許許多多個文件中找到問題用戶的文件,找到出現問題的日志信息然后解決問題。
自動清理七天產生的log
這個方法在初始化log之后進行調用,防止用戶產生過多的無用的log。
/*** auto clean the log files which are more than 7 days.*/public static void cleanLogFile(){File file = new File(sLogFileDirName);//獲取今天的時間long today = System.currentTimeMillis()/(1000*60*60*24);//這里注意一下 f這里使用的file 必須是一個目錄級 文件, 所以增加一個判斷String[] children = file.list();// 判斷獲取到的文件名列表是不是空。if(children == null) return;mExecutor.execute(new Runnable() {@Overridepublic void run() {for(String childrenFileName:children){String[] s = childrenFileName.split("_");try {//由于我的命名規則是 日期_設備號 所以我獲取第一位日期//計算是否大于七天再考慮進行刪除long formal = sSp.parse(s[0]).getTime()/(1000*60*60*24);if(Math.abs(today-formal)>=7)File file1 = new File(sLogFileDirName+"/"+childrenFileName);if (file1.delete()) {Log.d("lbTest","delete success"+file1.getName());}else{Log.d("lbTest","delete fail"+file1.getName());}}} catch (ParseException e) {e.printStackTrace();}}}});}壓縮
/*** @param observer async method , when zip is finished, observer.onLogFileReady()'ll be called.* @return if LogUtil haven't been initialized, it will return false, needed be init before use.*/public static boolean zipLogFile(LivePlayerObserver observer){// needed initialize.if(!initFlag) {return false;}SimpleDateFormat sp = new SimpleDateFormat("yyyyMMdd-HHmmss");String s = Build.MODEL.replaceAll(" ", "");sZipFilePathName = sp.format(new Date(System.currentTimeMillis())).toString()+"_"+s+".zip";sDestinationPath = sZip2FatherFilePath+"/"+sZipFilePathName;LBYzip(sDestinationPath,observer);return true;}private static void LBYzip(String destinationPath, LivePlayerObserver observer) {mExecutor.execute(new Runnable() {@Overridepublic void run() {try {zip(sLogFileDirName,destinationPath);String[] zipFileList = getZipFileList();if(zipFileList!=null&&zipFileList.length!=0){observer.onLogFileReady(zipFileList);}else{observer.error(Errors.E40002);}} catch (IOException e) {e.printStackTrace();}}});}public static String[] getZipFileList(){File file = new File(sZip2FatherFilePath);if(file.exists()) {return file.list();}return null;}/*** @param src source file abs path* @param dest destnation file path .* @throws IOException*/private static void zip(String src, String dest) throws IOException {File tempT = new File(src);if(tempT.exists()){tempT.delete();}ZipOutputStream out = null;try {File outFile = new File(dest);File fileOrDirectory = new File(src);out = new ZipOutputStream(new FileOutputStream(outFile));if (fileOrDirectory.isFile()) {zipFileOrDirectory(out, fileOrDirectory, "");} else {File[] entries = fileOrDirectory.listFiles();for (int i = 0; i < entries.length; i++) {zipFileOrDirectory(out, entries[i], "");}}} catch (IOException ex) {ex.printStackTrace();} finally {if (out != null) {try {out.close();} catch (IOException ex) {ex.printStackTrace();}}}}提供zip的輸出路徑,提供一個oberver告訴外面的調用者log壓縮的成功或者失敗,如果成功就提供一個壓縮好的路徑;如果失敗就通過回調告訴給外邊的失敗處理機制。
上傳:
對于先前說的上傳功能,由于不同的服務器接收的post原則不同,這里就不貼出自己的代碼了,畢竟okhttp很好用~
總結:
那這篇分享就到此結束了。
總結
以上是生活随笔為你收集整理的Android流媒体开发之 自定义一个完备的log模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2.GraphPad Prism界面介绍
- 下一篇: 关于iis中域名转向的问题