Android之Apk優化九道工序

針對安卓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.若寫死了資源名,則需要白名單使用正則表達式指定資源文件不被混淆。 

資源混淆後的效果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章