[Из песочницы] Уменьшение размера APK (в разумных пределах)

На Habr.com уже была подобная статья, доказывающая, что можно ужать APK файл с 1.5 МБ до 1757 байт и меньше. Цель данной статьи — уменьшить размер приложения до разумного предела, сохранив его функциональность и осветить некоторые тонкости и неявные моменты.

Начало


Создадим проект в Android Studio, выберем Empty Activity. Затем в файле styles.xml заменим Activity c ActionBar’ом

Theme.AppCompat.Light.DarkActionBar


на Activity без ActionBar’а

Theme.AppCompat.Light.NoActionBar


Итог:

image

В анализаторе APK видим следующее:

pd49sybjk1lagllkzgpr4crxdru.png

Итак, APK весит 1.5 МБ, при том, что оно только выводит надпись «Hello World!».

Этап первый (минификация)


В файле build.gradle пишем:


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


Синхронизируем Android Studio, чтобы изменения вступили в силу.

Пояснение:

minifyEnabled true

уберёт ненужный код в приложении

shrinkResources true

удалит из APK не используемые ресурсы.

Вес APK стал 960 КБ, без изменений в работе.

Этап второй (добавление функциональности)


Чтобы приложение имело смысл, добавим ему функциональность, например, кликер.

Код activity_main.xml




    

    



Код MainActivity.java
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;

    View.OnTouchListener on = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                num--;
                if(num > 0) {
                    text.setText(Integer.toString(num));
                } else {
                    num = 31;
                    text.setText("Нажмите, чтобы начать заново");
                }
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Settings = getSharedPreferences("settings", Context.MODE_PRIVATE);

        if (Settings.contains("left"))
            num = Settings.getInt("left", 0);

        button = findViewById(R.id.imageButton);

        button.setOnTouchListener(on);

        text = findViewById(R.id.number);

        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать заново");
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}


Приложение приобрело следующий вид:

ncste8kbwnvcyyg8-tgjuhkgyfm.png

Размер приложения 1.1 МБ, увеличение на 140 КБ.

Этап третий (убираем android.support и AppCompat)


На данный момент анализатор APK показывает следующее:

hbz-evsxiitt1_axe3bu6h9awb0.png

Заменим public class MainActivity extends AppCompatActivity на public class MainActivity extends Activity в MainActivity.java. Нажмите Alt + Enter, чтобы Android Studio импортировала библиотеки.

Размер приложения не изменился, но…

x_cmry4rbouigjtwkhjtuxxevfi.png

Где же кнопка?

На самом деле всё в порядке, она осталась кликабельной. Но у неё просто нет картинки.

Идем в activity_main.xml, находим внизу строчку

app:srcCompat="@mipmap/ic_launcher_round"


и меняем её на

android:src="@mipmap/ic_launcher_round" 


Теперь всё в порядке:

ztttdbyug0nrdvqs0fnjqf8kci4.png

Но размер не изменился.

Идём опять в build.gradle и очищаем блок зависимостей:


dependencies {
}


И синхронизируем Android Studio… с ошибками.

1. Идём в файл res/values/styles.xml и заменяем всё его содержимое следующим кодом:



    



2. ConstraintLayout, который используется в Android Studio, зависит от android.support, который уже был удалён из блока dependencies, поэтому заменим ConstraintLayout на RelativeLayout, который не зависит от android.support.

Код activity_main.xml:




    

    





Компилируем APK и смотрим итог:

1r8ibmx1qelop5uv63t1jw6_bc8.png

Наш APK весит 202 КБ, что в 7.5 раз меньше его начального размера.

Большую часть занимают ресурсы, ими и займемся.

1. Удалим файлы в папке res/drawable и очистим папку res/mipmap
2. Нарисуем свою иконку и кнопку, затем уменьшим её размер с помощью ImageOptim.

Кнопка:

vdnmk8mzdj2nlal8mma6peiufrs.png

Иконка:

szquypvptvmqkpoxmqrd_b0k_oy.png

3. Загрузим их в Android Studio, для этого выделим их в папке, нажмём Ctrl + C, перейдём в Android Studio, выберем папку res/drawable и нажмём Ctrl + V, после чего Andoid Studio предложит несколько вариантов, куда именно перенести изображения в зависимости от их разрешения.

В папке drawable можно переименовать файл, выбрав Refactor → Rename.
Иконка приложения имеет имя i.png, картинка для кнопки b.png

4. Теперь переходим в файл AndroidManifest.xml, строчки


android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"


заменим на


android:icon="@drawable/i"
android:roundIcon="@drawable/i"


В файле activity_main.xml заменим поле в ImageButton


android:src="@mipmap/ic_launcher_round"


на


android:src="@drawable/b"


Итог


Итоговый размер файла составляет 13.4 КБ, что в 112 раз меньше начального объёма!

Итоговый код MainActivity.java
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;

public class MainActivity extends Activity {
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;

    View.OnTouchListener on = new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                num--;
                if(num > 0) {
                    text.setText(Integer.toString(num));
                } else {
                    num = 31;
                    text.setText("Нажмите, чтобы начать заново");
                }
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.m);

        Settings = getSharedPreferences("settings", Context.MODE_PRIVATE);

        if (Settings.contains("left"))
            num = Settings.getInt("left", 0);
        
        text = findViewById(R.id.number);
        button = findViewById(R.id.button);
        button.setOnTouchListener(on);

        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать");
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}



Итоговый код activity_main.xml




    

    





На этом оканчивается разумное уменьшение APK файла, далее идёт инструкция по дальнейшему уменьшению приложения в ущерб удобства разработки.

Удаляем ресурсы


Удалим папку res/values, в файле AndroidManifest.xml заменим блок application на следующий код:


    
        
            
        
    


Также заменим идентификаторы на однобуквенные, файл activity_main.xml переименуем в m.xml

Изменим обработку нажатия:

Удалим строку

button.setOnTouchListener(on);


в MainLayout.java.
Заменим функцию

View.OnTouchListener on = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                num--;
                if(num > 0) {
                    text.setText(Integer.toString(num));
                } else {
                    num = 31;
                    text.setText("Нажмите, чтобы начать заново");
                }
            }
            return false;
        }
    };


на

public void o(View v) {
        num--;
        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать заново");
        }
    }


В файле m.xml (бывшем activity_main) в структуре ImageButton добавим строчку

android:onClick="o"


Итоговый размер составил 10.2 КБ, в 147 раз меньше начального размера. Я считаю, что это хороший результат.

wso8pg7p09xv9nlbjv_m4kkny9a.png

Код MainActivity.java
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;

public class MainActivity extends Activity {
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;


    public void o(View v) {
        num--;
        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать заново");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.m);

        Settings = getSharedPreferences("settings", Context.MODE_PRIVATE);

        if (Settings.contains("left"))
            num = Settings.getInt("left", 0);

        text = findViewById(R.id.n);
        button = findViewById(R.id.b);

        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать");
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}



Код m.xml




    

    




© Habrahabr.ru