На 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
Итог:
В анализаторе APK видим следующее:
Итак, 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 КБ, без изменений в работе.
Этап второй (добавление функциональности)
Чтобы приложение имело смысл, добавим ему функциональность, например, кликер.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/imageButton"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginBottom="32dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="#000000FF"
android:cropToPadding="false"
android:scaleType="fitXY"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@mipmap/ic_launcher_round" />
</android.support.constraint.ConstraintLayout>
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();
}
}
Приложение приобрело следующий вид:
Размер приложения 1.1 МБ, увеличение на 140 КБ.
Этап третий (убираем android.support и AppCompat)
На данный момент анализатор APK показывает следующее:
Заменим public class MainActivity extends AppCompatActivity
на public class MainActivity extends Activity
в MainActivity.java. Нажмите Alt + Enter, чтобы Android Studio импортировала библиотеки.
Размер приложения не изменился, но…
Где же кнопка?
На самом деле всё в порядке, она осталась кликабельной. Но у неё просто нет картинки.
Идем в activity_main.xml, находим внизу строчку
app:srcCompat="@mipmap/ic_launcher_round"
и меняем её на
android:src="@mipmap/ic_launcher_round"
Теперь всё в порядке:
Но размер не изменился.
Идём опять в build.gradle и очищаем блок зависимостей:
dependencies {
}
И синхронизируем Android Studio… с ошибками.
1. Идём в файл res/values/styles.xml и заменяем всё его содержимое следующим кодом:
<resources>
<style name="AppTheme" parent="android:Theme.DeviceDefault.NoActionBar">
</style>
</resources>
2. ConstraintLayout, который используется в Android Studio, зависит от android.support, который уже был удалён из блока dependencies
, поэтому заменим ConstraintLayout на RelativeLayout, который не зависит от android.support.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<TextView
android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="Hello World!"
android:textSize="20dp" />
<ImageButton
android:id="@+id/imageButton"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:background="#000000FF"
android:scaleType="fitXY"
android:src="@mipmap/ic_launcher_round"
android:visibility="visible" />
</RelativeLayout>
Компилируем APK и смотрим итог:
Наш APK весит 202 КБ, что в 7.5 раз меньше его начального размера.
Большую часть занимают ресурсы, ими и займемся.
1. Удалим файлы в папке res/drawable и очистим папку res/mipmap
2. Нарисуем свою иконку и кнопку, затем уменьшим её размер с помощью ImageOptim.
Кнопка:
Иконка:
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 раз меньше начального объёма!
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();
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<TextView
android:id="@+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="Hello World!"
android:textSize="20dp" />
<ImageButton
android:id="@+id/button"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:background="#000000FF"
android:scaleType="fitXY"
android:src="@drawable/b"
android:visibility="visible" />
</RelativeLayout>
На этом оканчивается разумное уменьшение APK файла, далее идёт инструкция по дальнейшему уменьшению приложения в ущерб удобства разработки.
Удаляем ресурсы
Удалим папку res/values, в файле AndroidManifest.xml заменим блок application на следующий код:
<application
android:icon="@drawable/i"
android:roundIcon="@drawable/i"
android:label="Clicker"
android:theme="@style/android:Theme.DeviceDefault.NoActionBar">
<activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity>
</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 раз меньше начального размера. Я считаю, что это хороший результат.
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();
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<TextView
android:id="@+id/n"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textColor="#000000"
android:textSize="20dp" />
<ImageButton
android:id="@+id/b"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:background="#000000FF"
android:scaleType="fitXY"
android:src="@drawable/b"
android:onClick="o"
android:visibility="visible" />
</RelativeLayout>
Автор: RigelGL