Портирование приложений под старые версии Android (minSdkVersion=3)
Пишу приложение под Андроид, целевая targetSdkVersion=29 (Android 10), все достаточно просто и приятно. Однако меня просят понизить минимальную версию до Android 4, а то и вообще до Android 2. Проект некоммерческий, публикация в маркетах не предполагается, так что на требования гуглоплеев можно не смотреть.
Хотелось бы почитать какие-то уроки или практики о таком бекпортировании, но начав поиск, нашел крайне мало информации, сталкиваюсь в лучшем случае с тем, что советуют использовать родную Саппорт-либу. Саму же Саппорт-либу я не использую в силу определенных причин (и не собираюсь использовать).
Пока думаю компилировать версию под последний СДК, а потом через патчинг байт-кода удалять неподдерживаемые вызовы на младших устройствах. Но, к примеру, Notification.builder.setChannelId(), которого нет на старых устройствах, можно безболезненно удалить, приложение и пользователь даже не заметят разницы, а вот если удалить Notification.builder.setActions(), то пропадут кнопочки в уведомлениях, чего бы хотелось избежать. И прежде чем изобретать велосипед, хотелось бы ознакомиться с опытом других разработчиков, ведь наверняка кто-то решал подобные задачи.
Update
Поясню, почему я не использую всеми любимые конструкции вида:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {...}
Допустим, у меня есть активити, примерно такое:
package com.example.degradation;
import android.app.Activity;
import android.app.NotificationChannel;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationAttributes;
import android.widget.TextView;
public class Home extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
String message="Device: "+Build.DEVICE+"\n"+
// "SDK_INT:" + Build.VERSION.SDK_INT +"\n"+ -- пришлось закомментировать, так как только с апи4, но это не важно
"SDK:" + Build.VERSION.SDK +"\n";
if(Build.VERSION.SDK.equals("some magic version?")){ // никогда не выполнится
NotificationChannel aa=new NotificationChannel("aa","aa",0);
message+="Name: "+aa.getName()+"\n";
message+="Vibration attributes: "+VibrationAttributes.USAGE_RINGTONE+"\n";
}
TextView t=new TextView(this);
t.setText(message);
setContentView(t);
}
}
Как видим, код под условием, по идее, никогда не должен быть выполнен. Однако, так выглядит это на эмуляторе 1.5:
И его лог:
Забавно, но если скомпилировать приложение с обращением к полю VibrationAttributes.USAGE_RINGTONE, которое есть только в Android 11, то значение 33 покажут и те устройства, где его нету (эмулятор 1.5 и реальное устройство на 10).
Возможно, это как-то связано с используемым мной тулчейном/компилятором (эклипс), что значения полей берутся из линкуемого android.jar, на стадии сборки, а не в рантайме:
000458: |[000458] com.example.degradation.Home.onCreate:(Landroid/os/Bundle;)V
000468: 6f20 0100 5400 |0000: invoke-super {v4, v5}, Landroid/app/Activity;.onCreate:(Landroid/os/Bundle;)V // method@0001
00046e: 2202 1500 |0003: new-instance v2, Ljava/lang/StringBuilder; // type@0015
000472: 1a03 0700 |0005: const-string v3, "Device: " // string@0007
000476: 7020 1000 3200 |0007: invoke-direct {v2, v3}, Ljava/lang/StringBuilder;.<init>:(Ljava/lang/String;)V // method@0010
00047c: 6203 0100 |000a: sget-object v3, Landroid/os/Build;.DEVICE:Ljava/lang/String; // field@0001
000480: 6e20 1100 3200 |000c: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
000486: 0c02 |000f: move-result-object v2
000488: 1a03 0000 |0010: const-string v3, "
" // string@0000
00048c: 6e20 1100 3200 |0012: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
000492: 0c02 |0015: move-result-object v2
000494: 1a03 2300 |0016: const-string v3, "SDK:" // string@0023
000498: 6e20 1100 3200 |0018: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
00049e: 0c02 |001b: move-result-object v2
0004a0: 6203 0000 |001c: sget-object v3, Landroid/os/Build$VERSION;.SDK:Ljava/lang/String; // field@0000
0004a4: 6e20 1100 3200 |001e: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
0004aa: 0c02 |0021: move-result-object v2
0004ac: 1a03 0000 |0022: const-string v3, "
" // string@0000
0004b0: 6e20 1100 3200 |0024: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
0004b6: 0c02 |0027: move-result-object v2
0004b8: 6e10 1200 0200 |0028: invoke-virtual {v2}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0012
0004be: 0c00 |002b: move-result-object v0
0004c0: 2202 1500 |002c: new-instance v2, Ljava/lang/StringBuilder; // type@0015
0004c4: 7110 0f00 0000 |002e: invoke-static {v0}, Ljava/lang/String;.valueOf:(Ljava/lang/Object;)Ljava/lang/String; // method@000f
0004ca: 0c03 |0031: move-result-object v3
0004cc: 7020 1000 3200 |0032: invoke-direct {v2, v3}, Ljava/lang/StringBuilder;.<init>:(Ljava/lang/String;)V // method@0010
0004d2: 1a03 2600 |0035: const-string v3, "Vibration attributes: 33
" // string@0026
0004d6: 6e20 1100 3200 |0037: invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // method@0011
0004dc: 0c02 |003a: move-result-object v2
0004de: 6e10 1200 0200 |003b: invoke-virtual {v2}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0012
0004e4: 0c00 |003e: move-result-object v0
0004e6: 6202 0000 |003f: sget-object v2, Landroid/os/Build$VERSION;.SDK:Ljava/lang/String; // field@0000
0004ea: 1a03 3600 |0041: const-string v3, "some magic version?" // string@0036
0004ee: 6e20 0e00 3200 |0043: invoke-virtual {v2, v3}, Ljava/lang/String;.equals:(Ljava/lang/Object;)Z // method@000e
0004f4: 2201 0700 |0046: new-instance v1, Landroid/widget/TextView; // type@0007
0004f8: 7020 0200 4100 |0048: invoke-direct {v1, v4}, Landroid/widget/TextView;.<init>:(Landroid/content/Context;)V // method@0002
0004fe: 6e20 0300 0100 |004b: invoke-virtual {v1, v0}, Landroid/widget/TextView;.setText:(Ljava/lang/CharSequence;)V // method@0003
000504: 6e20 0700 1400 |004e: invoke-virtual {v4, v1}, Lcom/example/degradation/Home;.setContentView:(Landroid/view/View;)V // method@0007
00050a: 0e00 |0051: return-void
И я не очень понимаю, чем отличаются между собой следующие поля:
#7 : (in Landroid/os/Build$VERSION;)
name : 'SDK_INT'
type : 'I'
access : 0x0019 (PUBLIC STATIC FINAL)
#11 : (in Landroid/os/VibrationAttributes;)
name : 'USAGE_RINGTONE'
type : 'I'
access : 0x0019 (PUBLIC STATIC FINAL)
Что в одном случае идет обращение к системному полю, а другое копируется на этапе компиляции/линковки.

