Советы и рецепты начинающему Android программисту

Добрый день, уважаемые хабраюзеры.

В данной статье я хочу поделиться своим опытом разработки под Android.
Требования к функционалу разрабатываемого продукта породили различные технические задачи, среди которых были как тривиальные, разжеванные во множестве блогов, так и крайне неоднозначные, с неочевидным решением. Я столкнулся с массой вещей, незнакомых мне, как .NET разработчику. Узнал о существовании инструментов, которые значительно упрощают жизнь. Думаю, что каждый начинающий андроидовец проходит похожий путь. Я мог бы сэкономить до трети времени, потраченного на разработку, поиск и эксперименты, имея такую статью.

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

Статья предназначена для начинающих, но и опытные разработчики смогут найти полезные моменты. Предполагается, что вы прочитали основы построения Android приложений, знаете о замечательном ресурсе StartAndroid и умеете делать HelloWorld. При этом опыта создания полноценных приложений у вас нет, и вы только что занялись устранением этого недостатка. Для меня же это был первый проект под Android.

Начало


Мы с напарником давно подумывали создать какой-нибудь интересный продукт для Google Play. В один прекрасный день, при прочтении очередной СМС с рекламой такси, возникла идея создать приложение, которое будет бороться с СМС спамом. Это показалось нам интересным, имеющим практическое применение, относительно несложным в реализации для небольшой команды.

Далее был выработан набор конкретных требований и сформирован набор задач, которые надо решить. Самые интересные из них:

Опущены в статье будут следующие моменты:
  • Верстка и дизайн
  • Все, что касается чисто Java кода
  • Архитектура приложения

Подготовка проекта


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

1) ActionBarSherlock необходим для реализации платформонезависимого ActionBar — меню вверху экрана. Качаем с официального сайта и импортируем в Workspace в виде исходников. Просто библиотеки (jar файла) будет недостаточно, так как есть известная проблема с недогрузкой некоторых ресурсов библиотекой.

2) Импортируем в Workspace в виде исходников Google play services из SDK sdk\extras\google\google_play_services\libproject\google-play-services_lib\. Это понадобится для биллинга и авторизации.

3) Положим в папку lib проекта библиотечки (перед этим найдем их в интернете)
* acra.jar — для реализации механизма отправки отчетов о краше приложения: ACRA.
* android-support-v4.jar — для реализации совместимости со старыми версиями Android.
* roboguice-2.0.jar, roboguice-sherlock-1.5.jar — для реализации Dependency Injection, если понравится реализация его в roboguice.
* ormlite-core.jar, ormlite-android.jar — популярная «легковесная» ORM для sqlite базы Android.
* joda-time.jar — библиотека для работы с датами.
* jdom.jar, gson.jar — для работы с JSON.
* checkout.jar — для биллинга (выбрал эту библиотечку, Checkout как более удобную, чем работа непосредственно с api).

Получение и разбор CMC


Ниже я приведу способ, работающий на Android с версией ниже 4.4 (KitKat), посколько в данной версии Google радикально поменял подход к обработке CMC. Описание работы с KitKat добавлю позже, когда это будет реализовано мной в приложении.

Для работы с CMC нам нужно разрешение в манифесте:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />

где
RECEIVE_SMS — разрешение приложению получать CMC.
READ_SMS — разрешение на чтение смс из памяти телефона. Казалось бы, оно нам не нужно, но без этого разрешения не работает запись.
WRITE_SMS — разрешение на запись CMC в память телефона.

Создадим прослушиватель события «принято CMC» SmsBroadcastReceiver. Он будет вызываться в момент получения телефоном СМС и запускать выполнение основных процессов по обработке СМС.

SmsBroadcastReceiver
//BroadcastReceiver обязательный предок прослушивателей системных событий
public class SmsBroadcastReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		//Интент - входной объект с данными, передаваемый всем прослушивателям
		Bundle bundle = intent.getExtras(); 
		//Извлекаем из словаря некий pdus - в нем информация о СМС
		Object[] pdus = (Object[]) bundle.get("pdus"); 

		if (pdus.length == 0) {
			return; // Что то пошло не так
		}
		// читаем CMC
		Sms sms = SmsFromPdus(pdus, context); 
		// определям, спам ли
		Boolean isClearFromSpam = SuperMegaMethodForResolving Spam(sms, context); 

		if (!isClearFromSpam) {
			// если это спам - прекращаем обработку CMC системой
			abortBroadcast(); 
			return;
		}
	}

	private Sms SmsFromPdus(Object[] pdus, Context context) {
		Sms sms = new Sms();
		for (int i = 0; i < pdus.length; i++) {
			SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
			sms.Text += smsMessage.getMessageBody(); //соберем весь текст (CMC может быть "многостраничной")
		}

		SmsMessage first = SmsMessage.createFromPdu((byte[]) pdus[0]);//из первой страницы получим
		sms.SenderId = first.getOriginatingAddress(); //отправителя
		Date receiveDate = new Date(first.getTimestampMillis()); //дату
		sms.RecieveDate = receiveDate;
		sms.Status = first.getStatus(); //статус (новое, прочтено, доставлено)

		return sms;
	}
}

public class Sms{
	public String SenderId;
	public String Text;
	public Date RecieveDate;
	public int Status;
}


Очень важно, чтобы onReceive отрабатывал менее чем за 10 секунд. Если метод захватывает управление на больший срок, исполнение прерывается и событие отдается другим обработчикам в порядке приоритета.
В моем случае в SuperMegaMethodForResolving происходит проверка на наличие СМС в списке контактов и в локальном списке отправителей, что занимает менее секунды. Затем управление отдается в выделенный поток, а onReceive вызывает abortBroadcast, что не дает другим обработчикам получить СМС (в том числе базовому приложению для СМС).

После нам необходимо подписать SmsBroadcastReceiver на событие приёма CMC. Для этого в блоке application манифеста объявим прослушиватель события android.provider.Telephony.SMS_RECEIVED с именем SmsBroadcastReceiver, который слушает системное событие SMS_RECEIVED и имеет приоритет 2147483631. Приоритет может быть до 2^31. При этом Google не рекомендует использовать значения больше 999. Но многие приложения их используют, а мы хотим, чтобы антиспам перехватывал CMC до того, как оно будет прочтено, например, приложением Contacts+. Это приложение запрашивает самый высокий из известных мне приоритетов.

<receiver
	android:name="su.Jalapeno.AntiSpam.SystemService.SmsBroadcastReceiver"
	android:enabled="true"
	android:exported="true" >
	<intent-filter android:priority="2147483631" >
		<action android:name="android.provider.Telephony.SMS_RECEIVED" />
	</intent-filter>
</receiver>

Автозапуск приложения при загрузке телефона


Запуск приложения после включения устройства может понадобиться для, например, уведомления пользователя о существовании еще непроверенных подозрительных СМС посредством нотификации.

Для автозагрузки потребуется разрешение в манифесте:

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

где
RECEIVE_BOOT_COMPLETED — разрешение слушать событие «загрузка»

Создадим прослушиватель события «загрузка» ServiceBroadcastReceiver.

public class ServiceBroadcastReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		//Сделаем что-нибудь
	}
}

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

Затем потребуется подписать ServiceBroadcastReceiver на событие загрузки телефона. Для этого в блоке application манифеста объявим прослушиватель события android.intent.action.BOOT_COMPLETED с именем SmsBroadcastReceiver.

<receiver
	android:name="su.Jalapeno.AntiSpam.SystemService.ServiceBroadcastReceiver"
	android:exported="true" >
	<intent-filter>
		<action android:name="android.intent.action.BOOT_COMPLETED" />
	</intent-filter>
</receiver>

Осуществление веб запросов


Итак, у нас есть CMC, и надо связаться с сервером. Дополним манифест следующим образом:

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

где
INTERNET — разрешение отправлять веб запрос.
ACCESS_NETWORK_STATE — разрешение на чтение статуса сети (подключен или нет 3g или wifi).

Осуществление запросов тривиально, для них используем базовый Android http client: org.apache.http.client.HttpClient.

Хранение данных в локальной БД


Делаем все так, как описано здесь.

Хранение настроек


Для хранения настроек приложения мы не используем app.config, *.ini или БД приложения, поскольку Android предоставляет нам механизм SharedPreferences.

Авторизация посредством токена Google


Это отняло у меня больше всего времени, из-за несистематизированности и неполноты информации, включая документацию Google. Итак, наша задача — получить от Google через приложение подписанный токен с информацией о пользователе и секретными сведениями о приложении. Это даст нам основание полагать, что токен сгенерирован не злоумышленником. Для решения этой задачи используем механизм CrossClientAuth.

Необходимо сделать следующее:
1) Получить сертификат приложения и подписать им приложение. Это просто осуществить в Eclipse с помощью мастера. Нажмем правой кнопкой мыши на проекте в Package Explorer -> Android tools -> Export signed application package. Мастер предложит создать новое хранилище сертификата, сгенерировать сертификат и поместить его в хранилище, защищенное указанным нами паролем. Не забудем сохранить хеш сертификата, поскольку он в дальнейшем понадобится.

2) Создать проект в консоли Google. Затем открыть созданный проект и перейти на вкладку Api & auth -> Credentials в панели слева. Здесь необходимо создать пару Client Id для работы серверной части и Android клиента. Жмем Create new Client ID, нам нужен Client ID for Android application.



Заполняем, как указано на скриншоте, указав корректное имя пакета и отпечаток сертификата. После завершения создания получим табличку с информацией и сгенерированным для нас «CLIENT ID». Он понадобится нам на сервере.

Затем создаем новый Client Id типа Web application. В моем случае адреса можно указать произвольно, так как у нас не будет взаимодействия по http с веб ресурсами Google. В результате получим новый CLIENT ID, он уже понадобится на клиенте.

3) Клиентский код в полном виде можно найти в интернете, например GoogleAuthUtil. Отмечу только ключевые моменты: как правильно составить Scope, и откуда взять для него Id

Код
//выбор аккаунта
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (requestCode == REQUEST_CODE_PICK_ACCOUNT) {
		if (resultCode == RESULT_OK) {
			Email = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
			getUsername();
		}
	}
	super.onActivityResult(requestCode, resultCode, data);
}

private void pickUserAccount() {
	String[] accountTypes = new String[] { "com.google" };
	Intent intent = AccountPicker.newChooseAccountIntent(null, null, accountTypes, false, null, null, null, null);
	startActivityForResult(intent, REQUEST_CODE_PICK_ACCOUNT);
}

//Код получения токена (по хитрому обернутый Try catch и в фоновом потоке
//WEB_CLIENT_ID из Client ID for web application
final private String WEB_CLIENT_ID = "1999999-aaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com";
//"Область" применения Client id. Тут указано что мы хотим аутентификацию
String SCOPE = String.format("audience:server:client_id:%s", WEB_CLIENT_ID);
//Вот и токен
String token = GoogleAuthUtil.getToken(_activity, Email, SCOPE);


Осталось передать токен серверу

4) Серверный код для проверки токена
Используем Microsoft.IdentityModel.Tokens.JWT Nuget. Нижеприведенный код позволяет получить GoogleId юзера и его Email.

Код
public string GetUserIdByJwt(string jwt, out string userEmail)
{
	userEmail = string.Empty;
	string userId = null;
	//Секретный Client ID веб сервиса (Client ID for web application)
	string audience = "111111111111111111-aaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com";
	//Секретный Client ID приложения (Client ID for Android application)
	string azp = "1111111111111-aaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com";

	var tokenHandler = new JWTSecurityTokenHandler();
	SecurityToken securityToken = tokenHandler.ReadToken(jwt);
	var jwtSecurityToken = securityToken as JWTSecurityToken;

	userEmail = GetClaimValue(jwtSecurityToken, "email");
	userId = GetClaimValue(jwtSecurityToken, "id");

	var validationParameters =
		new TokenValidationParameters()
		{
			AllowedAudience = audience,
			ValidIssuer = "accounts.google.com",
			ValidateExpiration = true,
			//с либами для токенов некоторая неразбериха. 
			//По какой то причине не удалось заставить проверять подпись Google 
			//в токене средствами Microsoft.IdentityModel
			ValidateSignature = false, 
		};

	try
	{
		//Выкинет Exception, если токен не валидный
		ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtSecurityToken, validationParameters);
		//Сверим, что наши Client Id совпадают с токеновскими
		bool allGood = ValidateClaim(jwtSecurityToken, "azp", azp) && ValidateClaim(jwtSecurityToken, "aud", audience);
		if (!allGood)
		{
			userId = null;
		}
	}
	catch
	{
		userId = null;
	}

	return userId;
}

//Сверим значение в Claim с ожидаемым
private static bool ValidateClaim(JWTSecurityToken securityToken, string type, string value)
{
	string claim = GetClaimValue(securityToken, type);

	if (claim == null)
		return false;

	return claim == value;
}

//Получим значение из Claim (по сути KeyValuePair)
private static string GetClaimValue(JWTSecurityToken securityToken, string type)
{
	var claim = securityToken.Claims.SingleOrDefault(x => x.Type == type);

	if (claim == null)
		return null;

	return claim.Value;
}


Работа с механизмом покупок


Для начала необходимо в девелоперской консоли Google у проекта приложения на вкладке КОНТЕНТ ДЛЯ ПРОДАЖИ создать нужные товары. Клиент будем писать на базе примеров Checkout. Здесь приведу выдержки из своего кода, касающиеся биллинга, для более полного понимания библиотеки Checkout.

Изменения в классе Application
public class MyApplication extends Application {
	private static final Products products = Products.create().add(IN_APP, 
		asList("Ид вашего товара из консоли гугл", "Ид вашего товара2 из консоли гугл"));
		
	private final Billing billing = new Billing(this, new Billing.Configuration() {
		@Nonnull
		@Override
		public String getPublicKey() {
			String base64EncodedPublicKey = "ЛИЦЕНЗИОННЫЙ КЛЮЧ ДЛЯ ЭТОГО ПРИЛОЖЕНИЯ, который вы можете взять на вкладке СЛУЖБЫ И API консоли разработчика";

			return base64EncodedPublicKey;
		}

		@Nullable
		@Override
		public Cache getCache() {
			return Billing.newCache();
		}
	});
	
	@Nonnull
	private final Checkout checkout = Checkout.forApplication(billing, products);
	@Nonnull
	private static MyApplication instance;

	public MyApplication() {
		instance = this;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		billing.connect();
	}

	@Nonnull
	public static MyApplication get() {
		return instance;
	}

	@Nonnull
	public Checkout getCheckout() {
		return checkout;
	}
}


Активити с покупкой
public class BillingActivity extends RoboSherlockActivity {
	private Sku _skuAccess;
	
	@Nonnull
	protected final ActivityCheckout checkout = Checkout.forActivity(this,
			MyApplication.get().getCheckout());
	@Nonnull
	protected Inventory inventory;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		_skuAccess = null;
		_activity = this;
		checkout.start();
		checkout.createPurchaseFlow(new PurchaseListener());
		inventory = checkout.loadInventory();
		inventory.whenLoaded(new InventoryLoadedListener());
	}
	
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		checkout.onActivityResult(requestCode, resultCode, data);
	}
	
	@Override
	protected void onDestroy() {
		checkout.stop();
		checkout.destroyPurchaseFlow();
		super.onDestroy();
	}

	@Nonnull
	public ActivityCheckout getCheckout() {
		return checkout;
	}

	public void Buy(View view) {
		purchase(_skuAccess);
	}
	
	private void purchase(@Nonnull final Sku sku) {
		boolean billingSupported = checkout.isBillingSupported(IN_APP);
		if (!billingSupported) {
			return;
		}

		checkout.whenReady(new Checkout.ListenerAdapter() {
			@Override
			public void onReady(@Nonnull BillingRequests requests) {
				requests.purchase(sku, null, checkout.getPurchaseFlow());
			}
		});
	}

	private class PurchaseListener extends BaseRequestListener<Purchase> {
		@Override
		public void onSuccess(@Nonnull Purchase purchase) {
			onPurchased();
		}

		private void onPurchased() {
			//перегрузим инвентарь - и после загрузки инвентаря проверим покупку и обработаем это в приложении
			inventory.load().whenLoaded(new InventoryLoadedListener());
		}

		@Override
		public void onError(int response, @Nonnull Exception ex) {
			// it is possible that our data is not synchronized with data on
			// Google Play => need to handle some errors
			if (response == ResponseCodes.ITEM_ALREADY_OWNED) {
				onPurchased();
			} else {
				super.onError(response, ex);
			}
		}
	}

	private class InventoryLoadedListener implements Inventory.Listener {
		private String _purchaseOrderId;

		@Override
		public void onLoaded(@Nonnull Inventory.Products products) {
			final Inventory.Product product = products.get(IN_APP);
			if (product.isSupported()) {
				boolean isPurchased = InspectPurchases(product);
				//Делаем что-нибудь
			}
		}

		private boolean InspectPurchases(Product product) {
			List<Sku> skus = product.getSkus();
			Sku sku = skus.get(0); //допустим один товар
			final Purchase purchase = product.getPurchaseInState(sku,
						Purchase.State.PURCHASED);
			boolean isPurchased = purchase != null
						&& !TextUtils.isEmpty(purchase.token);						
			if (isPurchased) {
				//Может уже куплено?
				return true;
			}
			else {
				//если нет - запомним товар для покупки
				_skuAccess = sku;
				return false;
			}
		}
	}

	private abstract class BaseRequestListener<Req> implements
			RequestListener<Req> {

		@Override
		public void onError(int response, @Nonnull Exception ex) {
		}
	}
}


Заключение


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

Подробнее
Реклама
Комментарии 65
  • +6
    1)
    Это отняло у меня больше всего времени, из-за несистематизированности и неполноты информации, включая документацию Google.
    Странно, но используя официальную документацию реализовал за пару минут.
    2) Для БД особого смысла использовать ORM нету (под Android). Если есть опыт, когда сие упростило разработку, хотелось бы послушать.
    3) Про ActionBarSherlock… Лучше почитайте автора компонента.

    • 0
      1) Что-то очень странное было у меня. После того Guide, я постоянно получал исключение с одним словом «Unknown», вероятно, что-то я делал не так, но не понял, что именно. В конце концов остановился на варианте из поста.
      2) Здесь вопрос выбора и предпочтений, я, как .net разработчик, не хотел касаться Cursor или ContentProvider, и ORM мне нравится на любых, даже самых мелких проектах
      3) Я поддерживаю Android с версии 2.3, так что цитирую разработчика ActionBarSherlock: If you are writing an application right now with a minSdkVersion lower than 14 you should be using it.
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            У не знаю, как у автора, у наших пользователей 2.3 это почти 10% устройств, так что раз на раз не приходится
            • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Здесь вопрос выбора и предпочтений, я, как .net разработчик, не хотел касаться Cursor или ContentProvider, и ORM мне нравится на любых, даже самых мелких проектах

            Прошу прощение за наглость, но UcaOrm совсем не рассматривали?
            • 0
              Выглядит шикарно: вложенные сущности, запросы, миграции кодом, а не скриптами. Просто не попадалась мне, когда выбирал инструменты. Так это ваша библиотека! На вид — крутая вещь для одного человека. В каком-нибудь новом проекте попробую воспользоватся.
              • 0
                На вид — крутая вещь для одного человека.

                Спасибо! Orm родился как сопутствующая библиотека к другому проекту и, поскольку проект пока забросил, то и orm с марта не обновлял.
                В каком-нибудь новом проекте попробую воспользоватся.

                Буду рад, если моя библиотека еще кому-то пригодится. Если вдруг не будет хватать функционала — обращайтесь, — доработаю!
        • 0
          А потом вы узнали, что с вроде как 4.4.2 блокировщики смс перестали перехватывать смски?
          • 0
            Вообще, очень спорное решение, на мой взгляд. Например, сейчас делаю приложение, которое общается с удаленным устройством через СМС. И вот эти технические СМС будут просто засорять ленту пользователя. Лучше бы уж ввели дополнительный permission. Ну предупреждал бы он пользователей большими красными буквами об опасности, но зачем вообще убирать эту возможность?! Как обычно, гугл лучше пользователя знает, что нужно этому самому пользователю…
            • +1
              Я точно знаю, что в KitKat изменился порядок работы с СМС. И старый софт, несомненно, перестанет работать. Сейчас мое приложение не установится на 4.4, указал это в манифесте.
              Но в 4.4 это точно можно делать. Я, думаю, к концу следующей недели буду иметь рабочий код и дополню статью.
              • 0
                Не прибегая к хакам и костылям? Было бы очень интересно об этом почитать.
                • 0
                  А такие хаки существуют? Единственное решение это сделать приложение месседжером, но большинство пользователей с этим не согласятся.
                  Я выпустил свой смс фильтр где-то за месяц до выхода 4.4 и это был фейл, потому что пока я искал решение этой проблемы пользователи 4.4 успели наставить колов.
                  В итоге заблокировал установку на 4.4 и более новых, но сейчас это потеря 40% новых устройств и с каждым годом будет только увеличиваться (40% взято из статистики установок в день в гуглплей в категории инструменты).
                  Еще не уверен что происходит после обновления, судя по статистике активных устройств и оценкам оно просто перестает работать и его удаляют.
                  В общем единственный вариант развития который я вижу: объединиться с разработчиком месседжера, либо забить на приложение.
                  • 0
                    Да вот я тоже других способов (кроме как дефолтный мессенджер, что в моем случае вообще не вариант) не нашел. Поэтому фраза
                    Но в 4.4 это точно можно делать.
                    и заинтересовала. Ждём «конца следующей недели».
                    • 0
                      Я сижу с бутылкой виски и думаю о жизни и о KitKat, а ведь так хорошо все начиналось…
                      Изначально, я запретил приложению установку на 4.4, зная что там всё не так. И решил заняться KitKat после релиза для старых версий.
                      После этого релиза, я стартанул разработку под KitKat. Я радостно сделал свое приложение дефолтным СМС приложением. Спокойно реализовал все требования к манифесту и настройкам приложения. Красиво разрулил в манифесте и коде активность сервисов (чтобы один APK работал на обоих версиях), запросил у пользователя право стать дефолтным приложением. Принял первое смс обработчиком, и эээ… Сначала мне пришлось руками его класть во входящие, так как теперь Messaging (нативное приложение) не прослушивает входящие СМС. Потом думать, как сделать нотификацию, которая вела бы на смс в Messaging (я не собирался делать читалку смс). А потом… я додумался проверить, а может ли Messaging слать СМС, будучи не дефолтным… Нет! Как и Hangout и многие другие.
                      И да, как писал uxgen, выход один — полноценный СМС менеджер. Что я не потяну. А если и потяну — это будет УГ, по сравнению с конкурентными и нативным… Как можно так сфейлится… Я в январе-феврале проводил «аналитику», понял лишь, что в KitKat все еще можно ловить смс, но никак не выяснил подробности и не опробовал на эмуляторе… и начал долгими весенне/летне/осенними вечерами ваять… Epic fail…

                      Если интересно, могу добавить в статью «рецепты» того, что удалось сделать, хотя там ничего особого.
                      • 0
                        Welcome to Android hell! Только зачем так мучиться-то было? Это же всё описано здесь.

                        А совет разработчикам приложений для «SMS backup & restore» иначе как издевательским и назвать не могу:
                        1. Выясните, какое приложение сейчас является дефолтным
                        2. Попросите сделать себя дефолтным приложением
                        3. Обработайте СМС и попросите вернуть дефолтное приложение.

                        Удобнее просто некуда!

                        P.S. Но СМС можно отправлять и не будучи дефолтным — просто в этом случае СМС будет обязательно записано в SMS Provider. Или Вы имеете в виду, что это в конкретных приложениях не реализовано?
                        • 0
                          Читал я эту статью. Но я не нашел там информации, которая сказала бы мне, что мессенджеры не смогут отправлять смс. И лишь информация, что не дефолтные могут слать смс, как вы и говорите. И да, мессенджеры так не делают.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • 0
                        По запросу «смс фильтр» мое приложение 5е, а по «sms filter» где-то на 3-4 странице. Русскоязычных пользователей 70%, изначально приложение разрабатывалось под особенности русских спамеров, которые конкуренты не учитывали. Без русской локализации приложение бы затерялось в куче аналогичных.
                        Как вариант можно делать отдельно версию с русской локализацией, чтоб не влияли на общий рейтинг, но тогда продвигать будет тяжелее.
                        • НЛО прилетело и опубликовало эту надпись здесь
              • +8
                Я считаю, что для новых проектов лучше использовать не ActionBarScherlock, а AppCompat SupportLibrary v4 от Google.
                • +5
                  Почему бы не использовать для сборки maven или gradle? Кидать джарки в папочку lib это не очень хорошая привычка.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • +1
                      Без проблем, храните локальный мавен репозиторий в гите. Но я бы так делать не стал, не настолько еще параноик.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • +3
                      Warning:

                      1 — очень плохая практика. Не делайте так если хотите сделать приложение совместимое по UI со всеми версиями ос.
                      4 — не правда про фракгменты. Для долгих фоновых задач я бы рекомендовал RoboSpice
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • –1
                          Какие ужасные советы
                          • НЛО прилетело и опубликовало эту надпись здесь
                          • 0
                            А запись задачи в базу не гарантирует? Сколько вы еще столь стандартный и очевидный вариант решения проблемы будете игнорировать?
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • 0
                                Не все задачи требуют записи в базу. вы что, каждый чих будете поддерживать, чтобы код обязательно выполнился? Что если юзер запросил адрес по координатам, но при повороте к примеру приложение выгрузилось? Вы что, это будете сериализовывать? Да это же бред.

                                И это никакое не громоздкое решение, а вполне нормальное, которое поддерживается стандартными средствами, при этом имеет такие фичи, как ContentProvider, CursorLoader и прочее (что позволяет автообновлять списки автоматически и прочие плюшки).

                                И более того, я не встречал в последнее время выгрузку приложений при поворотах. Ну работают они во многих случаях правильно. Поэтому я спокойно какие-то задачи пишу через лодеры и прочие штуки. Слышали про правило 80/20?
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • 0
                                    в местах плохого приема, к примеру на границе зоны действия Wi-Fi или в подвальчиках. Тогда человек, ожидая загрузки, может переключиться на какое-то другое приложение, а вернувшись в ваше, получит глюк.

                                    Еще раз повторю — тогда пишем в базу задачу и пока не выполнится — не удаляем. И человек не получит глюк. Когда-нибудь вы поймете, что это тоже решение. Я что 100500 раз в вашем топике об этом писал, что тут. Толку ноль. Пожалуй прекращу копипастить свою мысль.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                      • 0
                                        1. Вы мне заплатите за потраченное время?
                                        2. Вы знаете что такое ContentProvider, CursorLoader и как они взаимодействуют с забой и автоматически обновляют записи?
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                          • 0
                                            Ну я просто не хочу писать то, что не хочу. А если мне говорят написать, то уж извольте платить.

                                            Зы ни какого отношения лоадеры не имеют к задачам, которые решаются в моей демке.

                                            Ну мне нечего вам сказать, если вы не знаете в чем преимущества таких вещей, о которых я написал и как они помогают работать с базой и какие плюшки несут. Ставлю минус вам за ваши знания.
                                            Могу лишь благословить вас на использование вашего велосипеда и пожелать успехов.
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                              • 0
                                                (промахнулся немного с ответом, ответил сам себе)
                                                Ну а зачем отмазка? Я предложил вам заплатить мне за работу и я её сделаю. Вы же меня заставляете её сделать бесплатно? С чего вдруг? Почему я ваши требования должен выполнять? Вы мне кто? Я указал на ваши промахи, а вы пыжитесь. Это сейчас разговор с этой оперы.

                                                Судя по вашим комментриям вы даже цикла приложения не знаете, «знаток».

                                                Не вижу смысла вам доказывать знания азов Android. Но если вас так интересуют мои знания, то можете посмотреть мою статью в конце концов, да сделать логическое умозаключение (не эмоциональное, а логическое), знаю ли я детские вопросы по Android. Или в конце концов посмотрите комментарии на сайте стартандроид, я там часто разжёвывал новичкам этот вопрос.

                                                Не понимаю вашего негодования. Вы говорите что ваше решение лишено недостатков. Но что если, допустим взять ваш пример, что если ваш бин содержит не простые вещи внутри себя как в примере, а явные вызовы методов наших классов? Что если наш телефон выключился, а после включения маркет нам обновил приложение и эти методы изменились? Мы однозначно получим ошибку выполнения. И даже если мы бин изменили, то изменилась структура класса и сериализация не пройдет успешно. Может ли быть такая ситуация? Вполне.
                                                Я же говорю про то, что лучше писать в базу сам факт запуска задачи, нежели сериализовывать саму задачу. Понимаете разницу? Есть какие таблицы, мы там добавляем флаг со статусом задачи, а дальше при запуске телефона просто смотрим, есть ли у нас задачи на выполнение. Это хорошо очень многими вещами. Мы лишены недостатков сериализации, мы можем безпроблемно импортировать базу и другие фишки, касающиеся базы.

                                                Я в принципе всегда рад новым решениям всяких проблем (да вять тот же паттерн ViewHolder) и воспринимаю это всегда с позитивом, но тут не вижу плюсов пока что
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                    • 0
                                                      Отмазка — потому что вы не сможете решить проблему, которую я описал, при помощи лоадеров, и вы это понимаете или смутно догадываетесь.

                                                      Причем тут лодеры?! Где я писал что они помогают решить проблемы? Они никак не помгают решить проблему, ибо они работают в ОЗУ. Прекратите нести ахинею. Я нигде не писал что лодеры помогают решить эту задачу.
                                                      Если до сих пор не поняли о чем я, повторю: ContentProvider И CursorLoader дают отличные вещи для работы с базой. Понимаете такое слово как база? База — это такая вещь, куда вы можете засунуть свою задачу и где она не потеряется, а лодеры и контентПровайдеры вам помогут удобно, удобно работать с базой.
                                                      И при обновлении никакого рассинхнона с сериализацией не будет,

                                                      О каком рассинхроне идет речь? Что вы тут понимаете под рассинхронизацией? Почитайте лучше про InvalidClassException и serialVersionUID

                                                      1. модификация бина не даст в дальнейшем дессериализоваться бину.
                                                      2. Модификация методов в бине (допустим мы переименовали методы нашего вызывающего класса)
                                                      В этих ситуациях бины перестанут работать и вылезет ошибка. Это очевидно как пить дать.

                                                      И хватит писать про указание кода, я не собираюсь на халяву вам писать код. Прекратите страдать фигнёй, вы меня просите написать решение для работы с базой через провайдеры, куда я буду писать задачи на выполнение. Если не умеете, то и мой код вам не поможет. Сначала деньги, а потом стулья.
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                        • 0
                                                          Вы, что, извините, дурак (ну так, мягко)?
                                                          Каким образом связана модификация класса и жизненный цикл активности? Это вообще к Java относится! Я уже и указал на всё, что можно. И тут нет никакой связи с жизненным циклом приложения.
                                                          Вы пишете про поворот экрана. Причем тут к черту поворот экрана, когда я пишу о другом?! Я пишу о ситуации, когда допустим приложение выключилось и остались сериализованными какие-то задачи. Тут маркет обновляет приложение и обновляет классы или сам бин приложения! И дессериализация не пройдет. И еще раз повторю, это никак не относится к жизненному Android циклу. Сериализация — это чистой природы Java. Учите Java!
                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                            • 0
                                                              Мда… Никакого отношения Java не имеет к Андроиду. Это Android использует гибрид языка Java и свой SDK, не более того. Даже виртуальная машина Dalvik не проходит по стандартам Оракла, за что Оракл судится с гуглом.
                                                              Для таких как вы, я написал небольшую заметку когда-то: А тот ли ты путь выбрал, разработчик?
                                                              Учите Java господа, а потом лезьте в Android.
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                • 0
                                                                  Что вы ушли в абстракции? Я много кода написал в помощь другим, как и статьи писал. Я указал на ошибки ваши, которые нерешаемы в сериализации, а вы в домик спрятались после того, как поняли что ваших тут знаний не хватает и теперь начали писать требование меня написать вам код. Мне вам противно в ответ уже сообщения писать из-за вашего общения, а вы мне про код. Научитесь писать нормально, приходите. А я удаляюсь окончательно. С вами кашу не сваришь.
                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                    • 0
                                                                      Мда. Человеку не надо было вам отвечать еще после совета «писать ActionBar самому и не использовать фрагменты». Не удивлюсь, если вы скажете: «Нечего пользоваться готовыми классами, хотите гибкости — наследуйтесь от Object и пишите все сами, это лучшее решение, лишенное костылей от Oracle/Google/etc».
                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                              • +2
                                Это из разряда «не пишите на C++, там утечки памяти». Фрагменты — великолепная задумка. А то, что программист неправильно жизненный цикл реализует, так это не проблема фрагментов.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                    • 0
                                      Спасибо, действительно интересно.
                              • 0
                                Соглашусь, что фрагменты не всегда работают одинаково на разных версиях Android.
                                И да, утечки в памяти тоже наблюдались.
                                Тем не менее, фрагменты — очень важный и неотъемлемый класс Android SDK. Совсем не тяжело заставить работать фрагменты как нужно, и подстроиться под особенности LifeCycle.
                                Custom View хоть и является альтернативой, но по-моему есть велосипед и игнорирование опыта многих хороших разработчиков.
                                • НЛО прилетело и опубликовало эту надпись здесь
                              • 0
                                Посмотрите на порт joda-time под Android, который, по словам автора, решает проблемы с захламлением памяти, возникающим при использовании оригинального joda-time (причины указаны в readme) и добавляет некоторые дополнительные плюшки.

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