重拾Android之路之日志封裝StackTrace


引言

對(duì)于日志打印严嗜,之前都是使用Log.i()的方式。今天來(lái)探究下StackTraceElement獲取方法調(diào)用棧的信息仪媒。

痛點(diǎn):編寫(xiě)項(xiàng)目的時(shí)候狸眼,肯定會(huì)或多或少的使用Log捻爷,尤其是發(fā)現(xiàn)bug的時(shí)候,會(huì)連續(xù)在多個(gè)類(lèi)中打印Log信息份企,當(dāng)問(wèn)題解決了也榄,然后需要一行一行地去刪除剛才隨便添加的Log,有時(shí)候還要幾個(gè)輪回才能刪除干凈司志。

當(dāng)然了甜紫,我們有很多方案可以不去刪除:

  • 我們可以通過(guò)gradle去配置debug、release常量去區(qū)分
  • 可以對(duì)Log進(jìn)行一層封裝骂远,通過(guò)debug開(kāi)關(guān)常量來(lái)控制

所以囚霸,我們的需求是這樣的:

  1. 可以對(duì)Log封裝,通過(guò)debug開(kāi)關(guān)來(lái)控制正常日志信息的輸出
  2. 在修bug時(shí)激才,用于定位雜亂的log日志拓型,我們希望可以在bug解除后额嘿,很快的定位到,然后刪除滅跡劣挫。

一册养、什么是StackTrace

StackTrace(堆棧軌跡)存放的就是方法調(diào)用棧的信息,異常處理中常用的printStackTrace()實(shí)質(zhì)就是打印異常調(diào)用的堆棧信息压固。

二球拦、StackTraceElement介紹

StackTraceElement表示StackTrace(堆棧軌跡)中的一個(gè)方法對(duì)象,屬性包括方法的類(lèi)名帐我、方法名坎炼、文件名以及調(diào)用的行數(shù)。

public final class StackTraceElement implements java.io.Serializable {  
    // Normally initialized by VM (public constructor added in 1.5)  
    private String declaringClass;  
    private String methodName;  
    private String fileName;  
    private int    lineNumber;  
}

StackTraceElement被定義為final拦键,可見(jiàn)其作為一個(gè)Java的基礎(chǔ)類(lèi)不允許被繼承谣光。

獲取StackTraceElement的方法有兩種,均返回StackTraceElement數(shù)組芬为,也就是這個(gè)棧的信息萄金。

1、Thread.currentThread().getStackTrace()

2碳柱、new Throwable().getStackTrace()

StackTraceElement數(shù)組包含了StackTrace(堆棧軌跡)的內(nèi)容,通過(guò)遍歷它可以得到方法間的調(diào)用過(guò)程熬芜,即可以得到當(dāng)前方法以及其調(diào)用者的方法名莲镣、調(diào)用行數(shù)等信息

StackTraceElement數(shù)組,你可以理解為當(dāng)我們調(diào)用方法的時(shí)候涎拉,每進(jìn)入一個(gè)方法瑞侮,會(huì)將該方法的相關(guān)信息(例如:類(lèi)名,方法名鼓拧,方法調(diào)用行數(shù)等)存儲(chǔ)下來(lái)半火,壓入到一個(gè)棧中,當(dāng)方法返回的時(shí)候再將其出棧季俩。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        a();
    }

    void a() {
        b();
    }

    void b() {
        StringBuffer err = new StringBuffer();
        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
        for (int i = 0; i < stack.length; i++) {
            err.append("\tat ");
            err.append(stack[i].toString());
            err.append("\n");
        }
        Log.e("TAG", err.toString());
    }

我在onCreate中钮糖,調(diào)用了a方法,然后a中調(diào)用的b方法酌住。在b方法中打印出當(dāng)前線程中的棧幀集合信息店归。

at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:579)
at com.zxy.recovery.test.MainActivity.b(MainActivity.java:26)
at com.zxy.recovery.test.MainActivity.a(MainActivity.java:21)
at com.zxy.recovery.test.MainActivity.onCreate(MainActivity.java:17)
at android.app.Activity.performCreate(Activity.java:5231)
...

可以看到我們整個(gè)方法的調(diào)用過(guò)程,底部的最先開(kāi)始調(diào)用酪我,順序?yàn)閛nCreate->a->b->Thread.getStackTrace->VMStack.getThreadStackTrace.

最后兩個(gè)是因?yàn)槲覀兊膕tacks是在VMStack.getThreadStackTrace方法中獲取消痛,然后返回的,所以包含了這兩個(gè)的內(nèi)部調(diào)用信息都哭。

上面的例子便于理解在文末代碼中的編寫(xiě)邏輯秩伞。

三逞带、用途

1、我們可以封裝一個(gè)日志庫(kù)纱新,在打印目標(biāo)日志的時(shí)候展氓,也可以通過(guò)這個(gè)調(diào)用棧打印出這個(gè)日志所在的行數(shù),這樣就可以迅速的定位到日志輸出行怒炸,再也不要全局搜索去查找了带饱。

public static void d(String tag, String msg, Object... params) {
    StackTraceElement targetStackTraceElement = getTargetStackTraceElement();
    Log.d(tag, "(" + targetStackTraceElement.getFileName() + ":"
            + targetStackTraceElement.getLineNumber() + ")");
    Log.d(tag, String.format(msg, params));
}

2、如果我們寫(xiě)了一個(gè)SDK阅羹,希望某個(gè)方法在固定的位置被調(diào)用勺疼,我們也可以在這個(gè)方法被調(diào)用的時(shí)候,進(jìn)行檢查捏鱼,看這個(gè)方法的調(diào)用位置是否正確执庐。

例如,必須在Activity.onResume中執(zhí)行导梆,PVSdk.onResume轨淌,所以我們?cè)谡{(diào)用PVSdk.onResume方法的時(shí)候,在PVSdk.onResume方法里面來(lái)通過(guò)獲取調(diào)用棧的信息檢測(cè)這個(gè)方法是否在Activity的onResume方法中調(diào)用的看尼。

public class PVSdk {

    public static void onResume() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        boolean result = false;
        for (StackTraceElement stackTraceElement : stackTrace) {
            String methodName = stackTraceElement.getMethodName();
            String className = stackTraceElement.getClassName();
            try {
                boolean assignableFromClass = Class.forName(className).isAssignableFrom(Activity.class);
                if (assignableFromClass && "onResume".equals(methodName)) {
                    result = true;
                    break;
                }
            } catch (ClassNotFoundException e) {

            }
        }
        if (!result)
            throw new RuntimeException("PVSdk.onResume must in Activity.onResume");
    }
}

3递鹉、我們?cè)谶M(jìn)行源碼分析的時(shí)候,如果想分析整個(gè)代碼的執(zhí)行流程藏斩,我們可以進(jìn)行通過(guò)打印棧的信息來(lái)獲取躏结,這個(gè)在源碼分析的時(shí)候還是挺有用的。


推薦一下

我們今天要談的就是Log的封裝狰域,當(dāng)然封裝不僅僅是是上述的好處媳拴,我們還可以讓使用更加便捷,打出來(lái)的Log信息展示的更加優(yōu)雅兆览。

比如:

這個(gè)庫(kù)屈溉,就對(duì)Log的信息的展示做了非常多的處理,展示給大家是一個(gè)非常nice的效果:

最后附上核心代碼:

    public static void i(String msg) {
        if (!isDebug) return;
        StackTraceElement targetStackTraceElement = getTargetStackTraceElement();
        Log.i(TAG, "(" + targetStackTraceElement.getFileName() + ":"
                + targetStackTraceElement.getLineNumber() + ")");
        Log.i(TAG, msg);
    }
    private static StackTraceElement getTargetStackTraceElement() {
        // find the target invoked method
        StackTraceElement targetStackTrace = null;
        boolean shouldTrace = false;
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            boolean isLogMethod = stackTraceElement.getClassName().equals(L.class.getName());
            if (shouldTrace && !isLogMethod) {
                targetStackTrace = stackTraceElement;
                break;
            }
            shouldTrace = isLogMethod;
        }
        return targetStackTrace;
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抬探,一起剝皮案震驚了整個(gè)濱河市子巾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌小压,老刑警劉巖砰左,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異场航,居然都是意外死亡缠导,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)溉痢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)僻造,“玉大人憋他,你說(shuō)我怎么就攤上這事∷柘鳎” “怎么了竹挡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)立膛。 經(jīng)常有香客問(wèn)我揪罕,道長(zhǎng),這世上最難降的妖魔是什么宝泵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任好啰,我火速辦了婚禮,結(jié)果婚禮上儿奶,老公的妹妹穿的比我還像新娘框往。我一直安慰自己,他們只是感情好闯捎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布椰弊。 她就那樣靜靜地躺著,像睡著了一般瓤鼻。 火紅的嫁衣襯著肌膚如雪秉版。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天茬祷,我揣著相機(jī)與錄音清焕,去河邊找鬼。 笑死牲迫,一個(gè)胖子當(dāng)著我的面吹牛耐朴,可吹牛的內(nèi)容都是我干的借卧。 我是一名探鬼主播盹憎,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼铐刘!你這毒婦竟也來(lái)了陪每?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镰吵,失蹤者是張志新(化名)和其女友劉穎檩禾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疤祭,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盼产,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勺馆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戏售。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侨核,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灌灾,到底是詐尸還是另有隱情搓译,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布锋喜,位于F島的核電站些己,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嘿般。R本人自食惡果不足惜段标,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望博个。 院中可真熱鬧怀樟,春花似錦、人聲如沸盆佣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)共耍。三九已至虑灰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痹兜,已是汗流浹背穆咐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留字旭,地道東北人对湃。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像遗淳,于是被迫代替她去往敵國(guó)和親拍柒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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