Programmatic Views Как установить уникальный идентификатор?

Я создаю в своем приложении набор программных View s. Как оказалось, все они по умолчанию имеют одинаковый id=-1. Для работы с ними мне нужно создать уникальный идентификатор.

Я пробовал несколько подходов - генерация случайных чисел и на основе текущего времени, но в любом случае нет 100% гарантии, что разные представления будут иметь разные id

Просто интересно, есть ли более надежный способ создания уникальных? Возможно, есть специальный метод/класс?

Ответ 1

Просто добавление к ответу @phantomlimb,

while View.generateViewId() требуется API Level >= 17,
этот инструмент совместим со всеми API.

в соответствии с текущим уровнем API,
он решает погоду с использованием API системы или нет.

чтобы вы могли использовать ViewIdGenerator.generateViewId() и View.generateViewId() в в то же время и не беспокойтесь о получении того же идентификатора

import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.SuppressLint;
import android.os.Build;
import android.view.View;

/**
 * {@link View#generateViewId()}要求API Level >= 17,而本工具类可兼容所有API Level
 * <p>
 * 自动判断当前API Level,并优先调用{@link View#generateViewId()},即使本工具类与{@link View#generateViewId()}
 * 混用,也能保证生成的Id唯一
 * <p>
 * =============
 * <p>
 * while {@link View#generateViewId()} require API Level >= 17, this tool is compatibe with all API.
 * <p>
 * according to current API Level, it decide weather using system API or not.<br>
 * so you can use {@link ViewIdGenerator#generateViewId()} and {@link View#generateViewId()} in the
 * same time and don't worry about getting same id
 * 
 * @author [email protected]
 */
public class ViewIdGenerator {
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

    @SuppressLint("NewApi")
    public static int generateViewId() {

        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1; // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }

    }
}

Ответ 2

Просто хочу добавить ответ Kaj, начиная с уровня API 17, вы можете вызвать

View.generateViewId()

Вместо использования setId (int).

Если вам нужно это для целей ниже уровня 17, вот его внутренняя реализация в View.java, которую вы можете использовать непосредственно в своем проекте:

private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

/**
 * Generate a value suitable for use in {@link #setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    for (;;) {
        final int result = sNextGeneratedId.get();
        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
        int newValue = result + 1;
        if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
        if (sNextGeneratedId.compareAndSet(result, newValue)) {
            return result;
        }
    }
}

Идентификационный номер, превышающий 0x00FFFFFF, зарезервирован для статических представлений, определенных в файлах /res xml. (Скорее всего, 0x7f * * из R.java в моих проектах.)

Из кода каким-то образом Android не хочет, чтобы вы использовали 0 в качестве идентификатора вида, и его нужно перевернуть до 0x01000000, чтобы избежать конфликтов со статическими идентификаторами ресурсов.

Ответ 3

Создайте одноэлементный класс с атомарным Integer. Удалите целое число и верните значение, когда вам нужен идентификатор вида.

Идентификатор будет уникальным во время выполнения вашего процесса, но wil reset при перезапуске процесса.

public class ViewId {

    private static ViewId INSTANCE = new ViewId();

    private AtomicInteger seq;

    private ViewId() {
        seq = new AtomicInteger(0);
    }

    public int getUniqueId() {
        return seq.incrementAndGet();
    }

    public static ViewId getInstance() {
        return INSTANCE;
    }
}

Обратите внимание, что идентификатор может быть не уникальным, если уже есть представления, которые имеют идентификаторы в представлении "graph". Вы можете попытаться начать с числа Integer.MAX_VALUE и уменьшить его вместо перехода от 1 → MAX_VALUE

Ответ 4

Что касается резервного решения для API < 17, я вижу, что предлагаемые решения начинают генерировать идентификаторы, начиная с 0 или 1. Класс View имеет еще один экземпляр генератора, а также начинает отсчет с первого номера, что приведет к Генератор представлений, генерирующих те же идентификаторы, и в конечном итоге у вас будут разные представления с одинаковыми идентификаторами в иерархии вида. К сожалению, для этого нет хорошего решения, но это хак, который должен быть хорошо документирован:

public class AndroidUtils {

/**
 *  Unique view id generator, like the one used in {@link View} class for view id generation.
 *  Since we can't access the generator within the {@link View} class before API 17, we create
 *  the same generator here. This creates a problem of two generator instances not knowing about
 *  each other, and we need to take care that one does not generate the id already generated by other one.
 *
 *  We know that all integers higher than 16 777 215 are reserved for aapt-generated identifiers
 *  (source: {@link View#generateViewId()}, so we make sure to never generate a value that big.
 *  We also know that generator within the {@link View} class starts at 1.
 *  We set our generator to start counting at 15 000 000. This gives us enough space
 *  (15 000 000 - 16 777 215), while making sure that generated IDs are unique, unless View generates
 *  more than 15M IDs, which should never happen.
 */
private static final AtomicInteger viewIdGenerator = new AtomicInteger(15000000);

/**
 * Generate a value suitable for use in {@link View#setId(int)}.
 * This value will not collide with ID values generated at build time by aapt for R.id.
 *
 * @return a generated ID value
 */
public static int generateViewId() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return generateUniqueViewId();
    } else {
        return View.generateViewId();
    }
}

private static int generateUniqueViewId() {
    while (true) {
        final int result = viewIdGenerator.get();
        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
        int newValue = result + 1;
        if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
        if (viewIdGenerator.compareAndSet(result, newValue)) {
            return result;
        }
    }
}

}