Android Nougat: TextureView не поддерживает отображение фонового рисунка

Я использую TextureView в своем приложении для Android, и он работает нормально. Совсем недавно я тестировал свой код на Android-устройстве с Android API 25 (7.1.2). Тот же код теперь не работает и выдает ошибку, java.lang.UnsupportedOperationException: TextureView doesn't support displaying a background drawable.

Я знаю, что void setBackgroundDrawable (Drawable background) долгое время устарел, и теперь он должен быть удален. Но я даже не устанавливаю его самостоятельно.

Я использую последние buildTools и SDK. Итак, Интересно, почему обновленная внутренняя реализация textureView не обновилась.

Вот соответствующая трассировка стека:

java.lang.UnsupportedOperationException: TextureView doesn't support displaying a background drawable
at android.view.TextureView.setBackgroundDrawable(TextureView.java:315)
at android.view.View.setBackground(View.java:18124)
at android.view.View.<init>(View.java:4573)
at android.view.View.<init>(View.java:4082)
at android.view.TextureView.<init>(TextureView.java:159)
at com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView.<init>(AutoFitTextureView.java:24)
at com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView.<init>(AutoFitTextureView.java:20)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[...]
at java.lang.Thread.run(Thread.java:745)

Вот как я использую свой (не все еще настроенный) пользовательский TextureView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.abdulwasaetariq.xyz.ui.activity.MainActivity">

    <com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="1080px"
        android:layout_height="1080px"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true" />

</RelativeLayout>

Вот мой соответствующий AutoFitTextureView.java: enter code here

public class AutoFitTextureView extends TextureView {

private int mRatioWidth = 0;
private int mRatioHeight = 0;

public AutoFitTextureView(Context context) {
    this(context, null);
}

public AutoFitTextureView(Context context, AttributeSet attrs) {
    this(context, attrs, 0); //(LINE#20)
}

public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle); //(LINE#24)
}

public void setAspectRatio(int width, int height) {
    if (width < 0 || height < 0) {
        throw new IllegalArgumentException("Size cannot be negative.");
    }
    mRatioWidth = width;
    mRatioHeight = height;
    requestLayout();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
    } else {
        if (width < height * mRatioWidth / mRatioHeight) {
            setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
        } else {
            setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
        }
    }
}}

Итак, как вы можете видеть, исключения происходят в методах super(), а это означает, что мой пользовательский TextureView не несет ответственности за это исключение. Это внутренний вызов.

Вот мой gradle config:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.2'
    defaultConfig {
        applicationId "com.abdulwasaetariq.xyz"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
        })
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha8'
    compile 'com.github.hotchemi:permissionsdispatcher:2.3.2'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.2'
}

Любые идеи, почему это может происходить? Любые заметки о выпуске Android API 25, о которых говорится в этом изменении?

Ответы

Ответ 1

Если вы посмотрите на источник текстурного представления для API 24, вы увидите следующее:

/**
 * Subclasses of TextureView cannot do their own rendering
 * with the {@link Canvas} object.
 *
 * @param canvas The Canvas to which the View is rendered.
 */
@Override
public final void draw(Canvas canvas) {
    // NOTE: Maintain this carefully (see View#draw)
    mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /* Simplify drawing to guarantee the layer is the only thing drawn - so e.g. no background,
    scrolling, or fading edges. This guarantees all drawing is in the layer, so drawing
    properties (alpha, layer paint) affect all of the content of a TextureView. */

    if (canvas.isHardwareAccelerated()) {
        DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;

        HardwareLayer layer = getHardwareLayer();
        if (layer != null) {
            applyUpdate();
            applyTransformMatrix();

            mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
            displayListCanvas.drawHardwareLayer(layer);
        }
    }
}

Комментарий в теле draw() дает обоснование изменения, которое вы видели. Это единственная документация, которую я нашел. Сравните это с TextureView с API 23:

/**
 * Subclasses of TextureView cannot do their own rendering
 * with the {@link Canvas} object.
 *
 * @param canvas The Canvas to which the View is rendered.
 */
@Override
public final void draw(Canvas canvas) {
    // NOTE: Maintain this carefully (see View.java)
    mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    applyUpdate();
    applyTransformMatrix();
}

API 24 также представил переопределения для методов "заданного фона", которые не переопределены в API 23. Настройка фона теперь явно обескуражена и просто не разрешена. Если вы видите исключение неподдерживаемой операции, и вы явно не устанавливаете фон, это, вероятно, пробирается сквозь ваши стили. Попробуйте установить android:background="@null" в свой XML, чтобы заставить фон быть нулевым, чтобы обойти эту ошибку. Вы также можете добавить следующий код к своему пользовательскому представлению, чтобы сохранить функциональность в тех версиях, которые поддерживают настройку фона:

@Override
public void setBackgroundDrawable(Drawable background) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && background != null) {
        setBackgroundDrawable(background);
    }
}

Непонятно, как заменить те функциональные возможности, которые вы потеряли для API 24+, или, если вам это вообще нужно, но просто хотите иметь фоновый инструмент в вашем арсенале.

Ответ 2

Ниже приведены фрагменты исходного кода для View для Android Nougat:

/**
 * Allow setForeground/setBackground to be called (and ignored) on a textureview,
 * without throwing
 */
static boolean sTextureViewIgnoresDrawableSetters = false;

В конструкторе с одним аргументом (называемом из всех остальных):

        // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
        // On N+, we throw, but that breaks compatibility with apps that use these methods.
        sTextureViewIgnoresDrawableSetters = targetSdkVersion <= M;

В конструкторе View, где выбрано ваше исключение:

...
        switch (attr) {
            case com.android.internal.R.styleable.View_background:
                background = a.getDrawable(attr);
                break;
...
    if (background != null) {
        setBackground(background);  // <--- this is the problematic line, apparently "background" is not null here
    }

Фактическое определение setBackground:

/**
 * Set the background to a given Drawable, or remove the background. If the
 * background has padding, this View padding is set to the background's
 * padding. However, when a background is removed, this View padding isn't
 * touched. If setting the padding is desired, please use
 * {@link #setPadding(int, int, int, int)}.
 *
 * @param background The Drawable to use as the background, or null to remove the
 *        background
 */
public void setBackground(Drawable background) {
    //noinspection deprecation
    setBackgroundDrawable(background);
}

Затем переопределение setBackgroundDrawable в TextureView:

@Override
public void setBackgroundDrawable(Drawable background) {
    if (background != null && !sTextureViewIgnoresDrawableSetters) {
        throw new UnsupportedOperationException(
                "TextureView doesn't support displaying a background drawable");
    }
}

Итак, что вы можете собрать вместе из всего, что есть: 1) У вас есть целевой SDK N (Нуга) - очевидно из вашего файла сборки; 2) Конструктор из View определяет ненулевой фон (я не могу объяснить эту часть в данный момент).

Это все, что требуется для того, чтобы это было реальной проблемой. Я не вижу, что вам удалось определить возможность рисования в вашем xml, поэтому переопределение setBackground или setBackgroundDrawable представляется наиболее разумной возможностью разрешить эту проблему для меня. Может возникнуть другое обходное решение (или, может быть, "предлагаемое использование" было бы лучшей терминологией), благодаря чему вы можете принудить переменную background в конструкторе оставаться пустой.

Ответ 3

Как раз упомянуть не только TextureView: я обнаружил, что GridLayout также не поддерживает отображение фонового рисунка с API 24.

Я пробовал:

A) gridLayout.setBackgroundResource(R.drawable.board_960x960px_border_in_bg);

В) Resources res = getResources(); Drawable drawable = res.getDrawable(R.drawable.board_960x960px_border_in_bg); gridLayout.setBackground(drawable);

Ни один из вышеперечисленных процессов не работает над API 23.

Однако фон TableLayout не исчезнет даже в API 24+, поэтому я переписал все мои соответствующие коды из GridLayout в TableLayout, и теперь все в порядке.