Android 11 應(yīng)用創(chuàng)建日志文件失敗

應(yīng)用背景:

本應(yīng)用日志使用的是xlog組件,在app初始化時初始化xlog組件栈妆,傳入了緩存日志文件路徑初肉、日志文件路徑、日志文件名三個關(guān)鍵參數(shù)荆责,xlog會根據(jù)這三個參數(shù)生成緩存日志文件和日志文件滥比。緩存日志文件類型為.mmap3,日志文件類型為.xlog做院,app可以調(diào)用xlog的flush函數(shù)將緩存日志文件內(nèi)容寫入日志文件盲泛;日志文件名是以當前進程的名字為基礎(chǔ),將進程名中的"."替換為了"_"键耕,這樣做的目的是后臺的日志系統(tǒng)對于文件命名有要求寺滚,不允許文件名在除后綴名之外出現(xiàn)".",由此傳入的兩個日志文件名為:

package_name:service 
package_name

之前考慮到為了在app卸載后日志文件依然存在屈雄,使用的日志存儲路徑為手機的外部存儲的公有空間村视,地址為
/storage/emulated/0/xxx/appname/logs/

android存儲空間背景:

這里使用了知乎老哥的一個圖簡單介紹一下背景

image.jpeg

內(nèi)部存儲:

/data目錄,對用戶不可見酒奶,即使使用adb依然不可查看蚁孔,只有root過的手機可以查看⊥锖浚或者是debug版本的app可以在androidstudio上使用Device File Explorer查看杠氢。內(nèi)部存儲空間本身就是為了保護應(yīng)用本身的隱私而設(shè)計的,所有設(shè)備上的內(nèi)部存儲空間都是始終可用的瘸彤,在存儲應(yīng)用所依賴的數(shù)據(jù)時更為可靠修然;app訪問自己的內(nèi)部存儲空間不需要權(quán)限笛钝,其他應(yīng)用無法訪問质况,Shared Preferences和SQLite數(shù)據(jù)庫就是存儲在內(nèi)部存儲空間;在app卸載時內(nèi)部存儲空間的所有文件都會被刪掉玻靡,緩存性文件會在設(shè)備存儲空間不足時被刪除结榄,所以文件放在緩存性目錄下隨時可能被刪除。獲取內(nèi)部存儲空間的路徑為:

context.filesDir()囤捻,/data/data/包名/files/ // 持久性文件根目錄 路徑在不同的手機上可能會不同
context.cacheDir(), /data/data/包名/cache/ // 緩存性文件根目錄

外部存儲:

/storage 目錄臼朗,外部存儲也分為公有空間和私有空間。

私有空間:

存儲應(yīng)用私有數(shù)據(jù),外部存儲應(yīng)用私有目錄對應(yīng)Android/data/包名视哑。此路徑文件在android 11前可以直接在手機的文件管理下查看绣否,在android 11中不可見,但可以使用adb查看挡毅。和內(nèi)部存儲空間一樣也分為持久性目錄和緩存性目錄蒜撮,需要注意的是在android 10 開啟分區(qū)存儲后,應(yīng)用只能訪問自身的私有空間跪呈,即使獲得了存儲權(quán)限也不能訪問其他應(yīng)用的私有空間段磨。在應(yīng)用卸載后系統(tǒng)會自動移除這些目錄釋放空間。獲取私有空間的路徑為:

getExternalFilesDirs(@NonNull Context context, @Nullable String type) // 持久性文件 /storage/emulated/0/Android/data/包名/files 根據(jù)傳入的type不同返回路徑不同 傳入null則返回files路徑
getExternalCacheDirs(context) // 緩存性文件 /storage/emulated/0/Android/data/包名/cache

公有空間:

如果應(yīng)用的數(shù)據(jù)可供其他應(yīng)用訪問耗绿,則使用共享存儲空間苹支。具體的訪問方法變化如下:

//外部存儲公有目錄的訪問方法 
//分區(qū)存儲開啟前 
getExternalStorageDirectory() // 分區(qū)存儲開啟之前 獲得讀寫權(quán)限后 獲取路徑使用File直接操作 

//android 10 開啟分區(qū)存儲 
MediaStore // 訪問媒體集合 訪問其他應(yīng)用文件需要權(quán)限 READ_EXTERNAL_STORAGE 
Storage Access Framework // 存儲訪問框架 訪問文檔和其他文件 使用時會出現(xiàn)系統(tǒng)提供的文檔列表頁面提供給用戶操作 關(guān)鍵在于獲取文件的Uri 

//android 11 應(yīng)用targetSdkVersion >= 30 強制開啟分區(qū)存儲 
File API // android 11重新開啟了使用file訪問媒體文件的方法 不過還是會重定向到MediaStore 造成性能影響 
fopen() // 加入了原生庫的訪問 MANAGE_EXTERNAL_STORAGE // 針對手機管家、文件管理器等app提供的外部存儲管理權(quán)限

在分區(qū)存儲開啟之前可以使用getExternalStorageDirectory()獲取路徑進行操作误阻,而在android 10開啟分區(qū)存儲后债蜜,關(guān)閉了使用獲取路徑讀取文件的方法,官方推薦的方法為使用MediaStore訪問媒體資源究反,使用Storage Access Framework訪問文檔和其他文件策幼。android 11更新后又重新加入了使用File訪問文件路徑的方法,但是會重定向到MediaStore奴紧。

問題:

在android 11的手機中主進程可以寫入日志文件特姐,但是服務(wù)進程不會寫入日志文件,包括mmap文件和xlog文件黍氮,導致日志選擇上傳的時候失敗唐含。

問題分析:

  • 由于android 11開啟了強制分區(qū)存儲導致的文件讀寫問題?

android 11雖然開啟了強制分區(qū)存儲沫浆,但是只是針對targetSdk >= 30的情況捷枯,而本應(yīng)用的targetSdk為28,并未開啟強制分區(qū)存儲专执;
主進程可以正常寫入文件說明文件寫入沒有問題淮捆;
經(jīng)過試驗也可以在服務(wù)進程在外部存儲公有空間創(chuàng)建文件,無論是使用File API還是通過JNI的fopen方法均可完成本股;
由此可以判斷與分區(qū)存儲無關(guān)攀痊。

但是將日志存儲路徑更換為外部存儲應(yīng)用的私有空間后,兩個進程的日志文件都創(chuàng)建成功了拄显。所以真的和分區(qū)存儲無關(guān)嗎苟径?

  • xlog的mmap打開失敗躬审?

為了排除xlog版本老舊的問題棘街,我從gradle引入了新版本的xlog蟆盐,依然存在上述問題;通過查看xlog的原理文檔和xlog的github源碼可知遭殉,Xlog初始化經(jīng)過了以下步驟

Xlog.open // 應(yīng)用初始化Xlog命令 傳入多個參數(shù) 
appenderOpen(level, mode, cacheDir, logDir, nameprefix, 0, pubkey) // Xlog.class 一個JNI方法 JNIEXPORT void JNICALL Java_com_tencent_mars_xlog_Xlog_appenderOpen // Java2C_Xlog.cc 獲取傳入的參數(shù)構(gòu)造一個XlogConfig對象 
appender_open(const XLogConfig& _config) // appender.cc 初始化一個XloggerAppender對象 
XloggerAppender::NewInstance(const XLogConfig& _config) // appender.cc 調(diào)用構(gòu)造函數(shù) 
XloggerAppender::XloggerAppender(const XLogConfig& _config) // appender.cc 調(diào)用Open函數(shù) 
XloggerAppender::Open(const XLogConfig& _config) // appender.cc 調(diào)用openMmapFile打開mmap 
OpenMmapFile(const char* _filepath, unsigned int _size, boost::iostreams::mapped_file& _mmmap_file) // mmap_util.cc 調(diào)用mmap的open函數(shù) _mmmap_file.open(param) // mmap_util.cc 再向下深入就進入了boost iostream庫使用mmap寫入映射文件的流程 
IsMmapFileOpenSucc(const boost::iostreams::mapped_file& _mmmap_file) // mmap_util.cc 判斷mmap是否open成功

通過以上流程分析可以發(fā)現(xiàn)石挂,問題最大可能是出現(xiàn)在了mmap.open函數(shù)中,如果打開失敗就是創(chuàng)建mmap文件失敗险污,那么程序自然就不可能將緩存通過mmap寫入映射文件誊稚。那么為了驗證open的結(jié)果,需要獲取到IsMmapFileOpenSucc的結(jié)果罗心,而為了獲取這個結(jié)果就需要加入打印日志代碼將xlog重新打包測試了里伯。為了達成這個目的:

  1. 在github下載mars源碼,自己編譯xlog庫渤闷。

分析mars的源碼疾瓮,可以發(fā)現(xiàn)使用的是cmake編譯,可以實現(xiàn)很好的跨平臺效果飒箭,在代碼主目錄下可以看到編譯的腳本文件如下狼电。

image.jpeg

將代碼下載后執(zhí)行腳本文件,出現(xiàn)的問題有

(1)找不到NDK_ROOT路徑弦蹂,在電腦的.bash_profile文件中配置肩碟;和之前配置的是NDK_HOME路徑一樣
(2)ifaddrs.h類報錯,找不到結(jié)構(gòu)體ifaddrs凸椿;查看ifaddrs.h代碼里面沒有聲明ifaddrs結(jié)構(gòu)體削祈,但是查看文件的提交可以發(fā)現(xiàn)在之前的版本是有這個結(jié)構(gòu)體的,只是在一次更新中替換成了如下include代碼脑漫,在電腦的ndk文件中搜索這個類髓抑,顯示的結(jié)果如下。所以理論上編譯不應(yīng)該出現(xiàn)問題优幸,為了解決這個問題我嘗試將ndk ifaddrs結(jié)構(gòu)體粘貼到mars ifaddrs.h文件中吨拍,編譯通過生成so庫,但是這里還是留下了疑問网杆。

// mars/comm/jni/ifaddrs.h 
#include <ifaddrs.h> 

//ndk搜索ifaddrs.h 里面包含了提示缺失的結(jié)構(gòu)體 
struct ifaddrs {
 /** Pointer to the next element in the linked list. */
 struct ifaddrs* ifa_next;

 /** Interface name. */
 char* ifa_name;
 /** Interface flags (like `SIOCGIFFLAGS`). */
 unsigned int ifa_flags;
 /** Interface address. */
 struct sockaddr* ifa_addr;
 /** Interface netmask. */
 struct sockaddr* ifa_netmask;

 union {
   /** Interface broadcast address (if IFF_BROADCAST is set). */
   struct sockaddr* ifu_broadaddr;
   /** Interface destination address (if IFF_POINTOPOINT is set). */
   struct sockaddr* ifu_dstaddr;
 } ifa_ifu;

 /** Unused. */
 void* ifa_data;
};
/** Synonym for `ifa_ifu.ifu_broadaddr` in `struct ifaddrs`. */
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
/** Synonym for `ifa_ifu.ifu_dstaddr` in `struct ifaddrs`. */
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
  1. 獲得xlog的java層代碼

為了獲得xlog的java層jar包羹饰,先找到了引入gradle加載的xlog arr庫,雖然不能在androidstudio的External libraries中直接查看碳却,但是可以獲取到包的路徑队秩,復制后更改后綴名為zip打開,取出可以看到的文件目錄如下追城,classes.jar就是我們需要的java層代碼刹碾,放入應(yīng)用的lib中即可燥撞。

一開始引入了一個mars-xlog-1.x-source.jar文件座柱,一直引入失敗迷帜。。

image.jpeg
  1. 插入日志打印mmap open結(jié)果是否成功

為了打印日志色洞,在Java_com_tencent_mars_xlog_Xlog_appenderOpen函數(shù)中加入了打印日志方法測試戏锹,分別嘗試了mars自帶的兩種方法和jni打印android日志的方法如下,全部失敗火诸。

// mars里使用的打印日志方法 本質(zhì)也是調(diào)用 __android_log_print 失敗 
xerror2(TSF"hello from JNI");
LOGD("testxlog", "-------user define:%s--------", "hello from JNI"); // 有個開關(guān)恒為false改為了true 

// 自己加入的 __android_log_print 失敗 
printf 
LOGDD("LOG from JNI");

可以看到mars的日志是可以正常輸出的锦针,但是上面的四種方法卻失敗了,所以會不會是日志打印位置的原因置蜀?此時日志上未初始化奈搜?又留下了一個疑問

最后嘗試將日志放入xlog的寫入JNI函數(shù)JNICALL Java_com_tencent_mars_xlog_Xlog_logWrite2中打印,日志打印成功盯荤。

LOGDD("LOG from JNI"); 
LOGDD("is use mmap %d", XloggerAppender::is_use_mmap);

最后在appender.cc中加入了全局靜態(tài)變量保存mmap open結(jié)果馋吗,在write函數(shù)中獲取結(jié)果打印∏锍樱可以看到有兩個進程一個進程open成功一個進程open失敗宏粤,那就一定是mmap在android 11的適配問題了嗎?

image.jpeg

問題原因:

最終在導師的幫忙下定位到了問題灼卢,拉取了全部的日志绍哎,發(fā)現(xiàn)在打印mmap日志前有一個奇怪的系統(tǒng)日志如下(由于我之前過濾日志沒注意過,也沒有把全部日志輸出到一個文件里仔細查看)鞋真,MediaProvider報錯如下崇堰。


image.png

查看android 11 MediaProvider源碼,發(fā)現(xiàn)了如下流程

MediaProvider getAbsoluteSanitizedPath // 處理后的path和原path不相同 報錯 
FileUtils getAbsoluteSanitizedPath sanitizePath // 以 / 作為分隔符 將路徑分成一塊塊的數(shù)組 數(shù)組項調(diào)用下面處理 
sanitizeDisplayName // 以點開頭的轉(zhuǎn)為下劃線返回名字(這里考慮的是會不會點開頭的是隱藏文件涩咖?)繼續(xù)調(diào)用 buildValidFatFilename 
buildValidFatFilename // 會修改文件名將所有的無效字符替換為 "_" 使得對FAT文件系統(tǒng)有效 遍歷每個字符赶袄,其中特殊字符包括 ":" 都被替換為了 "_" 特殊字符如下
image.jpeg

最終返回的路徑與原路徑不匹配,自然就會文將創(chuàng)建失敗。為了驗證這個問題,打開android 11 的文件管理器汗洒,嘗試在手機外部存儲公共空間修改文件名加入":"商佑,提示特殊字符無效。

查看android 10的代碼滤祖,未發(fā)現(xiàn)如上流程,也說明了為什么這個bug只和android 11有關(guān)。

總結(jié):

問題的本質(zhì)是我們使用進程名作為文件名溉跃,而進程名中帶有":",導致文件創(chuàng)建失敗告抄。并且不只是":"撰茎,許多其他的字符如上圖都屬于公共空間文件名的不合法字符,但是在應(yīng)用的私有空間是不存在這個問題的打洼,這可能是由于在分區(qū)存儲后公有空間和私有空間文件訪問走的是兩套機制龄糊,訪問公有空間要通過MediaStore逆粹,但是訪問私有空間并不需要,自然就沒有了這個文件名稱的校驗炫惩。

對于這個問題的解決方案一開始就很清晰僻弹,就是將日志路徑設(shè)置到應(yīng)用的私有空間,但是在為了找尋問題出現(xiàn)的原因他嚷,確實費了一番力氣蹋绽。

還存在的問題:

mars源碼中為什么不能在Java_com_tencent_mars_xlog_Xlog_appenderOpen中打印日志?
ifaddrs類為什么會引入失斀畋汀卸耘?
mmap的原理?

這些可能只有等以后自己懂得更多才能研究了粘咖。鹊奖。

鏈接如下:
mars代碼
https://github.com/Tencent/mars
android mediaprovider代碼
https://android.googlesource.com/platform/packages/providers/MediaProvider/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涂炎,隨后出現(xiàn)的幾起案子忠聚,更是在濱河造成了極大的恐慌,老刑警劉巖唱捣,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件两蟀,死亡現(xiàn)場離奇詭異,居然都是意外死亡震缭,警方通過查閱死者的電腦和手機赂毯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拣宰,“玉大人党涕,你說我怎么就攤上這事⊙采纾” “怎么了膛堤?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晌该。 經(jīng)常有香客問我肥荔,道長,這世上最難降的妖魔是什么朝群? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任燕耿,我火速辦了婚禮,結(jié)果婚禮上姜胖,老公的妹妹穿的比我還像新娘誉帅。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布蚜锨。 她就那樣靜靜地躺著档插,像睡著了一般。 火紅的嫁衣襯著肌膚如雪踏志。 梳的紋絲不亂的頭發(fā)上阀捅,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天胀瞪,我揣著相機與錄音针余,去河邊找鬼。 笑死凄诞,一個胖子當著我的面吹牛圆雁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帆谍,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼伪朽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汛蝙?” 一聲冷哼從身側(cè)響起烈涮,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窖剑,沒想到半個月后坚洽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡西土,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年讶舰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片需了。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡跳昼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肋乍,到底是詐尸還是另有隱情鹅颊,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布墓造,位于F島的核電站挪略,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏滔岳。R本人自食惡果不足惜杠娱,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谱煤。 院中可真熱鬧摊求,春花似錦、人聲如沸刘离。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茧痕,卻和暖如春野来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踪旷。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工曼氛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人令野。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓舀患,卻偏偏與公主長得像,于是被迫代替她去往敵國和親气破。 傳聞我的和親對象是個殘疾皇子聊浅,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Timber Google官方Demo使用的日志庫 詳細用法參考: Timber Timber說明: 默認的Tre...
    A代碼搬運工閱讀 1,598評論 0 0
  • 背景 在Android開發(fā)過程中難免會需要日志輸出的,日志在開發(fā)調(diào)試现使、異常跟蹤以及排查問題上都有很大的幫助低匙,但是打...
    Coder蔣閱讀 3,245評論 0 1
  • 看完了微信團隊對Xlog的整體介紹,迫不及待開始了研究碳锈,理論部分我是完全參考微信終端跨平臺組件 mars 系列(一...
    星期五__閱讀 9,367評論 2 17
  • title: App技術(shù)選型--日志框架 版 本 歷 史 日志對于開發(fā)來說是非常重要的顽冶,不管是調(diào)試數(shù)據(jù)查看、bug...
    海南雞閱讀 1,452評論 0 0
  • 同步地址 本文介紹 MARS xlog 使用以及使用過程中踩過的坑 xlog 是什么 xlog 是微信開源框架 M...
    Noah牛YY閱讀 12,027評論 2 0