Decrease apk size (reasonably)

Habr.com already had a similar article proving that it is possible to shrink an APK file from 1.5 MB to 1757 bytes or less. The purpose of this article is to reduce the size of the application to a reasonable limit, while retaining its functionality and to highlight some subtleties and implicit moments.

Start


Create a project in Android Studio, select Empty Activity. Then in the styles.xml file, replace the Activity with ActionBar

Theme.AppCompat.Light.DarkActionBar

on Activity without ActionBar

Theme.AppCompat.Light.NoActionBar

Bottom line:

image

In the APK analyzer, we see the following:



So, the APK weighs 1.5 MB, despite the fact that it only displays the inscription “Hello World!”.

Stage one (minification)


In the file build.gradle we write:


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

Sync Android Studio for the changes to take effect.

Explanation:

minifyEnabled true
removes unnecessary code in the application
shrinkResources true
will remove unused resources from the APK.

Weight APK became 960 KB, without changes in work.

Stage two (adding functionality)


To make the application make sense, we add functionality to it, for example, a clicker.

Code activity_main.xml
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayoutxmlns: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"><TextViewandroid: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" /><ImageButtonandroid: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>


MainActivity.java code
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;
publicclassMainActivityextendsAppCompatActivity{
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;
    View.OnTouchListener on = new View.OnTouchListener() {
        @OverridepublicbooleanonTouch(View v, MotionEvent event){
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                num--;
                if(num > 0) {
                    text.setText(Integer.toString(num));
                } else {
                    num = 31;
                    text.setText("Нажмите, чтобы начать заново");
                }
            }
            returnfalse;
        }
    };
    @OverrideprotectedvoidonCreate(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("Нажмите, чтобы начать заново");
        }
    }
    @OverrideprotectedvoidonPause(){
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}

The application has acquired the following form:



Application size 1.1 MB, an increase of 140 KB.

Stage three (remove android.support and AppCompat)


At the moment, APK Analyzer shows the following:



replace public class MainActivity extends AppCompatActivityon public class MainActivity extends Activityin MainActivity.java. Press Alt + Enter for Android Studio to import the libraries.

The size of the application has not changed, but ...



Where is the button?

In fact, everything is in order, it remained clickable. But she just does not have a picture.

Go to activity_main.xml, find the line below

app:srcCompat="@mipmap/ic_launcher_round"

and change it to

android:src="@mipmap/ic_launcher_round" 

Now everything is in order:



But the size has not changed.

Go back to build.gradle and clear the dependency block:


dependencies {
}

And sync Android Studio ... with errors.

1. Go to the res / values ​​/ styles.xml file and replace all its contents with the following code:

<resources><stylename="AppTheme"parent="android:Theme.DeviceDefault.NoActionBar"></style></resources>

2. ConstraintLayout, which is used in Android Studio, depends on android.support, which has already been removed from the block dependencies, so we replace ConstraintLayout with RelativeLayout, which does not depend on android.support.

Code activity_main.xml:
<?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#FFFFFF"><TextViewandroid: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" /><ImageButtonandroid: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>


We compile the APK and see the result:



Our APK weighs 202 KB, which is 7.5 times smaller than its initial size.

Most of the resources are occupied, and we will deal with them.

1. Delete the files in the res / drawable folder and clear the res / mipmap folder
2. Draw your icon and button, then reduce its size using ImageOptim.

Button:



Icon:



3. Load them into Android Studio, to do this, select them in the folder, press Ctrl + C, go to Android Studio, select the res / drawable folder and press Ctrl + V, after which Andoid Studio will offer several options for where transfer images depending on their resolution.

In the drawable folder, you can rename the file by selecting Refactor -> Rename.
The application icon has the name i.png, the image for the b.png button

4. Now go to the AndroidManifest.xml file, the lines


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

replace on


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

In the activity_main.xml file, replace the field in the ImageButton


android:src="@mipmap/ic_launcher_round"

on


android:src="@drawable/b"

Total


The final file size is 13.4 KB, which is 112 times smaller than the initial volume!

Summary code 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;
publicclassMainActivityextendsActivity{
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;
    View.OnTouchListener on = new View.OnTouchListener() {
        @OverridepublicbooleanonTouch(View v, MotionEvent event){
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                num--;
                if(num > 0) {
                    text.setText(Integer.toString(num));
                } else {
                    num = 31;
                    text.setText("Нажмите, чтобы начать заново");
                }
            }
            returnfalse;
        }
    };
    @OverrideprotectedvoidonCreate(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("Нажмите, чтобы начать");
        }
    }
    @OverrideprotectedvoidonPause(){
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}


The resulting code is activity_main.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#FFFFFF"><TextViewandroid: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" /><ImageButtonandroid: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>


This ends a reasonable decrease in the APK file, followed by instructions for further reducing the application to the detriment of development convenience.

We delete resources


Delete the res / values ​​folder, in the AndroidManifest.xml file, replace the application block with the following code:

<applicationandroid:icon="@drawable/i"android:roundIcon="@drawable/i"android:label="Clicker"android:theme="@style/android:Theme.DeviceDefault.NoActionBar"><activityandroid:name=".MainActivity"><intent-filter><actionandroid:name="android.intent.action.MAIN" /><categoryandroid:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>

We will also replace the identifiers with single-letter ones; rename the activity_main.xml file to m.xml.

Change the click processing:

Delete the line

button.setOnTouchListener(on);

in MainLayout.java.
Replace function

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

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

In the file m.xml (formerly activity_main) in the ImageButton structure, add the line

android:onClick="o"

The total size was 10.2 KB , 147 times smaller than the initial size. I think this is a good result.



MainActivity.java code
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;
publicclassMainActivityextendsActivity{
    SharedPreferences Settings;
    ImageButton button;
    TextView text;
    int num = 31;
    publicvoido(View v){
        num--;
        if(num > 0) {
            text.setText(Integer.toString(num));
        } else {
            num = 31;
            text.setText("Нажмите, чтобы начать заново");
        }
    }
    @OverrideprotectedvoidonCreate(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("Нажмите, чтобы начать");
        }
    }
    @OverrideprotectedvoidonPause(){
        super.onPause();
        SharedPreferences.Editor editor = Settings.edit();
        editor.putInt("left", num);
        editor.apply();
    }
}


M.xml code
<?xml version="1.0" encoding="utf-8"?><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#FFFFFF"><TextViewandroid: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" /><ImageButtonandroid: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>


Also popular now: