針對安卓APP開發,打包的安卓apk之後發佈應用。但在實現原有功能的前提下,怎麼樣保證apk體量儘可能的小又安全是我們需要追求的極致。
第一步:
使用svg轉vector
可縮放矢量圖形,SVG不會像位圖一樣因爲縮放而讓圖片質量下降。節約空間與內存,常用於簡單小圖標。
1.在Android Studio中使用:
將svg圖片轉換爲VectorDrawable。
2.使用注意兼容性:
在app/build.gradle中添加:
// 使用support-v7兼容
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
implementation 'com.android.support:appcompat-v7:28.0.0'
在佈局中引用:
app:srcCompat="@drawable/icon"
3.靜態VectorDrawable在XML中的定義
官方例子:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="triangle"//定義矢量圖的名稱
android:height="64dp"//drawable的固定高度,支持所有的尺寸單位,一般使用dp
android:width="64dp"//drawable的固定寬度,支持所有的尺寸單位,一般使用dp
android:viewportHeight="600"//視圖的高度,可以理解爲畫布的高度
android:viewportWidth="600" >//視圖的寬度,下面的pathData中的內容便會在600寬高的畫布內操作
<group //定義一個組,可以包含path 及子group, 同時可以定義轉換信息,如旋轉,伸縮,位移
android:name="rotationGroup"//組名
android:pivotX="300.0"//X座標中心點,默認爲0
android:pivotY="300.0"//Y座標中心點,默認爲0
android:rotation="45.0" >//旋轉角度,順時針
<path
android:name="v"//路徑的名稱
android:fillColor="#000000"//填充顏色
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />//路徑的數據
</group>
</vector>
效果:
解析一下數據:M300,70 l 0,-70 70,70 0,0 -70,70z
相關的指令:
M = moveto 相當於 android Path 裏的moveTo(),用於移動起始點
L = lineto 相當於 android Path 裏的lineTo(),用於畫線
H = horizontal lineto 用於畫水平線
V = vertical lineto 用於畫豎直線
C = curveto 相當於cubicTo(),三次貝塞爾曲線
S = smooth curveto 同樣三次貝塞爾曲線,更平滑
Q = quadratic Belzier curve quadTo(),二次貝塞爾曲線
T = smooth quadratic Belzier curveto 同樣二次貝塞爾曲線,更平滑
A = elliptical Arc 相當於arcTo(),用於畫弧
Z = closepath 相當於closeTo(),關閉path
座標軸爲以(0,0)爲中心,X軸水平向右,Y軸水平向下;
所有指令大小寫均可。大寫絕對定位,參照全局座標系;小寫相對定位,參照父容器座標系;
指令和數據間的空格可以省略;同一指令出現多次可以只用一個。
(還有很多高級動畫操作,請自行百度)
第二步:
使用着色器Tint
實現目的:
- 一張矢量圖適配所有顏色
- 更優雅的selector實現方式
1.適配所有顏色:
- XML:直接添加android:tint="@color"屬性。
<ImageView
android:id="@+id/image1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:src="@mipmap/logo" />
<ImageView
android:id="@+id/image2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:src="@mipmap/logo"
android:tint="#3F51B5" />
- 代碼實現
Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.icon);
// 保留原來的圖片
Drawable.ConstantState state = drawable.getConstantState();
/**
* DrawableCompat類:是Drawable的向下兼容類,我們爲了在6.0一下兼容tint屬性而使用
* wrap方法:使用tint就必須調用該方法對Drawable進行一次包裝
* 調用mutate後會對ConstantState進行一次拷貝
*/
Drawable drawable1 = DrawableCompat.wrap(state == null ? drawable : state.newDrawable()).mutate();
drawable1.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
//設置Tint屬性
DrawableCompat.setTint(drawable, ContextCompat.getColor(this, R.color.colorAccent));
my_image.setImageDrawable(drawable);// 改變後的圖片
my_image1.setImageDrawable(drawable1);// 原來的圖片
2.實現selector
- XML實現:效果不好,Android6.0下不兼容。
- 代碼實現:Android6.0下兼容。
Drawable drawable = ContextCompat.getDrawable(this, R.mipmap.icon);
int[] colors = new int[]{ContextCompat.getColor(this, R.color.colorPrimary), ContextCompat.getColor(this, R.color.colorAccent)};
int[][] states = new int[2][];
// 注意順序
states[0] = new int[]{android.R.attr.state_pressed};
states[1] = new int[]{};
ColorStateList colorList = new ColorStateList(states, colors);
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(states[0], drawable);
stateListDrawable.addState(states[1], drawable);
Drawable.ConstantState state = stateListDrawable.getConstantState();
drawable = DrawableCompat.wrap(state == null ? stateListDrawable : state.newDrawable()).mutate();
// 設置各個狀態下的顏色
my_image.setImageDrawable(drawable);
第三步:
資源打包配置
當應用不需要支持幾十種語言時,通過配置 resConfigs 去除無用的語言資源。可以減少apk體積100k左右。
defaultConfig {
applicationId "com.zachary.util"
minSdkVersion 18
targetSdkVersion 26
versionCode 101
versionName "1.0.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//只保留默認的資源和指定的資源:中文、英文
resConfigs "zh-rCN", "en-rUS"
}
第四步:
動態庫的打包配置(定製化設備除外)
當引入so庫的時候,modle的build.gradle:
//將so庫打包到apk內
sourceSets{
main{
jniLibs.srcDirs=['libs']
}
}
在modle的build.gradle中defaultConfig配置so庫架構,減小apk體積
//配置so庫架構(真機:arm 模擬器:x86)
ndk {
abiFilters('armeabi', 'armeabi-v7a')
}
第五步:
- 在modle的build.gradle中buildTypes配置,移除無用資源:
buildTypes {
release {
// 移除無用的resource文件
shrinkResources true
}
}
注:打包時會刪除沒有用到的資源。若是動態獲取資源id,在打包時也會將其刪除。
- 推薦在物理上的刪除,移除無用的資源(Lint)
通過Android Studio中:Refactor-->Remove Unused Resources...快速刪除(慎用)
通過Android Studio中:Analyze-->Run Inspection by Name-->unused resources
根據自己的實際需求進行選擇,直接刪除不需要的資源文件即可。
第六步:
開啓代碼混淆
在modle的build.gradle中buildTypes配置
buildTypes {
release {
// 開啓混淆(反編譯)
minifyEnabled true
// 移除無用的resource文件
shrinkResources false
debuggable false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
可以查看文章:Android之傻瓜混淆
注:將所需要的混淆都加進去,發現哪個class混淆有問題就將其keep,重複的刪掉:
爲保持第三方庫中的類而不亂,-dontwarn和-keep 結合使用,意思是保持com.xx.bbb.**這個包裏面的所有類和所有方法而不混淆,接着還叫ProGuard不要警告找不到com.xx.bbb.**這個包裏面的類的相關引用。
-dontwarn com.xx.bbb.**
-keep class com.xx.bbb.** { *;}
第七步:
啓用資源縮減
在第五步中有介紹,在modle的build.gradle中buildTypes配置,移除無用資源:
buildTypes {
release {
// 移除無用的resource文件
shrinkResources true
}
}
使我們保留了代碼文件,只是不打包進入apk,如果沒有動態獲取資源id,打包沒有問題的話,請開啓。因爲總是會因爲各種需求的變化,我們的項目中留有暫時沒有用到的資源。
注:混淆文件proguard-rules.pro
裏,添加以下命令,啓用資源縮減無效。
-dontshrink
第八步:
將png格式大圖轉webp格式
WebP是一種同時提供了有損壓縮與無損壓縮的圖片文件格式,這種圖片格式相比png或者jpg格式的圖片損失的質量幾乎可以忽略不計,但是壓縮後的體積卻比png和jpg要小很多。
在Android Studio中進行轉換:右鍵png圖片,選擇Convert to WebP
針對大圖壓縮還是很明顯的。
進行批量轉換:
介紹一下工具iSparta,可以將png批量轉換成webp格式、生成APNG、圖片無損壓縮、png有損壓縮。
官網地址:iSparta,我嘗試過最新版本下載,下載過程很痛苦,下載之後也不能正常使用,具體原因不知。
因此我在百度雲提供:win64、win32、mac的iSparta工具下載:
- 鏈接:https://pan.baidu.com/s/1VdYYcQ92WzRoJAk9RHWUYg
- 提取碼:8i0n
第九步:
微信開源的AndResGuard進行資源混淆:AndResGuard
1.在項目根目錄下build.gradle中,添加插件的依賴:
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
// 資源混淆
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.15'
}
2.在app目錄下,創建and_res_guard.gradle文件(可以直接將內容寫入build.gradle文件):
apply plugin: 'AndResGuard'
andResGuard {
// mappingFile = file("./resource_mapping.txt")
mappingFile = null
use7zip = true
useSign = true
// 打開這個開關,會keep住所有資源的原始路徑,只混淆資源的名字
keepRoot = false
// 白名單
whiteList = [
// for your icon
"R.drawable.logo",
// for fabric
"R.string.com.crashlytics.*",
// for google-services
"R.string.google_app_id",
"R.string.gcm_defaultSenderId",
"R.string.default_web_client_id",
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key",
// 例如:友盟、融雲在sdk裏面寫死了資源名,但是被我們混淆之後找不到指定資源,則會報崩潰
// umeng share for sina
"R.drawable.sina*",
// for umeng update
"R.string.umeng*",
"R.string.UM*",
"R.string.tb_*",
"R.string.rc_*",
"R.layout.umeng*",
"R.layout.tb_*",
"R.layout.rc_*",
"R.drawable.umeng*",
"R.drawable.tb_*",
"R.drawable.rc_*",
"R.drawable.u1*",
"R.drawable.u2*",
"R.anim.umeng*",
"R.color.umeng*",
"R.color.tb_*",
"R.color.rc_*",
"R.style.*UM*",
"R.style.umeng*",
"R.style.rc_*",
"R.id.umeng*",
"R.id.rc_*",
// 項目中遇到第三方使用getIdentifier訪問的資源的問題,只好全部id都放入白名單
"R.id.*"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.15'
//path = "/usr/local/bin/7za"
}
/**
* 可選: 如果不設置則會默認覆蓋assemble輸出的apk
**/
// finalApkBackupPath = "${project.rootDir}/final.apk"
/**
* 可選: 指定v1簽名時生成jar文件的摘要算法
* 默認值爲“SHA-1”
**/
// digestalg = "SHA-256"
}
3.在build.gradle文件中引用and_res_guard.gradle。
apply from: 'and_res_guard.gradle'
集成完AndResGuard完畢,在app的gradle的tasks中執行指令。
雙擊執行resguardRelease指令,執行完畢後,可以在app目錄下的/build/output/apk/release/AndResGuard_{apk_name}/ 文件夾中找到混淆後的Apk。
注:
1.出現異常(Execution failed for task ':app:resguardRelease'. > can't the get signConfig for release build)
解決:配置相應簽名即可
// 定義APK的簽名信息.
signingConfigs {
debug {
}
release {
keyAlias '***'
keyPassword '***'
storeFile file('D:/**簽名/**.jks')
storePassword '***'
}
}
buildTypes {
release {
signingConfig signingConfigs.release //增加簽名
}
debug {
}
}
2.若寫死了資源名,則需要白名單使用正則表達式指定資源文件不被混淆。
資源混淆後的效果: