Category Archive: Android

Запустил платное приложение Bilemin Cards в Google Play

Наконец-то, 31 октября 2016 я запустил своё первое платное приложение в Google Play под названием «Bilemin Cards».
Вот ссылка на приложение: https://play.google.com/store/apps/details?id=kz.bilemin.cards. Стоимость приложения 3$, что составляет 990 тенге.
Вообще, я настроен скептически, кажется, я вообще не заработаю денег на этом приложении, но посмотрим
В этой статье я буду через определенные промежутки времени писать о всём, что касается приложения, и что я буду делать для продвижения приложения.

Ноябрь, 2016
В ноябре я начал рекламировать приложение через adwords. Кампания длилась неделю, бюджет был 1$/день.

Показы

24 894

Взаимодействия

437 кликов

Коэффициент взаимодействия

1,76 % CTR

Средн. цена

0,02 $ за клик

Стоимость

9,60 $

Итого за 7 дней взяли 9,60$. Почему не 7$ спросите вы? Показ рекламы не может сразу остановиться, поэтому каждый день уходило более 1$.

Вывод: Реклама не сработала, никто не купил приложение. Любой бюджет можно скликать 🙂

01.12.2016
Отправил приложение на конкурс award.kz в номинацию «Мобильные приложения».
Было более 350 просмотров и один лайк.
Предварительные результаты голосования:

p.s. пока 11-е место, даже не в первой десятке 🙂
p.p.s. организаторы запросили 7 промо-кодов для оценки приложения, я же им передал 10 кодов! При своей оценке они не активировали ни одного кода!! Вообще, до этого я сгенерил 500 промо-кодов, теперь думаю как их распространить.
13.12.2016
Запустил бесплатную версию приложения: https://play.google.com/store/apps/details?id=kz.bilemin.freecards
p.s. Не пощупав, никто ничего не купит, по крайней мере, наши люди 🙂
15.12.2016
Состоялась первая покупка приложения. Неожиданно, если честно 🙂
20.12.2016
Освежил группу вконтакте по новогоднему: https://vk.com/bilemin
Создал запись: https://vk.com/bilemin?w=wall-44243064_8 и начал её рекламу через VK:

25.12.2016
На прошлой неделе я запускал рекламу ВКонтакте. Сперва рекламировал запись в сообществе:

Статистика следующая:

В итоге показы почему-то упали.
Далее замутил схему с промо-кодами, но рекламу не приняли модераторы.
В итоге решил рекламировать приложения напрямую:

Тут уже надо было платить не за показы, а за переходы.

В итоге всего 8 человек на сегодняшний день установили бесплатное приложение. Короче, реклама в VK не дала результатов.

25.12.2016
Решил рекламировать своими силами. Взял базу пользователей сайта bilemin.kz, что примерно 1100 человек и начал делать рассылку.
03.01.2017
Год завершился с такими показателями:

Если с 1000 отправок писем целевой аудитории будет хотя бы одна установка, то можно отправлять миллионы писем.
Сегодня с mail.ru выудил 438 email’ов из Казахстана и сделал рассылку.
05.01.2017
Разослал еще 4000 писем. Статистика еще не обновилась, что-то Google Play запаздывает.
Наконец-то, обновилась статистика:

14.01.2017
Что-то застопорились скачивания. Прошло более недели, а прирост мизерный.

24.01.2017
Прошло 10 дней. Несколько дней назад вновь запускал рассылку по 4000 адресам (другая выборка) и ждал пока обновиться статистика.

p.s. Уныло ждем 50 установок 🙂

Установка CyanogenMod 11 на HTC One X

Это был мой первый опыт по установке неродной прошивки, поэтому я наступил на много граблей. Данная статья призвана помочь вам не допустить моих ошибок…

Два совета, которые позволят прошить CyanogenMod без лишних проблем и реанимации «кирпича»:

  1. Зарядите телефон на 100%
  2. Обязательно обновите HBOOT до версии 1.36.0000 или чуть выше

Почему перед началом работ по перепрошивке нужно зарядить телефон на 100%?
Расскажу свою историю… С первых моих действий я совершил ошибку — не стал заряжать сотку до рекомендуемых 70%, думал, что сотка во время прошивки будет заряжаться от кабеля. Но не тут то было, зарядкой батареи управляет прошивка. На телефоне была старая версия HBOOT, поэтому все мои попытки установки CyanogenMod были тщеты, и за это время телефон разрядился в ноль, перестав включаться.
Продержав сутки телефон на зарядке, он так и не зарядился. Где-то прочитал, что нужно передернуть провода на батарее. Особенность HTC One X в том, что батарея несъемная, поэтому я разобрал телефон, чтобы перетыкнуть провода:
htc-one-x-disassembled
Причем, чтобы добраться до батерии, нужно разобрать — всё.
Передернув провод, я собрал аппарат. Боялся, что мог забыть подключить какой-нибудь шлейф (но в конце выясниться, что собрал девайс верно).
Аппарат по прежнему не включался, потом вычитал, что можно поключая, а потом отключая USB-кабель можно немного зарядить телефон. В итоге у меня включился телефон. И я понял, что разбирать телефон было не нужно!

Далее необходимо хорошо зарядить телефон, для этого я запускал вечно выполняющийся скрипт charge.bat.
Вот код этого скрипта:

@echo off
:start
fastboot getvar battery-voltage
fastboot reboot-bootloader
ping /n 6 localhost >nul
goto start

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

No Carrier на Google Play

После публикации приложения на Google Play выяснилось, что на планшетах заказчика приложение не поддерживается. Причем мой планшет Asus поддерживается, а Samsung Galaxy Tab 4 не поддерживается, хотя там и там Android 4.4.2.
Если на Samsung Galaxy Tab искать приложение в Google Play, то оно не находится, т.к. не поддерживается. Если зайти на страничку приложения по ссылке, то можно увидеть сообщение «No Carrier». Сразу поняли, что дело в телефонных вызовах. У нас в приложении есть возможность набрать номер службы поддержки, поэтому в манифесте прописано разрешение:

<uses-permission android:name="android.permission.CALL_PHONE" />

Сперва подумали, что нужно вставить в планшет нормальную симку, а не LTE, но мой планшет вообще устанавливал приложение с Google Play без симки.
В итоге: где-то вычитал, что некоторые планшеты почему-то не могут совершать звонки.
Т.к. в нашем приложении вызов службы поддержки — это вспомогательный функционал, то прописали в манифесте необязательность телефонных вызовов:

<uses-feature android:name="android.hardware.telephony" android:required="false" />

После того, как прописали необязательность телефонных звонков, в Google Play к 5200-м поддерживаемым устройствам добавилось еще 500 устройств, в том числе Samsung Galaxy Tab 4. Проблема решилась!

Соответствие разрешений (uses-permission) и функций (uses-feature) можно посмотреть здесь:
http://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions-features

org.apache.http.NoHttpResponseException: The target server failed to respond

Случилось так, что в двух моих Android-приложениях перестала работать статистика. Хотя приложения уже работали более года без обновлений, да и сервис статистики я не трогал.
Запустив одно из приложений под отладкой, обнаружил ошибку:

org.apache.http.NoHttpResponseException: The target server failed to respond

Потом локализовал ошибку. Вот фрагмент проблемного кода:

HttpParams httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);//тайм-аут подключения
HttpConnectionParams.setSoTimeout(httpParams, 10000);//тайм-аут сокета
HttpClient httpClient = new DefaultHttpClient(httpParams);
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);//тут выбрасывается Exception

Попробовал переписать код так (без HTTP-параметров).:

HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);

Статистика в приложении начала работать. Т.е. дело оказалось в передаваемых параметрах. Мне кажется, провайдер хостинга что-то обновил, и теперь сервер перестал понимать передаваемые запросы.
Погуглив, нашел другой способ передачи параметров, способ — работает!

HttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(HttpConnectionParams.CONNECTION_TIMEOUT, 5000);
httpClient.getParams().setParameter(HttpConnectionParams.SO_TIMEOUT, 10000);
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);

Как видно из приведенного выше кода:

  1. Нужно создать объект HttpClient, используя конструктор без параметров DefaultHttpClient()
  2. С помощью httpClient.getParams().setParameter(String name,Object value) установить необходимые параметры

Надеюсь, этот код прослужит долго!

Unable to execute dex: java.nio.BufferOverflowException

Снова при запуске импортированного приложения повторилась следующая ошибка:

[2014-05-09 23:36:21 - Dex Loader] Unable to execute dex: java.nio.BufferOverflowException. 
Check the Eclipse log for stack trace.
[2014-05-09 23:36:21 - HelloJni] Conversion to Dalvik format failed: 
Unable to execute dex: java.nio.BufferOverflowException. 
Check the Eclipse log for stack trace.

И снова решение проблемы плохо гуглиться: в основном предлагают откатить или обновить версию SDK. После гугления вспомнил, как решается проблема.
Нужно чтобы целевая версия SDK совпадала в манифесте приложения (AndroidManifest.xml) и в свойствах проекта (project.properties)
Например, в представленных ниже фрагментах указана 19-я версия SDK.
AndroidManifest.xml:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

project.properties:

# Project target.
target=android-19

Номера версий Android

Номера версий Android:

Кодовое название

Версия

Уровень API

1.0

API level 1

1.1

API level 2

Cupcake

1.5

API level 3, NDK 1

Donut

1.6

API level 4, NDK 2

Eclair

2.0

API level 5

Eclair

2.0.1

API level 6

Eclair

2.1

API level 7, NDK 3

Froyo

2.2.x

API level 8, NDK 4

Gingerbread

2.3 — 2.3.2

API level 9, NDK 5

Gingerbread

2.3.3 — 2.3.7

API level 10

Honeycomb

3.0

API level 11

Honeycomb

3.1

API level 12, NDK 6

Honeycomb

3.2.x

API level 13

Ice Cream Sandwich

4.0.1 — 4.0.2

API level 14, NDK 7

Ice Cream Sandwich

4.0.3 — 4.0.4

API level 15, NDK 8

Jelly Bean

4.1.x

API level 16

Jelly Bean

4.2.x

API level 17

Jelly Bean

4.3.x

API level 18

KitKat

4.4 — 4.4.2

API level 19

Оригинальная версия: https://source.android.com/source/build-numbers.html

Ошибка: Finalizing a Cursor that has not been deactivated or closed

Решил схалявить в коде, в итоге два часа убил, чтобы найти ошибку с курсором SQLite в Android.

Текст ошибки:

Finalizing a Cursor that has not been deactivated or closed

Или

android.database.sqlite.DatabaseObjectNotClosedException: 
Application did not close the cursor or database object that was opened here

Изначально было понятно, что где-то не закрывается курсор.
Перепроверил все курсоры, после использования все курсоры закрываются. Причем, стек ошибок показывал фрагмент, где именно ошибка, но понял это лишь под отладкой.
Вот фрагмент косячного кода:

public int getBufferCount(){
	String sql = "SELECT COUNT(*) FROM " + TBL_BUFFER;
	Cursor cursor = db.rawQuery(sql, null);
	cursor.moveToFirst();
	if(!cursor.isAfterLast()){
		return cursor.getInt(0);//ошибка
	}
	cursor.close();
	return 0;
}

Т.е. я делал return cursor.getInt(0);, а курсор оставался открытым!

Исправленный вариант №1:

public int getBufferCount(){
	String sql = "SELECT COUNT(*) FROM " + TBL_BUFFER;
	Cursor cursor = db.rawQuery(sql, null);
	cursor.moveToFirst();
	int ret = 0;
	if(!cursor.isAfterLast()){
		ret = cursor.getInt(0);
	}
	cursor.close();
	return ret;
}

Исправленный вариант №2:

public int getBufferCount(){
	String sql = "SELECT COUNT(*) FROM " + TBL_BUFFER;
	Cursor cursor = db.rawQuery(sql, null);
	cursor.moveToFirst();
	if(!cursor.isAfterLast()){
		try{
			return cursor.getInt(0);
		}finally{
			cursor.close();
		}
	}
	cursor.close();
	return 0;
}

p.s.: Всё, что имеет метод close(), должно быть closed!

Перевод десятичных грудусов в градусы, минуты и секунды

При работе с картами или GPS мы можем получить долготу (longitude) и широту (latitude) в виде виде десятичных грудусов. Например, 37.422006 (широта) и -122.084095 (долгота). Это где-то в Силиконовой долине. Нам может потребоваться перевести эти десятичные градусы в градусы, минуты и секунды, т.е. в формат более понятный человеку.

Для справки:

  • Положительное значение широты — северная широта
  • Отрицительное значание широты — южная широта
  • Положительное значение долготы — восточная долгота
  • Отрицательное значение долготы — западная долгота

Преобразование будет реализовано в виде функции, принимающей в качестве параметра десятичный градуc (double), а в качестве результата возвращающая строку вида: X° Y′ Z″, где X — градусы, Y — минуты и Z — секунды.
Реализация на Java:

public static String doubleToDegree(double value){
	int degree = (int) value;
	double rawMinute = Math.abs((value % 1) * 60);		
	int minute = (int) rawMinute;
	int second = (int) Math.round((rawMinute % 1) * 60);
	return String.format("%d° %d′ %d″", degree,minute,second);
}

Пример использования:

System.out.println(doubleToDegree(37.422006));//Результат: 37° 25′ 19″
System.out.println(doubleToDegree(-122.084095));//Результат: -122° 5′ 3″

Результаты можно интерпретировать следующим образом:
37° 25′ 19″ означает 37 градусов 25 минут 19 секунд северной широты.
-122° 5′ 3″ означает 122 градуса 5 минут 3 секунды западной долготы.

Задание: создать клиент Twitter’а (OAuth и REST API)

Недавно проходил собеседование на должность удаленного Android-разработчика.

Дали следующее тестовой задание:
Задание
Создать простой twitter-клиент. Приложение должно содержать следующие функции:

  • Отображение списка твитов авторизованного пользователя (основной экран)
  • Имена пользователей должны выделятся в тексте твита и при нажатии на имя пользователя должен отображаться список твиттов этого пользователя.
  • Создание нового твита (максимум 140 символов)

Требования:

  1. Реализация на языке Java.
  2. Разрешается использовать любые библиотеки кроме непосредственно twitter-библиотеки.

После выполнения задания нужно прислать код (либо ссылку на репозитоий на GitHub), указав количество потраченного на задание времени.

Информация для чтения
Twitter API: https://dev.twitter.com/docs/api

От себя скажу, что задание очень понравилось. На всё про всё потратил около двух суток.
С твиттером я был знаком очень слабо: раньше когда-то регистрировался, но толком не пользовался.
Также у меня не было опыта по работе с OAuth (википедия) и REST API (википедия). Разобравшись с OAuth авторизацией и REST API, вы сможете по такому же принципу интегрироваться почти со всеми порталами и соц. сетями.

Ниже приведу скриншоты того, что получилось.
1) При первом запуске приложение перенаправляет пользователя в браузер для авторизации. После успешной авторизации браузер обратно передает управление приложению.
01
2) Открывается главное окно с твитами текущего пользователя:
02
3) Если нажать на имя пользователя, то откроются твиты этого пользователя:
03
4) Нажав кнопку «Новый твит», откроете диалоговое окно ввода текста нового твита:
04
5) Нажав кнопку «Отправить», твит появиться в твитах авторизованного пользователя:
06
6) По нажатию кнопки «Выход» приложение снова откроет странице авторизации, в которой можно будет залогиниться под другим пользователем:
07

При написании приложения очень помог код с GitHub’а: https://github.com/ddewaele/AndroidTwitterSample

Код моего приложения также есть на GitHub: https://github.com/Nyquest/twitterclient

Можно также скачать исходный код отсюда

APK-файл: скачать

Подписанный APK-файл: скачать

Неизвестный пасьянс

19 октября выпущена первая версия пасьянса.

Представляю вашему вниманию мой любимый пасьянс, который я реализовал под Android.
Ссылка в Google Play (Play Market): https://play.google.com/store/apps/details?id=kz.naik.unknownsolitaire

Логотип пасьянса:
512x512

Игровое поле выглядит следующим образом:
desc
В пасьянсе 36 карт. Все карты разбиты на колоды по 3 карты:
trio
Цель пасьянса: разложить все карты по четырем домам:
houses
Только верхние карты колод доступны для перемещения:
only_top
Возможно два вида перемещения карт:
1) Перемещение в дом
2) Перемещение между колодами

Перемещение в дом
В пустой дом вы можете положить любой доступный для перемещения Туз:
add_ace
Вслед за Тузом вы
должны положить карты такой же масти в следующей последовательности:
Король, Дама, Валет, Десятка, Девятка, Восьмерка, Семерка и Шестерка.

Перемещение между колодами
Перемещение между колодами возможно, если перемещаемая карта одного достоинства
с картой, на которую перемещается. Например, мы можем переместить Шестерку
только на Шестерку, Даму на Даму и т.д.
movement_between
Если в колоде уже имеется три карты, то переместить в эту колоду карты нельзя:
full
Примечание
При старте новой игры в случае отсутствия допустимых ходов вы можете
взять любой туз из любой колоды, но только один.
any_ace
Завершенный пасьянс выглядит следующим образом:
completed