Gradle中混淆的使用

在 Android 日常開(kāi)發(fā)過(guò)程中荚孵,混淆是我們開(kāi)發(fā) App 的一項(xiàng)必不可少的技能尘应。只要是我們親身經(jīng)歷過(guò) App 打包上線的過(guò)程泽裳,或多或少都需要了解一些代碼混淆的基本操作桩蓉。那么蝉稳,混淆到底是什么抒蚜?它的好處有哪些?具體效果如何耘戚?別急嗡髓,下面我們來(lái)一一探索它的"獨(dú)特"魅力。


混淆簡(jiǎn)介

代碼混淆Obfuscated code)是將程序中的代碼以某種規(guī)則轉(zhuǎn)換為難以閱讀和理解的代碼的一種行為收津。

混淆的好處

混淆的好處就是它的目的:令 APK 難以被逆向工程饿这,即很大程度上增加反編譯的成本浊伙。此外,Android 當(dāng)中的"混淆"還能夠在打包時(shí)移除無(wú)用資源长捧,顯著減少 APK 體積嚣鄙。最后,還能以變通方式避免 Android 中常見(jiàn)的 64k 方法數(shù)引用的限制串结。

我們先來(lái)看一下混淆前后的 APK 結(jié)構(gòu)對(duì)比:

安卓_AS中混淆使用_內(nèi)容1.png
安卓_AS中混淆使用_內(nèi)容2.png

從上面兩張圖可以看出:經(jīng)過(guò)混淆處理之后哑子,我們的 APK 中包名、類(lèi)名肌割、成員名等都被替換為隨機(jī)卧蜓、無(wú)意義的名稱,增加了代碼閱讀和理解的困難程度把敞,提高了反編譯的成本弥奸。細(xì)心的小伙伴可能又會(huì)注意到:混淆前后 APK 的體積竟然從 2.7M 減小到了 1.4M,體積縮減了近一倍奋早!真的有這么神奇嗎盛霎?哈哈,確實(shí)是這么神奇伸蚯,讓我們慢慢來(lái)揭開(kāi)它的神秘面紗吧摩渺。


Android 當(dāng)中的混淆

在 Android 中,我們平常所說(shuō)的"混淆"其實(shí)有兩層意思剂邮,一個(gè)是 Java 代碼的混淆摇幻,另外一個(gè)是資源的壓縮。其實(shí)這兩者之間并沒(méi)有什么關(guān)聯(lián)挥萌,只不過(guò)習(xí)慣性地放在一起來(lái)使用绰姻。那么,說(shuō)了這么多引瀑,Android 平臺(tái)上到底該如何開(kāi)啟混淆呢狂芋?

啟用混淆

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

以上就是開(kāi)啟混淆的基本操作了,通過(guò) minifyEnabled 設(shè)置為 true 來(lái)開(kāi)啟混淆憨栽。同時(shí)帜矾,可以設(shè)置 shrinkResourcestrue 來(lái)開(kāi)啟資源的壓縮。不難看出屑柔,我們一般在打 release 包時(shí)才啟用混淆屡萤,因?yàn)榛煜龝?huì)增加額外的編譯時(shí)間,所以不建議在 debug 模式下啟用掸宛。此外死陆,需要注意的是:只有在啟用混淆的前提下開(kāi)啟資源壓縮才會(huì)有效!以上代碼中的 proguard-android.txt 表示 Android 系統(tǒng)為我們提供的默認(rèn)混淆規(guī)則文件唧瘾,而 proguard-rules.pro 則是我們想要自定義的混淆規(guī)則措译,至于如何自定義混淆規(guī)則我們將在接下來(lái)會(huì)講到别凤。

代碼混淆

其實(shí),Java 平臺(tái)為我們提供了 Proguard 混淆工具來(lái)幫助我們快速地對(duì)代碼進(jìn)行混淆领虹。根據(jù) Java 官方介紹规哪,Proguard 對(duì)應(yīng)的具體中文定義如下:

  • 它是一個(gè)包含代碼文件壓縮優(yōu)化掠械、混淆校驗(yàn)等功能的工具
  • 它能夠檢測(cè)并刪除無(wú)用的類(lèi)由缆、變量、方法和屬性
  • 它能夠優(yōu)化字節(jié)碼并刪除未使用的指令
  • 它能夠?qū)㈩?lèi)猾蒂、變量和方法的名字重命名為無(wú)意義的名稱從而達(dá)到混淆效果
  • 最后均唉,它還會(huì)校驗(yàn)處理后的代碼,主要針對(duì) Java 6 及以上版本和 Java ME

資源壓縮

Android 中肚菠,編譯器為我們提供了另外一項(xiàng)強(qiáng)大的功能:資源的壓縮舔箭。資源壓縮能夠幫助我們移除項(xiàng)目及依賴倉(cāng)庫(kù)中未使用到的資源,有效地降低了apk包的大小蚊逢。由于資源壓縮與代碼混淆是協(xié)同工作层扶,所以,如果需要開(kāi)啟資源的壓縮烙荷,切記要先開(kāi)啟代碼混淆镜会,否則會(huì)出現(xiàn)以下問(wèn)題:

CopyERROR: Removing unused resources requires unused code shrinking to be turned on. See http://d.android.com/r/tools/shrink-resources.html for more information.
Affected Modules: app

自定義要保留的資源

當(dāng)我們開(kāi)啟了資源壓縮之后,系統(tǒng)會(huì)默認(rèn)替我們移除所有未使用的資源终抽,假如我們需要保留某些特定的資源戳表,可以在我們項(xiàng)目中創(chuàng)建一個(gè)被 <resources> 標(biāo)記的 XML 文件(如 res/raw/keep.xml),并在 tools:keep 屬性中指定每個(gè)要保留的資源昼伴,在 tools:discard 屬性中指定每個(gè)要舍棄的資源匾旭。這兩個(gè)屬性都接受逗號(hào)分隔的資源名稱列表。同樣圃郊,我們可以使用字符 \* 作為通配符价涝。如:

Copy<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/activity_video*,@layout/dialog_update_v2"
    tools:discard="@layout/unused_layout,@drawable/unused_selector" />

啟用嚴(yán)格檢查模式

正常情況下,資源壓縮器可準(zhǔn)確判定系統(tǒng)是否使用了資源持舆。不過(guò)色瘩,如果您的代碼(包含庫(kù))調(diào)用 Resources.getIdentifier(),這就表示您的代碼將根據(jù)動(dòng)態(tài)生成的字符串查詢資源名稱逸寓。這時(shí)居兆,資源壓縮器會(huì)采取防御性行為,將所有具有匹配名稱格式的資源標(biāo)記為可能已使用席覆,無(wú)法移除。例如汹买,以下代碼會(huì)使所有帶 img_ 前綴的資源標(biāo)記為已使用:

CopyString name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

這時(shí)佩伤,我可以開(kāi)啟資源的嚴(yán)格審查模式聊倔,只會(huì)保留確定已使用的資源耙蔑。

移除備用資源

Gradle 資源壓縮器只會(huì)移除未被應(yīng)用引用的資源,這意味著它不會(huì)移除用于不同設(shè)備配置的備用資源甸陌。必要時(shí),我們可以使用 Android Gradle 插件的 resConfigs 屬性來(lái)移除您的應(yīng)用不需要的備用資源文件(常見(jiàn)的有用于國(guó)際化支持的 strings.xml盐股,適配用的 layout.xml 等):

Copyandroid {
    defaultConfig {
        ...
        //保留中文和英文國(guó)際化支持
        resConfigs "en", "zh"
    }
}

自定義混淆規(guī)則

品嘗完了以上"配菜"钱豁,下面讓我們來(lái)品味一下本文的"主菜":自定義混淆規(guī)則。首先疯汁,我們來(lái)了解一下常見(jiàn)的混淆命令牲尺。

keep 命令

這里說(shuō)的 keep 命令指的是一系列以 -keep 開(kāi)頭的命令,它主要用來(lái)保留 Java 中不需要進(jìn)行混淆的元素幌蚊。以下是常見(jiàn)的 -keep 命令:

  • -keep

    作用:保留指定的類(lèi)和成員谤碳,防止被混淆處理。例如:

Copy# 保留包:com.moos.media.entity 下面的類(lèi)以及類(lèi)成員
-keep public class com.moos.media.entity.**

# 保留類(lèi):NumberProgressBar -keep public class com.moos.media.widget.NumberProgressBar {*;}
  • -keepclassmembers

    作用:保留指定的類(lèi)的成員(變量/方法)溢豆,它們將不會(huì)被混淆蜒简。如:

Copy# 保留類(lèi)的成員:MediaUtils類(lèi)中的特定成員方法
-keepclassmembers class com.moos.media.MediaUtils {
    public static *** getLocalVideos(android.content.Context);
    public static *** getLocalPictures(android.content.Context);
}
  • -keepclasseswithmembers

    作用:保留指定的類(lèi)和其成員(變量/方法),前提是它們?cè)趬嚎s階段沒(méi)有被刪除漩仙。與-keep 使用方式類(lèi)似:

Copy# 保留類(lèi):BaseMediaEntity 的子類(lèi)
-keepclasseswithmembers public class * extends com.moos.media.entity.BaseMediaEntity{*;}

# 保留類(lèi):OnProgressBarListener接口的實(shí)現(xiàn)類(lèi)
-keep public class * implements com.moos.media.widget.OnProgressBarListener {*;}
  • @Keep

    除了以上方式搓茬,你也可以選擇使用 @Keep 注解來(lái)保留期望代碼,防止它們被混淆處理讯赏。比如垮兑,我們通過(guò) @Keep 修飾一個(gè)類(lèi)來(lái)保留它不被混淆:

Copy@Keep
data class CloudMusicBean(var createDate: String,
                          var id: Long,
                          var name: String,
                          var url: String,
                          val imgUrl: String)

同樣地,我們也可以讓 @Keep 來(lái)修飾方法或者字段進(jìn)而保留它們漱挎。

其他命令

  1. dontwarn

    -dontwarn 命令一般在我們引入新的 library 時(shí)會(huì)使用到系枪,常用于處理 library 中無(wú)法解決的警告。如:

Copy-keep class twitter4j.** { *; }

-dontwarn twitter4j.**
  1. 其他的命令用法可參考 Android 系統(tǒng)提供的默認(rèn)混淆規(guī)則:
#混淆時(shí)不生成大小寫(xiě)混合的類(lèi)名
-dontusemixedcaseclassnames

#不跳過(guò)非公共的庫(kù)的類(lèi)
-dontskipnonpubliclibraryclasses

#混淆過(guò)程中記錄日志
-verbose

#關(guān)閉預(yù)校驗(yàn)
-dontpreverify

#關(guān)閉優(yōu)化
-dontoptimize

#保留注解
-keepattributes *Annotation*

#保留所有擁有本地方法的類(lèi)名及本地方法名
-keepclasseswithmembernames class * {
    native <methods>;
}

#保留自定義View的get和set方法
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

#保留Activity中View及其子類(lèi)入?yún)⒌姆椒牧拢? onClick(android.view.View)
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

#保留枚舉
-keepclassmembers enum * {
    **[] $VALUES;
    public *;
}

#保留序列化的類(lèi)
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

#保留R文件的靜態(tài)成員
-keepclassmembers class **.R$* {
    public static <fields>;
}
   
-dontwarn android.support.**

-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

更多混淆命令可以參考文章:《Proguard 最全混淆規(guī)則說(shuō)明》私爷,這里就不做詳細(xì)講解了。


混淆"黑名單"

我們?cè)诹私饬嘶煜幕久钪蟛布校芏嗳藨?yīng)該還是一頭霧水:到底哪些內(nèi)容該混淆衬浑?其實(shí),我們?cè)谑褂么a混淆時(shí)放刨,ProGuard 對(duì)我們項(xiàng)目中大部分代碼進(jìn)行了混淆操作工秩,為了防止編譯時(shí)出錯(cuò),我們應(yīng)該通過(guò) keep 命令保留一些元素不被混淆。所以助币,我們只需要知道哪些元素不應(yīng)該被混淆

枚舉

項(xiàng)目中難免可能會(huì)用到枚舉類(lèi)型浪听,然而它不能參與到混淆當(dāng)中去。原因是:枚舉類(lèi)內(nèi)部存在 values 方法眉菱,混淆后該方法會(huì)被重新命名迹栓,并拋出 NoSuchMethodException。慶幸的是俭缓,Android 系統(tǒng)默認(rèn)的混淆規(guī)則中已經(jīng)添加了對(duì)于枚舉類(lèi)的處理克伊,我們無(wú)需再去做額外工作。想了解更多枚舉內(nèi)部細(xì)節(jié)可以去查看源碼华坦,篇幅有限不再細(xì)說(shuō)愿吹。

被反射的元素

被反射使用的類(lèi)、變量季春、方法洗搂、包名等不應(yīng)該被混淆處理。原因在于:代碼混淆過(guò)程中载弄,被反射使用的元素會(huì)被重命名耘拇,然而反射依舊是按照先前的名稱去尋找元素,所以會(huì)經(jīng)常發(fā)生 NoSuchMethodExceptionNoSuchFiledException 問(wèn)題宇攻。

實(shí)體類(lèi)

實(shí)體類(lèi)即我們常說(shuō)的"數(shù)據(jù)類(lèi)"惫叛,當(dāng)然經(jīng)常伴隨著序列化反序列化操作。很多人也應(yīng)該都想到了逞刷,混淆是將原本有特定含義的"元素"轉(zhuǎn)變?yōu)闊o(wú)意義的名稱嘉涌,所以,經(jīng)過(guò)混淆的"洗禮"之后夸浅,序列化之后的 value 對(duì)應(yīng)的 key 已然變?yōu)闆](méi)有意義的字段仑最,這肯定是我們不希望的。同時(shí)帆喇,反序列化的過(guò)程創(chuàng)建對(duì)象從根本上來(lái)說(shuō)還是借助于反射警医,混淆之后 key 會(huì)被改變,所以也會(huì)違背我們預(yù)期的效果坯钦。

四大組件

Android 中的四大組件同樣不應(yīng)該被混淆预皇。原因在于:

  1. 四大組件使用前都需要在 AndroidManifest.xml 文件中進(jìn)行注冊(cè)聲明,然而混淆處理之后婉刀,四大組件的類(lèi)名就會(huì)被篡改吟温,實(shí)際使用的類(lèi)與 manifest 中注冊(cè)的類(lèi)并不匹配,故而出錯(cuò)突颊。
  2. 其他應(yīng)用程序訪問(wèn)組件時(shí)可能會(huì)用到類(lèi)的包名加類(lèi)名鲁豪,如果經(jīng)過(guò)混淆潘悼,可能會(huì)無(wú)法找到對(duì)應(yīng)組件或者產(chǎn)生異常。

JNI 調(diào)用的Java 方法

當(dāng) JNI 調(diào)用的 Java 方法被混淆后爬橡,方法名會(huì)變成無(wú)意義的名稱挥等,這就與 C++ 中原本的 Java 方法名不匹配,因而會(huì)無(wú)法找到所調(diào)用的方法堤尾。

其他不應(yīng)該被混淆的

  • 自定義控件不需要被混淆
  • JavaScript 調(diào)用 Java 的方法不應(yīng)混淆
  • Java 的 native 方法不應(yīng)該被混淆
  • 項(xiàng)目中引用的第三方庫(kù)也不建議混淆

混淆后的堆棧跟蹤

代碼經(jīng)過(guò) ProGuard 混淆處理后,想要讀取 StackTrace(堆棧追蹤)信息就會(huì)變得很困難迁客。由于方法名稱和類(lèi)的名稱都經(jīng)過(guò)混淆處理郭宝,即使程序發(fā)生崩潰問(wèn)題,也很難定位問(wèn)題所在掷漱。幸運(yùn)的是粘室,ProGuard 為我們提供了補(bǔ)救的措施,在著手進(jìn)行之前卜范,我們先來(lái)看一下 ProGuard 每次構(gòu)建后生成了哪些內(nèi)容衔统。

混淆輸出結(jié)果

混淆構(gòu)建完成之后,會(huì)在 <module-name>/build/outputs/mapping/release/ 目錄下生成以下文件:

  • dump.txt

    說(shuō)明 APK 內(nèi)所有類(lèi)文件的內(nèi)部結(jié)構(gòu)海雪。

  • mapping.txt

    提供混淆前后的內(nèi)容對(duì)照表锦爵,內(nèi)容主要包含類(lèi)、方法和類(lèi)的成員變量奥裸。

  • seeds.txt

    羅列出未進(jìn)行混淆處理的類(lèi)和成員险掀。

  • usage.txt

    羅列出從 APK 中移除的代碼。

恢復(fù)堆棧跟蹤

了解完混淆構(gòu)建完畢后輸出的內(nèi)容之后湾宙,我們現(xiàn)在就來(lái)看一下之前的問(wèn)題:混淆處理后樟氢,StackTrace 定位困難。如何來(lái)恢復(fù) StackTrace 的定位能力呢侠鳄?系統(tǒng)為我們提供了 retrace 工具埠啃,結(jié)合上文提到的 mapping.txt 文件,就可以將混淆后的崩潰堆棧追蹤信息還原成正常情況下的 StackTrace 信息伟恶。主要有兩種方式來(lái)恢復(fù) StackTrace碴开,為了方便理解,我們以下面這段崩潰信息為例知押,借助兩種方式分別來(lái)還原:

Copy java.lang.RuntimeException: Unable to start activity 
     Caused by: kotlin.KotlinNullPointerException
        at com.moos.media.ui.ImageSelectActivity.k(ImageSelectActivity.kt:71)
        at com.moos.media.ui.ImageSelectActivity.onCreate(ImageSelectActivity.kt:58)
        at android.app.Activity.performCreate(Activity.java:6237)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
  1. 通過(guò) retrace 腳本工具

    首先我們要進(jìn)入到 Android SDK 路徑的 /tools/proguard/bin 目錄中叹螟,這里以 Mac 系統(tǒng)為例,可以看到如下內(nèi)容:

安卓_AS中混淆使用_內(nèi)容3.png

可以看到如上三個(gè)文件台盯,而 proguardgui.sh 才是我們需要的 retrace 腳本(Windows系統(tǒng)下為 proguardgui.bat )罢绽。Windows 系統(tǒng)中只需要雙擊腳本 proguardgui.bat 即可運(yùn)行,至于 Mac 系統(tǒng)静盅,如果你沒(méi)有做任何配置良价,只需要將 proguardgui.sh 腳本拖動(dòng)到 Mac 自帶的終端中寝殴,回車(chē)鍵即可運(yùn)行。接著明垢,我們會(huì)看到如下界面:

安卓_AS中混淆使用_內(nèi)容4.png

選擇 ReTrace 欄 蚣常,并添加我們項(xiàng)目中混淆生成的 mapping.txt 文件所在位置,然后將我們的混淆后的崩潰信息復(fù)制到 Obfuscated stack trace 那一欄痊银,點(diǎn)擊 ReTrace! 按鈕即可還原出我們的崩潰日志信息抵蚊,結(jié)果如上圖所示,我們之前的混淆日志:at com.moos.media.ui.ImageSelectActivity.k(ImageSelectActivity.kt:71) 被還原成了 at com.moos.media.ui.ImageSelectActivity.initView(ImageSelectActivity.kt:71)溯革。ImageSelectActivity.k 是我們混淆后的方法名贞绳,ImageSelectActivity.initView 則是最初未混淆前的方法名,借助于 ReTrace 工具的幫助致稀,我們就可以像以前一樣很快定位到崩潰代碼區(qū)域了冈闭。

  1. 通過(guò) retrace 命令行

    我們先要將崩潰信息復(fù)制到 txt 格式的文件(如:proguard_stacktrace.txt)中保存,然后執(zhí)行以下命令即可(MAC系統(tǒng)):

Copyretrace.sh -verbose mapping.txt proguard_stacktrace.txt

如果你是 windows 系統(tǒng)抖单,可以執(zhí)行以下命令:

Copyretrace.bat -verbose mapping.txt proguard_stacktrace.txt

最終還原的結(jié)果和之前效果一樣:

安卓_AS中混淆使用_內(nèi)容5.png

也許你通過(guò)以上兩種方式在對(duì) stackTrace 進(jìn)行恢復(fù)時(shí)萎攒,發(fā)現(xiàn) Unknown Source 問(wèn)題:

安卓_AS中混淆使用_內(nèi)容6.png

值得注意的是,記得在混淆規(guī)則中加上如下配置來(lái)提升我們的 StackSource 查找效率:

Copy# 保留源文件名和具體代碼行號(hào)
-keepattributes SourceFile,LineNumberTable

此外矛绘,我們每次使用 ProGuard 創(chuàng)建發(fā)布構(gòu)建時(shí)都都會(huì)覆蓋之前版本的 mapping.txt 文件耍休,因此我們每次發(fā)布新版本時(shí)都必須小心地保存一個(gè)副本。通過(guò)為每個(gè)發(fā)布構(gòu)建保留一個(gè) mapping.txt 文件副本货矮,我們就可以在用戶提交的已混淆的 StackTrace 來(lái)對(duì)舊版本應(yīng)用的問(wèn)題進(jìn)行調(diào)試和修復(fù)羹应。


漲姿勢(shì)的操作

經(jīng)過(guò)上文的介紹,我們知道次屠,APK 在經(jīng)過(guò)代碼混淆處理后园匹,包名、類(lèi)名劫灶、成員名被轉(zhuǎn)化為無(wú)意義裸违、難以理解的名稱,增加反編譯的成本本昏。Android ProGuard 為我們提供了默認(rèn)的"混淆字典"供汛,即將元素名稱轉(zhuǎn)為英文小寫(xiě)字母的形式。那么涌穆,我們可以定義自己的混淆字典嗎怔昨?賣(mài)個(gè)關(guān)子,我們先來(lái)看一張效果圖:

安卓_AS中混淆使用_內(nèi)容7.png

這個(gè)波操作是不是有點(diǎn)"出類(lèi)拔萃"了宿稀?哈哈趁舀,就不賣(mài)關(guān)子了,其實(shí)很簡(jiǎn)單祝沸,只要生成一套自己的 txt 格式的混淆字典矮烹,然后在混淆規(guī)則 Proguard-rules.pro 中應(yīng)用一下即可:

安卓_AS中混淆使用_內(nèi)容8.png

當(dāng)然越庇,大家也可以自己去定制化自己的"混淆字典",增加反編譯的難度奉狈。

一路走下來(lái)卤唉,我們發(fā)現(xiàn),從混淆技術(shù)的必要性和優(yōu)點(diǎn)來(lái)看仁期,它還是很值得我們?nèi)ド钊雽W(xué)習(xí)和研究的桑驱,本文帶大家領(lǐng)略的僅僅是"冰山一角"。由于本人的技術(shù)水平有限跛蛋,若大家發(fā)現(xiàn)有問(wèn)題或者闡述不當(dāng)之處碰纬,歡迎指出并修正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末问芬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寿桨,更是在濱河造成了極大的恐慌此衅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亭螟,死亡現(xiàn)場(chǎng)離奇詭異挡鞍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)预烙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)墨微,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人扁掸,你說(shuō)我怎么就攤上這事翘县。” “怎么了谴分?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵锈麸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我牺蹄,道長(zhǎng)忘伞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任沙兰,我火速辦了婚禮氓奈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鼎天。我一直安慰自己舀奶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布斋射。 她就那樣靜靜地躺著伪节,像睡著了一般光羞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怀大,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天纱兑,我揣著相機(jī)與錄音,去河邊找鬼化借。 笑死潜慎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蓖康。 我是一名探鬼主播铐炫,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蒜焊!你這毒婦竟也來(lái)了倒信?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泳梆,失蹤者是張志新(化名)和其女友劉穎鳖悠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體优妙,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乘综,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了套硼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卡辰。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖邪意,靈堂內(nèi)的尸體忽然破棺而出九妈,到底是詐尸還是另有隱情,我是刑警寧澤雾鬼,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布允蚣,位于F島的核電站,受9級(jí)特大地震影響呆贿,放射性物質(zhì)發(fā)生泄漏嚷兔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一做入、第九天 我趴在偏房一處隱蔽的房頂上張望冒晰。 院中可真熱鬧,春花似錦竟块、人聲如沸壶运。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒋情。三九已至埠况,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棵癣,已是汗流浹背辕翰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狈谊,地道東北人喜命。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像河劝,于是被迫代替她去往敵國(guó)和親壁榕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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

  • 在 Android 日常開(kāi)發(fā)過(guò)程中赎瞎,混淆是我們開(kāi)發(fā) App 的一項(xiàng)必不可少的技能牌里。只要是我們親身經(jīng)歷過(guò) App 打...
    江偉_404a閱讀 250評(píng)論 0 0
  • 在 Android 日常開(kāi)發(fā)過(guò)程中,混淆是我們開(kāi)發(fā) App 的一項(xiàng)必不可少的技能务甥。只要是我們親身經(jīng)歷過(guò) App 打...
    水月沐風(fēng)閱讀 3,758評(píng)論 3 34
  • Proguard 混淆工具來(lái)幫助我們快速地對(duì)代碼進(jìn)行混淆牡辽。根據(jù) Java 官方介紹,Proguard 對(duì)應(yīng)的具體中...
    獨(dú)自闖天涯的碼農(nóng)閱讀 5,892評(píng)論 0 7
  • 本篇文章:自己在混淆的時(shí)候整理出比較全面的混淆方法缓呛,比較實(shí)用,自己走過(guò)的坑杭隙,淌出來(lái)的路哟绊。請(qǐng)大家不要再走回頭路,可能...
    Zane_Samuel閱讀 55,317評(píng)論 8 93
  • 表情是什么痰憎,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒票髓。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了铣耘,難過(guò)就哭了洽沟。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,154評(píng)論 2 7