Пользователь
0,0
рейтинг
27 февраля 2011 в 20:47

Разработка → Пишем первое приложение для Android из песочницы

В любом деле самое сложное — это начало. Часто бывает тяжело войти в контекст, с чем столкнулся и я, решив разработать свое первое Android-приложение. Настоящая статья для тех, кто хочет начать, но не знает с чего.

Статья затронет весь цикл разработки приложения. Вместе мы напишем простенькую игру “Крестики-Нолики” с одним экраном (в ОС Android это называется Activity).

Отсутствие опыта разработки на языке Java не должно стать препятствием в освоении Android. Так, в примерах не будут использоваться специфичные для Java конструкции (или они будет минимизированы на столько, на сколько это возможно). Если Вы пишете, например, на PHP и знакомы с основополагающими принципами в разработке ПО, эта статья будет вам наиболее полезна. В свою очередь так как, я не являюсь экспертом по разработке на Java, можно предположить, что исходный код не претендует на лейбл “лучшие практики разработки на Java”.



Установка необходимых программ и утилит



Перечислю необходимые инструменты. Их 3:
  1. JDK — набор для разработки на языке Java;
  2. Android SDK and AVD Manager — набор утилит для разработки + эмулятор;
  3. IDE c поддержкой разработки для Android:
    • Eclipse + ADT plugin;
    • IntelliJ IDEA Community Edition;
    • Netbeans + nbandroid plugin;



Утилиты устанавливаются в определенном выше порядке. Ставить все перечисленные IDE смысла нет (разве только если Вы испытываете затруднения с выбором подходящей). Я использую IntelliJ IDEA Community Edition, одну из самых развитых на данный момент IDE для Java.

Запуск виртуального устройства


Запустив AVD Manager и установив дополнительные пакеты (SDK различных версий), можно приступить к созданию виртуального устройства с необходимыми параметрами. Разобраться в интерфейсе не должно составить труда.



Список устройств





Создание проекта


Мне всегда не терпится приступить к работе, минимизируя подготовительные мероприятия, к которым относится создание проекта в IDE, особенно, когда проект учебный и на продакшн не претендует.

Итак, File->New Project:

















По нажатию кнопки F6 проект соберется, откомпилируется и запустится на виртуальном девайсе.

Структура проекта


На предыдущем скриншоте видна структура проекта. Так как в этой статье мы преследуем сугубо практические цели, заострим внимание лишь на тех папках, которые будем использовать в процессе работы. Это следующие каталоги: gen, res и src.

В папке gen находятся файлы, которые генерируются автоматически при сборке проекта. Вручную их менять нельзя.

Папка res предназначена для хранения ресурсов, таких как картинки, тексты (в том числе переводы), значения по-умолчанию, макеты (layouts).

src — это папка в которой будет происходить основная часть работы, ибо тут хранятся файлы с исходными текстами нашей программы.

Первые строки



Как только создается Activity (экран приложения), вызывается метод onCreate(). IDE заполнила его 2 строчками:
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Метод setContentView (равносильно this.setContentView) устанавливает xml-макет для текущего экрана. Далее xml-макеты будем называть «layout», а экраны — «Activity». Layout в приложении будет следующий:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/main_l"
    android:gravity="center"
    >
</TableLayout>


Для этого приложения идеально подойдет TableLayout. Id можно присвоить любому ресурсу. В данном случае, TableLayout присвоен id = main_l. При помощи метода findViewById() можно получить доступ к виду:

    private TableLayout layout; // это свойство класса KrestikinolikiActivity

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        layout = (TableLayout) findViewById(R.id.main_l);
        buildGameField();
    }


Теперь необходимо реализовать метод buildGameField(). Для этого требуется сгенерировать поле в виде матрицы. Этим будет заниматься класс Game. Сначала нужно создать класс Square для ячеек и класс Player, объекты которого будут заполнять эти ячейки.

Square.java


package com.example;

public class Square {
    private Player player = null;

    public void fill(Player player) {
        this.player = player;
    }

    public boolean isFilled() {
        if (player != null) {
            return true;
        }
        return false;
    }

    public Player getPlayer() {
        return player;
    }
}


Player.java


package com.example;


public class Player {
    private String name;

    public Player(String name) {
        this.name = name;
    }

    public CharSequence getName() {
        return (CharSequence) name;
    }
}


Все классы нашего приложения находятся в папке src.

Game.java


package com.example;

public class Game {
 /**
     * поле
     */
    private Square[][] field;
 
 /**
     * Конструктор
     *
     */
    public Game() {
        field = new Square[3][3];
        squareCount = 0;
        // заполнение поля
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j] = new Square();
                squareCount++;
            }
        }
    }
 
 public Square[][] getField() {
        return field;
    }
}


Инициализация Game в конструкторе KrestikinolikiActivity.
public KrestikinolikiActivity() {
    game = new Game();
 game.start(); // будет реализован позже
}


Метод buildGameField() класса KrestikinolikiActivity. Он динамически добавляет строки и колонки в таблицу (игровое поле):
private Button[][] buttons = new Button[3][3];
 //(....)
    private void buildGameField() {
        Square[][] field = game.getField();
        for (int i = 0, lenI = field.length; i < lenI; i++ ) {
            TableRow row = new TableRow(this); // создание строки таблицы
            for (int j = 0, lenJ = field[i].length; j < lenJ; j++) {
                Button button = new Button(this);
                buttons[i][j] = button;
                button.setOnClickListener(new Listener(i, j)); // установка слушателя, реагирующего на клик по кнопке
                row.addView(button, new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
                        TableRow.LayoutParams.WRAP_CONTENT)); // добавление кнопки в строку таблицы
                button.setWidth(107);
                button.setHeight(107);
            }
            layout.addView(row, new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT,
                    TableLayout.LayoutParams.WRAP_CONTENT)); // добавление строки в таблицу
        }
    }

В строке 8 создается объект, реализующий интерфейс View.OnClickListener. Создадим вложенный класс Listener. Он будет виден только из KrestikinolikiActivity.
public class Listener implements View.OnClickListener {
        private int x = 0;
        private int y = 0;

        public Listener(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public void onClick(View view) {
            Button button = (Button) view;
        }
    }

Осталось реализовать логику игры.
public class Game {
    /**
     * игроки
     */
    private Player[] players;
    /**
     * поле
     */
    private Square[][] field;
    /**
     * начата ли игра?
     */
    private boolean started;
    /**
     * текущий игрок
     */
    private Player activePlayer;
    /**
     * Считает колличество заполненных ячеек
     */
    private int filled;
    /**
     * Всего ячеек
     */
    private int squareCount;

    /**
     * Конструктор
     *
     */
    public Game() {
        field = new Square[3][3];
        squareCount = 0;
        // заполнение поля
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j] = new Square();
                squareCount++;
            }
        }
        players = new Player[2];
        started = false;
        activePlayer = null;
        filled = 0;
    }

    public void start() {
        resetPlayers();
        started = true;
    }

    private void resetPlayers() {
        players[0] = new Player("X");
        players[1] = new Player("O");
        setCurrentActivePlayer(players[0]);
    }

    public Square[][] getField() {
        return field;
    }

    private void setCurrentActivePlayer(Player player) {
        activePlayer = player;
    }

    public boolean makeTurn(int x, int y) {
        if (field[x][y].isFilled()) {
            return false;
        }
        field[x][y].fill(getCurrentActivePlayer());
        filled++;
        switchPlayers();
        return true;
    }

    private void switchPlayers() {
        activePlayer = (activePlayer == players[0]) ? players[1] : players[0];
    }

    public Player getCurrentActivePlayer() {
        return activePlayer;
    }

    public boolean isFieldFilled() {
        return squareCount == filled;
    }

    public void reset() {
        resetField();
        resetPlayers();
    }

    private void resetField() {
        for (int i = 0, l = field.length; i < l; i++) {
            for (int j = 0, l2 = field[i].length; j < l2; j++) {
                field[i][j].fill(null);
            }
        }
        filled = 0;
    }
}

Определение победителя


К. О. подсказывает, что в крестики-нолики выирывает тот, кто выстроет X или O в линию длиной, равной длине поля по-вертикали, или по-горизонтали, или по-диагонали. Первая мысль, которая приходит в голову — это написать методы для каждого случая. Думаю, в этом случае хорошо подойдет паттерн Chain of Responsobility. Определим интерфейс
package com.example;

public interface WinnerCheckerInterface {
    public Player checkWinner();
}

Так как Game наделен обязанностью выявлять победителя, он реализует этот интерфейс. Настало время создать виртуальных «лайнсменов», каждый из которых будет проверять свою сторону. Все они реализует интерфейс WinnerCheckerInterface.

WinnerCheckerHorizontal.java


package com.example;

public class WinnerCheckerHorizontal implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerHorizontal(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        for (int i = 0, len = field.length; i < len; i++) {
            lastPlayer = null;
            int successCounter = 1;
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                currPlayer = field[i][j].getPlayer();
                if (currPlayer == lastPlayer && (currPlayer != null && lastPlayer !=null)) {
                    successCounter++;
                    if (successCounter == len2) {
                        return currPlayer;
                    }
                }
                lastPlayer = currPlayer;
            }
        }
        return null;
    }
}

WinnerCheckerVertical.java


package com.example;

public class WinnerCheckerVertical implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerVertical (Game game) {
        this.game = game;
    }
    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        for (int i = 0, len = field.length; i < len; i++) {
            lastPlayer = null;
            int successCounter = 1;
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                currPlayer = field[j][i].getPlayer();
                if (currPlayer == lastPlayer && (currPlayer != null && lastPlayer !=null)) {
                    successCounter++;
                    if (successCounter == len2) {
                        return currPlayer;
                    }
                }
                lastPlayer = currPlayer;
            }
        }
        return null;
    }
}

WinnerCheckerDiagonalLeft.java


package com.example;

public class WinnerCheckerDiagonalLeft implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerDiagonalLeft(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        int successCounter = 1;
        for (int i = 0, len = field.length; i < len; i++) {
            currPlayer = field[i][i].getPlayer();
            if (currPlayer != null) {
                if (lastPlayer == currPlayer) {
                    successCounter++;
                    if (successCounter == len) {
                        return currPlayer;
                    }
                }
            }
            lastPlayer = currPlayer;
        }
        return null;
    }
}

WinnerCheckerDiagonalRight.java


package com.example;

public class WinnerCheckerDiagonalRight implements WinnerCheckerInterface {
    private Game game;

    public WinnerCheckerDiagonalRight(Game game) {
        this.game = game;
    }

    public Player checkWinner() {
        Square[][] field = game.getField();
        Player currPlayer;
        Player lastPlayer = null;
        int successCounter = 1;
        for (int i = 0, len = field.length; i < len; i++) {
            currPlayer = field[i][len - (i + 1)].getPlayer();
            if (currPlayer != null) {
                if (lastPlayer == currPlayer) {
                    successCounter++;
                    if (successCounter == len) {
                        return currPlayer;
                    }
                }
            }
            lastPlayer = currPlayer;
        }
        return null;
    }
}

Проинициализируем их в конструкторе Game:
//(....)
 /**
     * "Судьи" =). После каждого хода они будут проверять,
     * нет ли победителя
     */
    private WinnerCheckerInterface[] winnerCheckers;
 //(....)
    public Game() {
        //(....)
        winnerCheckers = new WinnerCheckerInterface[4];
        winnerCheckers[0] = new WinnerCheckerHorizontal(this);
        winnerCheckers[1] = new WinnerCheckerVertical(this);
        winnerCheckers[2] = new WinnerCheckerDiagonalLeft(this);
        winnerCheckers[3] = new WinnerCheckerDiagonalRight(this);
        //(....)
    }

Реализация checkWinner():
public Player checkWinner() {
        for (WinnerCheckerInterface winChecker : winnerCheckers) {
            Player winner = winChecker.checkWinner();
            if (winner != null) {
                return winner;
            }
        }
        return null;
    }

Победителя проверяем после каждого хода. Добавим кода в метод onClick() класса Listener
public void onClick(View view) {
            Button button = (Button) view;
            Game g = game;
            Player player = g.getCurrentActivePlayer();
            if (makeTurn(x, y)) {
                button.setText(player.getName());
            }
            Player winner = g.checkWinner();
            if (winner != null) {
                gameOver(winner);
            }
            if (g.isFieldFilled()) {  // в случае, если поле заполнено
                gameOver();
            }
        }

Метод gameOver() реализован в 2-х вариантах:
private void gameOver(Player player) {
        CharSequence text = "Player \"" + player.getName() + "\" won!";
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
        game.reset();
        refresh();
    }

    private void gameOver() {
        CharSequence text = "Draw";
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
        game.reset();
        refresh();
    }

Для Java, gameOver(Player player) и gameOver() — разные методы. Воспользовавшись Builder'ом Toast.makeText, можно быстро создать и показать уведомление. refresh() обновляет состояние поля:
private void refresh() {
        Square[][] field = game.getField();

        for (int i = 0, len = field.length; i < len; i++) {
            for (int j = 0, len2 = field[i].length; j < len2; j++) {
                if (field[i][j].getPlayer() == null) {
                    buttons[i][j].setText("");
                } else {
                    buttons[i][j].setText(field[i][j].getPlayer().getName());
                }
            }
        }
    }


Готово! Надеюсь, эта статья помогла Вам освоиться в мире разработки под OS Android. Благодарю за внимание!

Видео готового приложения





Исходники .

PS: статья была опубликована по просьбе комментаторов этого поста.
@AlexeyFrolov
карма
38,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (68)

  • +8
    Хм… Такое обилие статей в этом блоге предрекает наступление недели разработки под Android? :)
    • +9
      День открытых дверей в детском саду, я бы сказал.
      • +1
        Да ладно вам, не все статьи такие уж и детские :)
      • +6
        А что в этом плохого? Отменный howto для снижения порога вхождения в число разработчиков под андроид.
    • +6
      Было бы очень даже хорошо. Я за неделю Андроида!
      Я, после недавней покупки Андроид девайса, решил попробовать под него что-то создать. Сейчас мне очень не хватает вот таких вот подробных туториалов на русском языке.
      Спасибо, AlexeyFrolov!
    • +6
      Астрологи провозгласили неделю разработки под Android. Прирост HelloWolrd.apk в Android Market удваивается.
  • +3
    по-моему, хорошая статья. продолжайте в том же духе
    • +3
      спасибо :)
      • +9
        Мои 5 копеек — старайтесь избегать стиля «инструкция для дебилов в картинках». :) Серия скриншотов после File > New Project не несет смысловой нагрузки, оправдывающей 3 экрана пространства в посте, imho.

        А в целом статья отличная, спасибо вам. Я как-раз собирался искать, с какой стороны к этой теме подойти.
        • +3
          благодарю и за конструктивную критику и за похвалу.
  • +1
    Awesome!
  • +4
    а есть опыт сравнения IDEA и Eclipse? В чем их плюсы и минусы?
    • +4
      гугл разрабатывает ADT plugin для Eclipse. Поддержкой Android в intellij idea jetbrains занимается самостоятельно. Как IDE для java intellij idea во многом лучше Eclipse, это мнение разделяют многие. Мой выбор определило доверие к компании jetbrains и пока они его оправдывают, так как инструмент очень качественный.
    • +1
      В эклипсе просто нереально работать (моё имхо) познав все прелести Идеи. Без мега-плагинов вполне можно обойтись, так как в большинстве своем они бесполезны. Если правильно настроить идею, можно и дебажить и так далее.
      единственный плюс у эклипсы — новичку проще адаптироваться.
      Но, как известно, фломастеры на вкус разные.
      • +1
        единственный плюс у эклипсы — новичку проще адаптироваться.

        Ну-у, не знаю. Если иметь минимальный опыт работы с какой-либо IDE (не только для Java), то IDEA намного удобнее Eclipse. Дело даже не в плюшках, а в организации интерфейса. С ужасом вспоминаю меню настроек Eclipse, где что-либо найти достаточно трудно.
  • +12
    Это мое восемнадцатое первое приложение под Android!

  • +6
    Как вам удалось заставить эмулятор работать так быстро?
    • 0
      это сарказм? =)
      • +2
        Нет, я серьезно.
        Судя по видео у вас эмулятор просто летает. У меня же почему-то дичайшие тормоза по 2-3 секунды, притом что машина неслабая и памяти я выделил с лихвой. Проще отлаживать приложение прямо на устройстве, чем в этом эмуляторе.
        • 0
          вообще ничего дополнительно не оптимизировал. Хотите, скопирую сюда конфигурацию моей машины?
          • 0
            Давайте, а еще неплохо было бы узнать какие настройки вы ставите при создании устройства.
      • +1
        Не думаю, ибо у меня он тоже жутко глючит.
        • 0
          У меня c2q q8300, HD4850, 2Gb RAM
          • 0
            Если у Вас Windows 7, попробуйте обновить драйвера на видеокарту с сайта AMD. Не знаю почему, но «из коробки» драйвера на семерку работают не очень корректно, особенно с программами, которые не используют Direct3D для 3D ускорения графики.
            • 0
              До этого стояли нормальные дрова, обновился до последних — никакого эффекта.
              В Таск Менеджере поставил галки напротив четырех ядер, приоритет в риалтайм — то же самое.
              Кстати, эмулятор жрет всего почти 200 мб озу и практически не загружает процессор (~30%), это нормально?
    • +1
      Формула быстрой работы чего угодно очень проста: i7 + GTX560x2 + 8GB RAM

      А если серьезно, то даже на AMD Athlon II X3 + 3GB RAM + Ati 5570 работает не намного медленней.
      • 0
        Не поверите, i7 920 + GTX285 + 6GB RAM, а на деле как будто Celeron 400 :)
        • +4
          Тогда остается посоветовать Вам не держать Crysis на макс настройках в фоне во время работы :)
        • 0
          Совет по делу — попробуйте отключить Hyper-Threading и VT-X если не используете виртуальные машины, а также подтащите частоту на 3 Gz. Потому что хоть и эмулятор запрашивает туеву хучу ресурсов, но Тurbo Boost иногда работает не совсем корректно.
  • –1
    Уважаемые гуру верстки, а что тут не так, почему съезжает? ? В хабраредакторе все ок, тем не менее…
    • 0
      Не закрыт тег </ol> после списка с перечнем необходимых инструментов.
      • –2
        запустил специально IDE для того, что бы проверить =). Оказалось не хватало закрывающего слеша в теге .
  • 0
    Та часть, которая описывает WinnerChecker'ы написана в лучших традициях Java, вместо описания малюсенького кусочка кода на несколько строчек — создание интерфейса и подклассов
    • 0
      да, люблю это =). Вот такие вот бывают PHP-шники =).
      • 0
        просто люблю декомпозировать сложность… Часто помогает упростить решение задачи, а так же улучшает testability.
    • +1
      советую вам разобраться, для чего были сделаны эти интерфейсы и подклассы. И заодно может быть поймете, почему java тут абсолютно не причем.
      • 0
        поделитесь знанием пожалуйста
        я уверен, что в крестиках-ноликах не нужны паттерны из GOF'а
        • +1
          ну в крестиках-ноликах может быть и не шибко нужны, а вообще это просто хороший стиль написания программ, в данном случае скорее просто тренировка в применении паттернов.
        • 0
          Нужность — понятие субъективное.
          В программировании кроме как для тривиальных функций в одну строчку можно много чего надекомпозировать. Поэтому все определяется целью.

          Если это учебная программа, то имеет смысл повышать читабельность, декомпозируя для увеличения прозрачности процесса (но не больше! см. KISS).

          Если это тестовый код для проверки конкретной фичи — декомпозиция не нужна.

          Если же живая система… Очень много зависит от контекста, требований и команды. На текущий момент есть несколько источников, в которых эта тема хорошо рассмотрена. Предлагаю почитать «Совершенный код» и «Чистый код» для определения нужной степени детализации.
          • +1
            я хотел как раз упростить этот кусок в программе с целью не распугать новичков трехэтажным for с ветвлениями внутри. Кроме того, думаю, новичкам будет полезно узнать, как этот конкретный шаблон помогает упростить (и улучшить) код в этом конкретном кейсе, что, несомненно, более доходчиво, чем изучать их (паттерны) по абстрактным примерам.
    • +1
      Та часть, которая описывает WinnerChecker'ы написана в лучших традициях Java, вместо описания малюсенького кусочка кода на несколько строчек — создание интерфейса и подклассов
      Да, Java она такая негодяйка — как-то незаметно заставляет нормально декомпозировать задачу вместо портянки вложенных друг в друга циклов на вполовину меньше строчек.
  • +1
    спасибо, было интересно почитать
  • +2
    Так, а кто так бодро сливает автору карму? 11 голосов, а результат всего +2.

    И, как всегда, без комменариев. )
    • +4
      мда, забавно =))

    • +1
      Конкуренты из Android Market стараются :)
  • +3
    Зачем в каждой статье писать, как настраивать IDE?
    • +4
      Например я — пользовался только Eclipse, если я захочу попробовать IDEA — мне это будет весьма полезно.
    • +4
      Думаю нужна статья, где описывается настройки каждой IDE, потом авторы просто ссылки на эту статью давали бы…
      • 0
        Поддержка Netbeans бы не помешала… То, что есть, у меня не заработал эмулятор.
        • 0
          Настроил NetBeans без проблем. Если сам не разобрался — напиши, могу помочь. Однако читая комменты понял, что Eclipse и Idea лучше подходят для разработки под Android.
  • 0
    Спасибо за труд.
  • 0
    Не совсем понятно как Вы перешли от LinearLayout, который строится по умолчанию к tablelayout.
    И куда это ставить этот «private TableLayout layout;»
    • 0
      я думаю, Вам будет проще скачать исходники и посмотреть.
      • 0
        обновите статью, я там комментарий добавил. Плохое форматирование сбило Вас с толку видимо.
  • –2
    Полезная статейка, вот как раз подумывал сменить Eclipse и пощупать Android. Спасибо за статью!
    • 0
      Скажу сразу, jetbrains пока не заносили =)
      • 0
        хотя для меня сейчас ой как кстати была бы лицензия phpstorm2…
  • 0
    > Scr — это папка в которой будет происходить основная часть работы
    имелось в виду Src? Привет от дедушки Зигмунда :)
    • 0
      спасибо, поправил
  • 0
    Поправь в статье layout на тот, который у тебя в исходниках. Если взять layout из статьи, то приложение не запустится.

    <?xml version="1.0" encoding="utf-8"?>
    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/main_l"
    android:gravity="center"
    >

    • 0
      Поправил. Извините, что не оперативно. Спасибо за наводку.
  • 0
    Спасибо за статью.

    Все заработало на эмуляторе.

    Подскажите как сделать чтобы заработало на девайсе.

    Скажем HTC Desire (внутри проекта и девайса Andoid 2.2) воткнут в USB порт.

    Никак не найду кнопки Target: device///
  • 0
    В текущей версии IDEIA проект компилируется сочетанием SHIFT+F10, а не F6
  • 0
    О_О Классно… Но столько кода… Никогда мне не полюбить яву =(
  • 0
    Название статьи не соответствует содержанию.
    Нужно было назвать «крестики-нолики для android».
    Если собирались рассказать о том как писать именно приложения, то пусть тест самой программы был бы «Hello world», но описали бы весь цикл, до установки программы на устройство.
    А то толку в запуске на эмуляторе нет. Если нужны крестики-нолики под Windows — их уже 100500 вариантов наделали.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.