2011-07-22 10 views
21

Tworzę w mojej aplikacji kilka programowych View s. Wygląda na to, że wszystkie domyślnie mają to samo id=-1. Aby z nimi pracować, muszę wygenerować unikalny identyfikator.Programowe widoki Jak ustawić unikalne identyfikatory?

Próbowałem kilka podejść - generowania liczb losowych i na podstawie aktualnego czasu, ale i tak nie ma 100% gwarancji, że różne poglądy mają różne identyfikatory

prostu zastanawiasz się, czy jest jakiś bardziej niezawodny sposób na generowanie unikalnych te? Prawdopodobnie istnieje specjalna metoda/klasa?

+0

Myślę, że najlepszym sposobem jest zachować licznika int i przyrost podczas dodawania widoku i ustawić, że jak id – ingsaurabh

+3

Possible duplikat programu [Android: View.setID (int id) programowo - jak uniknąć konfliktów ID?] (http://stackoverflow.com/questions/1714297/android-view-setidint-id-programmatically-how-to-avoid- id-conflicts) –

Odpowiedz

32

tylko dodatkiem do odpowiedzią @phantomlimb,

podczas View.generateViewId() wymagają API Level> = 17,
to narzędzie jest compatibe ze wszystkimi API.

zgodnie z bieżącym poziomem interfejsu API,
decyduje o korzystaniu z systemu API za pomocą interfejsu API.

więc można użyć ViewIdGenerator.generateViewId() i View.generateViewId() w samym czasie i nie martwić się o ten sam identyfikator

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

W rzeczywistości jest to niepoprawne - używasz innego licznika AtomicInteger niż licznika View.generateViewId(), więc nie możesz używać tej klasy i View.generateViewId() zamiennie, jak twierdzisz w dokumentacja twojej klasy. Trzeba użyć tej klasy wyłącznie. – iainmcgin

+3

@iainmcgin, jeśli możesz używać 'ViewIdGenerator.generateViewId()' i 'View.generateViewId()' zamiennie, co oznacza Build.VERSION.SDK_INT> = 17, więc kod 'if (Build.VERSION.SDK_INT <17) { ...} 'nigdy się nie uruchomi. właściwie zawsze używa 'View.generateViewId()' w tym przypadku. :) – fantouch

+0

Ha, fajnie jest zobaczyć kilka chińskich komentarzy w kodzie :) –

13

Utwórz pojedynczą klasę, która ma całkowitą liczbę atomową. Podbij liczbę całkowitą i zwróć wartość, gdy potrzebujesz identyfikatora widoku.

Identyfikator będzie unikalny podczas wykonywania procesu, ale zostanie zresetowany po ponownym uruchomieniu procesu.

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; 
    } 
} 

Należy zauważyć, że identyfikator może nie być unikalny, jeśli istnieją już widoki, które mają identyfikatory w widoku "wykres". Można spróbować uruchomić z numeru, który jest Integer.MAX_VALUE i zmniejszenie jej zamiast iść od 1 -> MAX_VALUE

+1

Krótko mówiąc, od sprawdzenia każdego identyfikatora generowanego w pliku R.java, nie da się stwierdzić, czy ID jest unikatowy, czy nie (z tego co mogę powiedzieć). Zdarzają się przypadki, w których Android będzie nadawał się do mordu ze względu na konflikty ID. Najlepszym sposobem, jaki znalazłem, było użycie zmiennej Integer.MAX_VALUE i zmniejszenie wartości za każdym razem, gdy potrzebujesz nowego identyfikatora, jak wspomniałeś. – infl3x

+6

to jest sposób .. ale jak wspomniano Xiaochao Yang, z API 17 możesz użyć metody View.generateViewId() – lory105

+0

Czy możesz mi odpowiedzieć.To jest oparte na twojej odpowiedzi ... http: //stackoverflow.com/questions/32381678/how-to-reset-atomicinteger-unique-id-in-android – reegan29

47

tylko dodać do odpowiedzi Kaj, od poziomu API 17, można nazwać

View.generateViewId()

Niż użyć setId (int).

W przypadku, gdy jest to potrzebne dla celów niższych niż poziom 17, tutaj jest jego wewnętrzna implementacja w View.java można użyć bezpośrednio w projekcie:

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; 
     } 
    } 
} 

numer ID większa niż 0x00FFFFFF jest zarezerwowana dla poglądów statycznych zdefiniowane w plikach/res xml. (Najprawdopodobniej 0x7f * * z R.java w moich projektach.)

z kodu, jakoś Android nie chce korzystać z 0 jako id rzutem, a to musi być przerzucony przed 0x01000000 do unikaj konfrontacji ze statycznymi identyfikatorami zasobów.

+0

Nie ma potrzeby używania 0 jako identyfikatora widoku, ponieważ jest to uniwersalne, aby reprezentować niepoprawny identyfikator zasobu podczas wysyłania zapytania o zasób w środowisku wykonawczym. – Jared

+0

Jak zresetować 'generateViewId' .. – reegan29

2

Jeśli chodzi o rozwiązanie awaryjne dla API < 17, widzę, że proponowane rozwiązania zacząć generować identyfikatory zaczynające od 0 lub 1. Klasa View ma inną instancję generatora, a także zaczyna odliczanie od numeru pierwszego, co spowoduje, że zarówno twój, jak i generator View'a wygeneruje te same identyfikatory, a w efekcie uzyskasz różne Widoki o tych samych identyfikatorach w widoku hierarchia.Niestety nie jest to dobre rozwiązanie dla tego, ale jest to hack, który powinien być dobrze udokumentowane:

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; 
     } 
    } 
} 

} 
+0

Dobra uwaga na temat możliwego konfliktu między niestandardowym generatorem aplikacji a wewnętrznym generatorem w klasie Widok. Czy sprawdziłeś rzeczywistą logikę View.generateViewId() dla API 16? Nie znalazłem kopii źródła, więc mam nadzieję, że jest w zasadzie takie samo jak nowsze wersje API. – jk7

+0

Nie znalazłem kodu źródłowego. Ale doświadczyłem konfliktów podczas używania View.generateViewId() nawet w nowych API, więc zmodyfikowałem kod tak, aby zawsze używać mojej własnej metody generateUniqueViewId. – Singed

Powiązane problemy