編輯:關於Android編程
前言:該文接上兩篇博文App的打磨之路(上)和App的打磨之路(中),繼續描述打包、反編譯及加固。
每個Android應用在完成後都需要打成APK包,對於單個打包的方式在此就不贅述了,基本IDE都帶,只是在對外發布的應用需要配置屬於該應用的唯一簽名,下文主要講述需要上傳多個市場的情況下怎麼批量打包。
Maven是一個項目管理工具,它包含了一個項目對象模型(Project Object Model),一組標准集合,一個項目生命周期(ProjectLifecycle),一個依賴管理系統(Dependency Management System),和用來運行定義在生命周期階段(phase)中插件(plugin)目標(goal)的邏輯。
Maven也是自動構建工具,配合使用android-maven-plugin插件,以及maven-resources-plugin插件可以很方便的生成渠道包,下面簡要介紹下打包過程。
首先,在AndroidManifest.xml的節點中添加如下元素,用來定義渠道的來源:
定義好渠道來源後,接下來就可以在程序啟動時讀取渠道號了:
private String getChannel(Context context) { try { PackageManager pm = context.getPackageManager(); ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); return appInfo.metaData.getString("channel"); } catch (PackageManager.NameNotFoundException ignored) { } return ""; }
要替換AndroidManifest.xml文件定義的渠道號,還需要在pom.xml文件中配置Resources插件:
${project.basedir} true ${project.build.directory}/filtered-manifest AndroidManifest.xml
准備工作已經完成,現在需要的就是實際的渠道號了。下面的腳本會遍歷渠道列表,逐個替換並打包:
#!/bin/bash package(){ while read line do mvn clean mvn -Dchannel=$line package done < $1 } package $1
從以上描述中可以看出該方式每打一個包都會重新構建,執行效率太低,對於少量渠道還可以接受,渠道包過多就沒法滿足需求了。
Apktool是一個逆向工程工具,可以用它解碼(decode)並修改apk中的資源。接下來詳細介紹如何使用apktool生成渠道包。
前期工作和用Maven打包一樣,也需要在AndroidManifest.xml文件中定義
首先,使用apktool decode應用程序,在終端中輸入如下命令:
apktool d your_original_apk build
上面的命令會在build目錄中decode應用文件,decode完成後的目錄描述如下:
目錄
描述
assets目錄
存放需要打包到apk中的靜態文件
lib目錄
程序依賴的native庫
res目錄
存放應用程序的資源
smail目錄
存放Dalvik VM內部執行的smail代碼
AndroidManifest.xml
應用程序的配置文件
apktool.yml
apktool相關配置文件
接下來,替換AndroidManifest.xml文件中定義的渠道號,下面是一段python腳本:
import re
def replace_channel(channel, manifest):
pattern = r'()'
replacement = r"\g<1>{channel}\g<3>".format(channel=channel)
return re.sub(pattern, replacement, manifest)
更多有關Python的使用可參考Python教程。
然後,使用apktool構建未簽名的apk:
apktool b build your_unsigned_apk
最後,使用jarsigner重新簽名apk:
jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore your_keystore_path -storepass your_storepass -signedjar your_signed_apk, your_unsigned_apk, your_alias
上面就是使用apktool打包的方法,通過使用腳本可以批量地生成渠道包。不像Maven,每打一個包都需要執行一次構建過程,該方法只需構建一次,大大節省了時間,但缺點是每生成一個包需要重新簽名一次。
3、批量快速打包
如果能直接修改APK的渠道號,而不需要再重新簽名能節省不少打包的時間。上文APK瘦身中講述過APK解壓後的目錄結構,其中有個META-INF目錄,是存放簽名相關信息用來校驗APK的完整性的,如果在META-INF目錄內添加空文件,可以不用重新簽名應用。因此,通過為不同渠道的應用添加不同的空文件,可以唯一標識一個渠道。
下面的python代碼用來給apk添加空的渠道文件,渠道名的前綴為channel_:
import zipfile
zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED)
empty_channel_file = "META-INF/channel_{channel}".format(channel=your_channel)
zipped.write(your_empty_file, empty_channel_file)
假設渠道名為test,則添加完空渠道文件後META-INFO目錄多了一個名為channel_test的空文件:
接下來就可以在代碼中讀取空渠道文件名了:
public static String getChannel(Context context) {
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
String ret = "";
ZipFile zipfile = null;
try {
zipfile = new ZipFile(sourceDir);
Enumeration entries = zipfile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.startsWith("channel")) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String[] split = ret.split("_");
if (split != null && split.length >= 2) {
return ret.substring(split[0].length() + 1);
} else {
return "";
}
}
這樣,每打一個渠道包只需復制一個apk,在META-INF中添加一個使用渠道號命名的空文件即可。
更多關於打包詳情可參考AndroidMultiChannelBuildTool.
4、Gradle定制化打包
關於Gradle多渠道打包可以參考我的另一篇博文Android Studio常用Gradle操作,下面主要講解如何根據各個渠道不同的需求來定制化打包,如控制是否自動更新,使用不同的包名、應用名等。
使用不同的包名
如應用test有兩個不同的包名,分別是com.example.test1和com.example.test2,需要對應上傳到市場t1和t2,那麼在productFlavors中進行如下描述:
productFlavors {
t1 {
applicationId "com.example.test1"
}
t2 {
applicationId "com.example.test1"
}
}
上面的代碼添加了兩個渠道,兩個渠道的包名不同,運行gradle assemble命令即可生成兩個不同渠道的適配包。
控制是否自動更新
有些客戶端在啟動時會默認檢查客戶端是否有更新,如果有更新就會提示用戶下載。但是有些渠道和應用市場不允許這種默認行為,所以在適配這些渠道時需要禁止自動更新功能。一般的解決思路是提供一個配置字段,應用啟動的時候檢查該字段的值以決定是否開啟自動更新功能。
Gradle會在generateSources階段為flavor生成一個BuildConfig.java文件。BuildConfig類默認提供了一些常量字段,比如應用的版本名(VERSION_NAME),應用的包名(PACKAGE_NAME)等。更強大的是,開發者還可以添加自定義的一些字段。下面的示例假設t3市場默認禁止自動更新功能:
android {
defaultConfig {
buildConfigField "boolean", "AUTO_UPDATES", "true"
}
productFlavors {
t3 {
buildConfigField "boolean", "AUTO_UPDATES", "false"
}
}
}
上面的代碼會在BuildConfig類中生成AUTO_UPDATES布爾常量,默認值為true,在使用t3渠道時,該值會被設置成false。接下來就可以在代碼中使用AUTO_UPDATES常量來判斷是否開啟自動更新功能了。最後,運行gradle assembleT3命令即可生成默認不開啟自動升級功能的渠道包。
使用不同的資源
最常見的一類適配是修改應用的資源,如不同的應用名稱、不同的logo、不同的啟動頁等。
Gradle在構建應用時,會優先使用flavor所屬dataSet中的同名資源。所以,解決思路就是在flavor的dataSet中添加同名的字符串資源,以覆蓋默認的資源。下面以適配t4渠道的應用名為Example2為例進行介紹。
首先,在build.gradle配置文件中添加如下flavor:
android {
productFlavors {
t4 {
}
}
}
上面的配置會默認src/t4目錄為t4 flavor的dataSet。
接下來,在src目錄內創建t4目錄,並添加如下應用名字符串資源(src/t4/res/values/appname.xml):
Example2
默認的應用名字符串資源如下(src/main/res/values/strings.xml):
Example1
最後,運行gradle assembleT4命令即可生成應用名為Example2的應用了。
使用第三方SDK
某些渠道會要求客戶端嵌入第三方SDK來滿足特定的適配需求,假設渠道t5需要引用com.example.test3:test:1.0.0該庫,那麼可以像如下這樣描述:
android {
productFlavors {
t5 {
}
}
}
...
dependencies {
provided 'com.example.test3:test:1.0.0'
t5Compile 'com.example.test3:test:1.0.0'
}
上面添加了名為t5的flavor,並且指定編譯和運行時都依賴
com.example.test3:test:1.0.0。而其他渠道只是在構建的時候依賴該SDK,打包的時候並不會添加它。
接下來,需要在代碼中使用反射技術判斷應用程序是否添加了該SDK,從而決定是否要顯示該SDK提供的功能。部分代碼如下:
class MyActivity extends Activity {
private boolean useSdk;
@override
public void onCreate(Bundle savedInstanceState) {
try {
Class.forName("com.example.test3.Test");
useSdk = true;
} catch (ClassNotFoundException ignored) {
}
}
}
最後,運行gradle assembleT5命令即可生成包含該SDK功能的渠道包了。
二、反編譯
1、原理
反編譯,又稱為逆向編譯技術,是指將可執行文件變成高級語言源程序的過程。反編譯技術依賴於編譯技術,是編譯過程的逆過程。
編譯程序把一個源程序翻譯成目標程序的工作過程分為五個階段:詞法分析;語法分析;語義檢查和中間代碼生成;代碼優化;目標代碼生成。詞法分析的任務是對由字符組成的單詞進行處理,從左至右逐個字符地對源程序進行掃描,產生一個個的單詞符號,把作為字符串的源程序改造成為單詞符號串的中間程序。語法分析以單詞符號作為輸入,分析單詞符號串是否形成符合語法規則的語法單位,如表達式、賦值、循環等,最後看是否構成一個符合要求的程序。語義分析是審查源程序有無語義錯誤,為代碼生成階段收集類型信息。中間代碼是源程序的一種內部表示,或稱中間語言。中間代碼的作用是可使編譯程序的結構在邏輯上更為簡單明確,特別是可使目標代碼的優化比較容易實現。代碼優化是指對程序進行多種等價變換,使得從變換後的程序出發,能生成更有效的目標代碼。目標代碼生成是編譯的最後一個階段。
反編譯器分為前端和後端,前端是一個機器依賴的模塊,包含句法分析二進制程序、分析其指令的語義、並且生成該程序的低級中間表示法和每一子程序的控制流向圖,通用的反編譯機器是一個與語言和機器無關的模塊,分析低級中間代碼,將它轉換成對任何高級語言都可接受的高級表示法,並且分析控制流向圖的結構、把它們轉換成用高級控制結構表現的圖;而後端是一個目標語言依賴的模塊,生成目標語言代碼。
2、語言介紹
C++、C語言一般不能反編譯為源代碼,只能反編譯為asm(匯編)語言,因為C較為底層,編譯之後不保留任何元信息,而計算機運行的二進制實際上就代表了匯編指令,所以反編譯為匯編是較為簡單的。
C#、Java這類高級語言,尤其是需要運行環境的語言,如果沒有混淆,非常容易反編譯。原因很簡單,這類語言只會編譯為中間語言(C#為MSIL,Java為Bytecode),而中間語言與原語言本身較為相似,加上保留的元信息(記錄類名、成員函數等信息)就可以反向生成源代碼,注意是由反編譯器生成,不會與源代碼完全相同,但可以編譯通過。這些特性本來是為反射技術准備的,卻被反編譯器利用,現在的C#反編譯器ILSpy甚至可以反向工程。
3、工具
dex2jar 這個工具用於將dex文件轉換成jar文件
下載地址:http://sourceforge.net/projects/dex2jar/files/ jd-gui 這個工具用於將jar文件轉換成java代碼
下載地址:http://jd.benow.ca/ apktool 這個工具用於最大幅度地還原APK文件中的9-patch圖片、布局、字符串等等一系列的資源
下載地址:http://ibotpeaches.github.io/Apktool/install/
4、反編譯過程
4.1、解壓APK,獲得其中的classes.dex文件;
4.2、拷貝classes.dex文件到dex2jar工具的解壓目錄下,使用如下命令:d2j-dex2jar classes.dex獲得classes-dex2jar.jar文件;
4.3、使用工具jd-gui打開classes-dex2jar.jar文件,如果代碼未被混淆,那麼打開後就可以對除資源外的源碼進行分析了;
4.4、將APK拷貝到apktool的解壓目錄下,使用命令apktool -d ***.apk,其中d是decode的意思,表示我們要對
***.apk這個文件進行解碼。這樣可得到一個以APK名稱命名的目錄,該目錄下就是解碼後的結果了,其中的資源都是可以查看的。apktool命令除了這個基本用法之外,我們還可以再加上一些附加參數來控制decode的更多行為:
-f 如果目標文件夾已存在,則強制刪除現有文件夾(默認如果目標文件夾已存在,則解碼失敗)。
-o 指定解碼目標文件夾的名稱(默認使用APK文件的名字來命名目標文件夾)。
-s 不反編譯dex文件,也就是說classes.dex文件會被保留(默認會將dex文件解碼成smali文件)。
-r 不反編譯資源文件,也就是說resources.arsc文件會被保留(默認會將resources.arsc解碼成具體的資源文件)。
4.5、假如我們修改了解碼後的部分代碼或資源中的內容需要重新打包,那麼則使用命令
apktool b *** -o New_***.apk進行打包;
4.6、打包後還不能安裝,需要重新進行簽名,簽名過程上文已描述過,在此就不贅述該過程了;
4.7、Android還極度建議我們對簽名後的APK文件進行一次對齊操作,因為這樣可以使得我們的程序在Android系統中運行得更快,對齊操作使用的是zipalign工具,該工具存放於/build-tools/目錄下,對齊使用命令如下:zipalign 4 New_***.apk New_***_aligned.apk,其中4是固定值。
注:以上所寫***都表示該APK的名稱,還有以上所描述過程僅用作技術交流,僅限於學習。
三、加固
Android中的Apk反編譯可能是每個開發都會經歷的事,但是在反編譯的過程中,對於源程序的開發者來說那是不公平的,那麼Apk加固也是應運而生,現在網上有很多Apk加固的第三方平台,如以下所示:
愛加密加固
360加固
梆梆加固
其實加固有些人認為很高深的技術,其實不然,說的簡單點就是對源Apk進行加密,然後在套上一層殼即可,當然還有很多細節需要處理,其簡單介紹如下:
1、加殼程序
任務:對源程序Apk進行加密,合並脫殼程序的Dex文件 ,然後輸入一個加殼之後的Dex文件
語言:任何語言都可以,不限於Java語言
技術點:對Dex文件格式的解析
2、脫殼程序
任務:獲取源程序Apk,進行解密,然後動態加載進來,運行程序
語言:Android項目(Java)
技術點:如何從Apk中獲取Dex文件,動態加載Apk,使用反射運行Application
目前來說,不管是混淆、加密還是加固都不完全是安全的,不管何時,逆向和安全都永遠不會停止戰爭。但對於一般的應用來說,混淆和加固基本就可以保證你應用的安全了,因為不管是出於什麼原因都是需要考慮時間和人力成本的。
最近有一段時間沒寫博客了,一方面是工作比較忙,一方面也著實本人水平有限,沒有太多能與大家分享的東西,也就是在最近公司要做一個搶紅包的功能,老板發話了咋們就開干呗,本人就開
一、簡介 TextureMapFragment:用於顯示地圖片段。 二、示例3--Demo03MapFragment.cs 文件名:Demo
onReceiveError是WebViewClient提供的方法,用於網頁產生錯誤時進行回調處理。1. 舊版的onReceiveError在API23之前,該方法的簽名
關於Windows下Android開發環境搭建、配置方面文章,網上一搜一堆,為方便以後參考,權且做個記錄,主要關注安裝過程中的注意事項。對新手提醒的是,本文