Портирование приложений под старые версии 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)

Что в одном случае идет обращение к системному полю, а другое копируется на этапе компиляции/линковки.


Ответы (0 шт):