Змейка в 33 строки для Android на Java
Конечно, 33 строки — это «маркетинговый» ход для привлечения внимания.Полное количество строк в основном и единственном классе равно 86, но ядро как раз столько и занимает (33). Внутри код змейки, самого маленького приложения для Android, комментарии и некоторые исследования на тему уменьшения размеров, сборка приложения из командной строки с использованием Proguard.Быстрые ссылки по статье: самое маленькое приложение; змейка; ссылки на github.
ВведениеДумаю, что многие видели HelloWorld в Eclipse для Android.Выглядит, особенно для новичка, весьма некомпактно. Тут очень много, назовём условно, «лишнего», для того чтобы разобраться, как это всё работает, что из чего следует, и как настраивать приложение.От версии к версии иерархия файлов постоянно усложняется, что простоты к работе не добавляет.
Что тут есть (для самых маленьких):
1. две папки с Java-классами (src, gen). src содержит исходники, gen — сгенерированные классы. В основном, в папке gen бывает R.java.2. Папка для библиотек libs. В случае HelloWorld создаётся библиотека android-support-v4.jar (или её более новая версия). Эта библиотека используется для обратной совместимости приложений.То есть, если какие-то новые особенности (например объекты UI), создадут разработчики ОС, то, для того чтобы всё работало и на более ранних версиях, используются именно эти библиотеки. Также при помощи этой библиотеки разработчики исправляют некоторые ошибки. При использовании этой библиотеки, приложение, безусловно, значительно увеличит свой размер. Тут можно посмотреть список всех изменений по версиям.3. ресурсная папка (res), в которой обычно располагаются изображения, настройки стилей, различные строки, и много другого прочего. Все файлы из этой папки получают уникальный ID в автоматически создаваемом при компиляции R.java-файле, который помещается в папку gen. ID можно (нужно) использовать внутри приложения, для того чтобы выделить тот или иной объект. Например, иконка для приложения.4. asserts служит для хранения всех любых других видов файлов, которые нужно использовать в приложении, но им не будет выделен ID. Например, шрифт.5. Файл-манифест приложения AndroidManifest.xml. Из него ОС узнаёт, что и как запускать, какие разрешения есть у приложения и т.п.6. ic_launcher-web.png — файл-изображение для Google Play. Честно говоря, не знаю, насколько сейчас это является обязательным пунктом. В крайний раз, когда обновлял приложение на Google Play, этого не требовалось. Минутка субъективизма: не понимаю, что этот файл заслужил расположение в корне.7. Текстовых файла для настроек проекта project.properties. Самый, пожалуй, частый вариант редактирования — включить/выключить использование proguard.8. Текстовый файл для настроек proguard: proguard-project.txtОб этой иерархии всём подробнее можно прочитать тут.
В принципе, для написания приложения достаточно только основной класс для Activity и AndroidManifest.xml
При помощи project.properties и proguard-project.txt можно настраивать работу Proguard. У него самая главная функция — сокращать имена по минимуму, что помогает усложнить реверс инжиниринг, а также сократить размер выходного файла. Имена переменных, методов и классов сокращаются до одно-, двухбуквенных.Родная сборка приложения от Eclipse достаточно хороша, но лучше воспользоваться более «ручным» способом создания приложения при помощи командной строки. Как минимум исчезнет класс BuildConfig.java, который «ни с чего» появляется в папке gen. Также я заметил такую особенность, что родной компилятор собирает не только java, но и прочие файлы, которые находятся на одном уровне с исходниками. Чтобы не ошибиться (и не слить в релиз то, что не нужно), лучше воспользоваться компиляцией из командной строки.
Как это сделать, подробно написано в этой отличной статье.Но при работе Тут bat-файл был переработан: добавлена корректная обработка путей в Windows, работа с Proguard, генерация ключа вынесена в отдельный батник.
Как работать с Proguard через командную строку.Proguard работает с class-файлами (в нашем случае это файл из папки obj). Он всё собирает в выходной jar-файл (в нашем случае ./objPro/classes-processed.jar). Полученный jar-файл уже кормим сборщику apk-файлов. В итоге, всё собирается в dex-файлы, и apk-файлы. Для настройки сборки используется android.pro-файл. Если Proguard не используется, то всё собирается сразу из папки obj с помощью class-файлов.
Самое маленькое приложение на Android
GithubДавайте как предварительный этап перед созданием змейки соберём самое маленькое приложение на Android.В общем, для этого нужно только A.java (class A extends Activity): код
package a.a;
import android.app.Activity;
public class A extends Activity {
}
и AndroidManifest.xmlкод
Змейка GithubВ начале, конечно, захотелось мини-Excel, но я не нашёл хороший вариант в Java, при котором будет работать аналог функции exec в Javascript. Будет очень интересно посмотреть на результат, если кто-то сможет реализовать подобное.Характеристики кода: полное количество строк A.java — 86, без import’ов — 69, «физика» поведения — 33 строки (что сравнимо с Javascript).Приложение — полноэкранное. Управление при помощи жестов — вверх, вниз, влево, вправо.Исходный код A.java.A.java добавлен внутренний класс V extends View, который выводится на полный экран, в V есть в свою очередь свой внутренний класс P (Physics), который и описывает поведение змейки. Вот его размеры и равны примерно 30 строкам.Алгоритм работыприложение запускается в портретном режиме, основным элементом становится gv = new V (); В классе gv добавляется обработка жестов и запускается физику. В Физике инициализируется таймер (Timer), внутри которого происходит вычисление поведения змейки. Runnable и ASyncTask по размеру бы были гораздо больше. Сама змейки хранится в ArrayList. Были попытки сделать это через bitArray или через обычный массив, но в ArrayList всё как-то компактней получилось.
код package ru.ps.habrsnake;
import java.util.ArrayList; import java.util.Random; import java.util.Timer; import java.util.TimerTask;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
public class A extends Activity {
static final int RESULT_FINISH = 0, RESULT_OK = 1, RESULT_LOCK = 2, RESULT_SELF = 3, RESULT_INC = 4, RESULT_WIN = 5, RATE = 250, cSnake = 0xAACC33AA, cMeat = 0xEEAACC33, cBG = 0xFF000000, cText = 0xFFFFFFFF, width_cells = 10;
public V gv;
protected void onCreate (Bundle savedInstanceState) {
setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
requestWindowFeature (Window.FEATURE_NO_TITLE);
getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate (savedInstanceState);
gv = new V (this);
setContentView (gv);}
protected void onDestroy () {
super.onDestroy ();
gv.ph.timer.cancel ();}
class V extends View implements OnTouchListener{
public float diam_cell = 10.f, x, y;
public P ph;
public Paint paintSnake = new Paint (), paintMeat = new Paint ();
public V (Activity activity) {
super (activity);
setBackgroundColor (cBG);
paintSnake.setColor (cSnake);
paintMeat.setColor (cMeat);
setOnTouchListener (this);
diam_cell = ((float) A.this.getWindowManager ().getDefaultDisplay ().getWidth ()) / ((float) width_cells);
ph = new P (width_cells, (int) (A.this.getWindowManager ().getDefaultDisplay ().getHeight () / diam_cell));}
public void invalidateWrapper (){
A.this.runOnUiThread (new Runnable () {public void run () {invalidate ();}});}
public void onDraw (Canvas canvas) {
for (int i = 0; i < ph.arSnake.size(); canvas.drawCircle((ph.arSnake.get(i) % width_cells +.5f) * diam_cell, (ph.arSnake.get(i) / width_cells +.5f) * diam_cell, diam_cell*.5f, paintSnake),i++){}
canvas.drawCircle((ph.posMeat % width_cells +.5f) * diam_cell, (ph.posMeat / width_cells +.5f) * diam_cell, diam_cell*.5f, paintMeat);
canvas.drawText(ph.scores + "", canvas.getWidth() * .5f ,paintMeat.getTextSize(), paintMeat);}
public boolean onTouch(View v, MotionEvent event) {
x = (event.getActionMasked() == MotionEvent.ACTION_DOWN)?event.getX():x;
y = (event.getActionMasked() == MotionEvent.ACTION_DOWN)?event.getY():y;
ph.setDir((event.getActionMasked() == MotionEvent.ACTION_UP)?((Math.abs(event.getX() - x) > Math.abs (event.getY () — y) && (event.getX () — x) > 0)?3:(((Math.abs (event.getX () — x) > Math.abs (event.getY () — y)) && (event.getX () — x) <= 0)?2:((event.getY() - y) > 0?1:0))): ph.GlobDir);
return true;}
class P {
public int[] arI = new int[]{5,4,3,2};
public ArrayList
С пунктом 4 странно на получается странно. Если взять и протестировать использование функции summ в разных вариантах во всех трёх проектах (helloworld, small, snake), как изменяются размеры файлов при использовании в проекте этих методов, то получится следующая картина (цифры — количество байт):

Jar-файл уменьшается при использовании второй функции, а apk и dex увеличиваются. Скорее всего это связано с архивированием данных. Возможно, в некоторых случаях это даст свои результаты. Правда, они могут не оправдать Ваши ожидания.На этом, пожалуй, всё.
Итог Резюмируя, получилось 86 строк и 5696 байт для apk.Ещё раз ссылки на проекты.SmallSnake
