一、為什么要國際化
全球化的Internet需要全球化的軟件,全球化的軟件自然也就意味著軟件自身能夠適用于不同的地去和市場,Android應用自然也不例外。
引入國際化的目的是為了在不改變程序自身邏輯功能的前提下提供自適應的、更友好的用戶界面,這樣我們無需為不同語言的國家或地區單獨編寫一套程序,更何況同一個應用程序的代碼邏輯幾乎都是一模一樣的,僅僅為了更改語言而單獨編寫編譯太不值得,這種情況下就用到了我們的應用國際化。
二、為Android應用提供國際化資源
為Android提供國際化不必像Java那樣需要將程序中的標簽、提示信息等放在資源文件中,程序需要支持那些國家、語言環境就需要提供相應的資源文件,因為Android本身就采用了XML資源文件來管理所有的字符串消息,我們只需為各消息提供不同國家、語言對應的內容即可。
例如:如果我們希望某個應用支持簡體中文、美式英語兩種語言環境,那么就在Res目錄下添加values-zh-rCN、values-en-rUS兩個目錄即可。
如果希望應用程序的圖片也能隨國家、語言環境改變,那么為Drawable目錄添加幾個不同的語言國家版本就行了。
如果還需為Drawable目錄按分辨率提供文件夾,則可以再文件夾后面追加分辨率信息,比如drawable-zh-rCN-mdpi、drawable-zh-rCN-hdpi等。
如下圖即是AndroidStudio中為應用進行國際化操作后的視圖,甚至在這里我們可以看到該應用還為不同手機不同的Android版本、不同的分辨率提供了不同樣式、不同尺寸的支持:
三、常見的國際化文件夾名稱
- zh_cn: 簡體中文
- zh_hk: 繁體中文(中國香港)
- zh_tw: 繁體中文(中國臺灣地區)
- en-hk: 英語(香港)
- en_us: 英語(美國)
- en_gb: 英語(英國)
- en_ww: 英語(全球)
- ja_jp: 日語(日本)
- ko_kr: 韓文(韓國)
四、代碼混淆
我們都知道Java是一種跨平臺的、解釋型語言,Java 源代碼編譯成中間”字節碼”存儲于 class 文件中。
由于跨平臺的需要,Java 字節碼中包括了很多源代碼信息,如變量名、方法名,并且通過這些名稱來訪問變量和方法,這些符號帶有許多語義信息,很容易被反編譯成 Java 源代碼。為了防止這種現象,我們可以使用 Java 混淆器對 Java 字節碼進行混淆。
混淆就是對發布出去的程序進行重新組織和處理,使得處理后的代碼與處理前代碼完成相同的功能,而混淆后的代碼很難被反編譯,即使反編譯成功也很難得出程序的真正語義。
被混淆過的程序代碼,仍然遵照原來的檔案格式和指令集,執行結果也與混淆前一樣,只是混淆器將代碼中的所有變量、函數、類的名稱變為簡短的英文字母代號,在缺乏相應的函數名和程序注釋的況下,即使被反編譯,也將難以閱讀。
同時混淆是不可逆的,在混淆的過程中一些不影響正常運行的信息將永久丟失,這些信息的丟失使程序變得更加難以理解。
所以為什么需要代碼混淆呢?
原因很簡單,我們的apk很容易被反編譯出來,我們寫的代碼都會被看到,因此我們需要在編譯過程中對代碼進行一定程度的混淆,使得別人不能反編譯不出我們的代碼。
另外混淆器的作用不僅僅是保護代碼,它也有精簡編譯后程序大小的作用。由于以上介紹的縮短變量和函數名以及丟失部分信息的原因, 編譯后 jar 文件體積大約能減少25% ,這對當前費用較貴的無線網絡傳輸是有一定意義的。
下面介紹下具體混淆過程:
-
新建一個項目,Android Studio默認關閉代碼混淆開關,在build.gradle文件中,如下圖所示的minifyEnabled 開關,因此如果需要混淆代碼,需將false改為true。
minifyEnabled開關 然后在文件proguard-rules.pro添加具體混淆規則,下面是常見的的proguard.cfg配置項:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
# Uncomment this to preserve the line number information for
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
-renamesourcefileattribute SourceFile
-optimizationpasses 5 # 指定代碼的壓縮級別
-dontusemixedcaseclassnames # 混淆時不使用大小寫混合,混淆后的類名為小寫 windows下的同學還是加入這個選項吧(windows大小寫不敏感)
-ignorewarnings # 忽略警告
-dontpreverify # 不做預校驗(Android不需要preverify,去掉這一步可以加快混淆速度)
-verbose # 混淆時是否記錄日志(混淆后會生成映射文件,包含有類名->混淆后類名的映射關系,然后使用printmapping指定映射文件的名稱)
-printmapping priguardMapping.txt
-dontskipnonpubliclibraryclasses # 指定不去忽略非公共的庫的類(默認跳過,有些情況下編寫的代碼與類庫中的類在同一個包下,并且持有包中內容的引用,此時就需要加入此條聲明)
-dontskipnonpubliclibraryclassmembers # 指定不去忽略非公共的庫的類的成員
-dontoptimize # 優化 不優化輸入的類文件
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #混淆時所采用的算法(后面的參數是一個過濾器,這個過濾器是谷歌推薦的算法,一般不改變)
-keepattributes *Annotation* # 保護代碼中的Annotation不被混淆(這在JSON實體映射時非常重要,比如fastJson)
-keepattributes Signature # 避免混淆泛型,如果混淆報錯建議關掉
-keepattributes SourceFile,LineNumberTable # 拋出異常時保留代碼行號
# 保持哪些類不被混淆(不需要混淆系統組件等)
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 如果有引用v4包可以添加下面這兩行
-keep public class * extends android.support.v4.app.Fragment
-keep public class com.null.test.ui.fragment.** {*;}
-keep class com.wgh.willflowaicollection.R{*;}
-keep class com.wgh.willflowaicollection.MyApplication{*;}
-keep class com.wgh.willflowaicollection.ui.MainActivity
-keep class com.wgh.willflowaicollection.ui.SplashActivity
-keep class com.wgh.willflowaicollection.util.PreDefine
-keep class com.wgh.willflowaicollection.helper.**{*;}
-keep class com.wgh.willflowaicollection.greendao.**{*;}
-keep class com.wgh.willflowaicollection.service.**{*;}
-keep class com.wgh.willflowaicollection.model.**{*;}
# 保留Activity中的方法參數是view的方法,
# 從而我們在layout里面編寫onClick就不會影響
-keepclassmembers class * extends android.app.Activity {
public void * (android.view.View);
}
#apk 包內所有 class 的內部結構
-dump class_files.txt
#未混淆的類和成員
-printseeds seeds.txt
#列出從 apk 中刪除的代碼
-printusage unused.txt
#混淆前后的映射
-printmapping mapping.txt
########記錄生成的日志數據,gradle build時 在本項目根目錄輸出-end######
#####混淆保護自己項目的部分代碼以及引用的第三方jar包library#######
#-libraryjars libs/umeng-analytics-v5.2.4.jar
#三星應用市場需要添加:sdk-v1.0.0.jar,look-v1.0.1.jar
# -libraryjars libs/sdk-v1.0.0.jar
# -libraryjars libs/look-v1.0.1.jar
#如果不想混淆 keep 掉
# -keep class com.lippi.recorder.iirfilterdesigner.** {*; }
#友盟
# -keep class com.umeng.**{*;}
#項目特殊處理代碼
#忽略警告
-dontwarn com.lippi.recorder.utils**
#保留一個完整的包
-keep class com.lippi.recorder.utils.** {
*;
}
-keep class com.lippi.recorder.utils.AudioRecorder{*;}
#如果引用了v4或者v7包
-dontwarn android.support.**
####混淆保護自己項目的部分代碼以及引用的第三方jar包library-end####
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
# 保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
## 保護指定的類和類的成員,但條件是所有指定的類和類成員是要存在
-keepclasseswithmembernames class * {
public <init>(android.content.Context);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, int);
}
# 保留自定義控件(繼承自View)不能被混淆
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(***);
*** get* ();
}
# 保持自定義控件類不被混淆
-keepclassmembers class * extends android.widget.ImageView {
public void *(android.view.View);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context);
}
# 保持自定義控件類不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
# 保持自定義控件類不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化的類不能被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable
# 保持 Serializable 不被混淆并且 enum 類也不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保持枚舉 enum 類不被混淆 如果混淆報錯,建議直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * {
public void *ButtonClicked(android.view.View);
}
# 對R文件下的所有類及其方法,都不能被混淆
-keepclassmembers class **.R$* {
*;
}
# 對于帶有回調函數onXXEvent的,不能混淆
-keepclassmembers class * {
void *(**On*Event);
}
#移除log 測試了下沒有用還是建議自己定義一個開關控制是否輸出日志
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
#如果用用到Gson解析包的,直接添加下面這幾行就能成功混淆,不然會報錯。
#gson
# -libraryjars libs/gson-2.3.jar
-keepattributes Signature
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.**{*;}
# -libraryjars libs/VerBank-CSTSv3-ClientAPI.jar #這里這樣寫雖然說是不混淆,但我測試發現,有些類還是混淆了,所以才會寫下面的保持類不混淆的寫法
-dontwarn allone.**
-dontnote allone.**
-keep class allone.**{*;} #注意**和**{*;}的區別, -keep class allone.**這樣只能保留一層文件夾,如果下面有好多文件夾需要保留,需要攜程**{*;}這是我實驗出來,由于缺少必要的資料,這個坑試了好久才發現.
-keep class android.app.enterprise.**{*;}
-keep class android.**{*;}
-keep class com.android.**{*;}
-keep class com.google.**{*;}
-keep class com.google.code.**{*;}
-keep class com.google.android.**{*;}
-keep class com.nostra13.**{*;}
-keep class com.mcxiaoke.**{*;}
-keep class com.squareup.**{*;}
-keep class com.jakewharton
-keep class org.**{*;}
-keep class de.**{*;}
-keep class com.github.bumptech.**{*;}
-keep class com.sun.**{*;}
-keep class junit
-keep class com.jakewharton
-keep class com.baidu.**{ *; }
-keep class org.apache.commons.**{ *; }
-keep class com.qualcomm.**{*;}
-keep class javax.microedition.khronos.**{*;}
-keep class org.codeaurora.**{*;}
-keep class com.hianalytics.**{ *; }
-keep class huawei.android.**{ *; }
-keep class it.sauronsoftware.**{ *; }
-keep class com.xiaomi.**{ *; }
-keep class org.apache.**{ *; }
-keep class maven.com.squareup.**{ *; }
-keep class com.zhy.http.**{ *; }
-keep class okio.**{ *; }
-keep class com.microsoft.**{ *; }
-keep class org.etsi.uri.**{ *; }
-keep class openxmlformats.**{ *; }
-keep class w3.**{ *; }
-keep class schemaorg_apache_xmlbeans.**{ *; }
-keep class com.google.zxing.**{ *; }
-keep class com.google.zxing.**{ *; }
#greendao
-keep class de.greenrobot.daogenerator.**{ *; }
-keep class de.greenrobot.dao.**{*;}
-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# EventBus
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(Java.lang.Throwable);
}
-keepattributes Signature,InnerClasses
-keepclasseswithmembers class io.netty.** {
*;
}
-dontwarn io.netty.**
-dontwarn sun.**
-keep public class [your_pkg].R$*{
public static final int *;
}
感謝優秀的你跋山涉水看到了這里,不如關注下讓我們永遠在一起!