I am creating in my app bunch of programmatic Views. As it appeared to be they all by default have the same id=-1. In order to work with them I need to generate unique id's.
I have tried several approaches - random number generation and based on current time, but anyway there's no 100% guarantee that different Views will have different id's
Just wondering is there any more reliable way to generate unique ones? Probably there's special method/class?
Just want to add to Kaj's answer, from API level 17, you can call
View.generateViewId()
then use the View.setId(int) method.
In case you need it for targets lower than level 17, here is its internal implementation in View.java you can use directly in your project:
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;
}
}
}
ID number larger than 0x00FFFFFF is reserved for static views defined in the /res xml files. (Most likely 0x7f****** from the R.java in my projects.)
From the code, somehow Android doesn't want you to use 0 as a view's id, and it needs to be flipped before 0x01000000 to avoid the conflits with static resource IDs.
Just an addition to the answer of #phantomlimb,
while View.generateViewId() require API Level >= 17,
this tool is compatibe with all API.
according to current API Level,
it decide weather using system API or not.
so you can use ViewIdGenerator.generateViewId() and View.generateViewId() in the
same time and don't worry about getting same id
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 fantouchx#gmail.com
*/
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();
}
}
}
Since support library 27.1.0 there's generateViewId() in ViewCompat
ViewCompat.generateViewId()
Create a singleton class, that has an atomic Integer. Bump the integer, and return the value when you need a view id.
The id will be unique during the execution of your process, but wil reset when your process is restarted.
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;
}
}
Note that the id might not be unique, if there already are views that have ids in the view 'graph'. You could try to start with a number that is Integer.MAX_VALUE, and decrease it instead of going from 1 -> MAX_VALUE
Regarding the fallback solution for API<17, I see that suggested solutions start generating IDs starting from 0 or 1. The View class has another instance of generator, and also starts counting from number one, which will result in both your and View's generator generating the same IDs, and you will end up having different Views with same IDs in your View hierarchy. Unfortunately there is no a good solution for this but it's a hack that should be well documented:
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;
}
}
}
}
Related
I would like to know the following things, Kindly help me to understand why we use these components in ExoPlayer?
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false))
.setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy())
.setLivePresentationDelayMs(LIVE_PRESENTATION_DELAY)
.setDrmSessionManager(drmSessionManager)
.setManifestParser(new TvGoDashManifestParser())
.createMediaSource(mediaItem);
LIVE_PRESENTATION_DELAY = 10000
Here, what is the use of Live presentation delay in the above code if we reduce its value what would be the potential impact.
Also, we are setting the following buffering duration for load control. If we do changes on these items what would happen.
defaultLoadControl = new DefaultLoadControl.Builder()
.setAllocator(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE))
.setBufferDurationsMs(DEFAULT_MIN_BUFFER_MS,
DEFAULT_MAX_BUFFER_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS).build();
DEFAULT_MIN_BUFFER_MS = 15000
DEFAULT_MAX_BUFFER_MS = 50000
DEFAULT_BUFFER_FOR_PLAYBACK_MS = 1500
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 3000
And if we didn't define load control for the ExoPlayer, then how does it handle these parameters by default?
All the parameters with their default values are documented in exoplayer's source code, and you can find them by searching in the github repo.
For the DefaultLoadControl parameters you can take a look at this line. the meaning of all 4 parameters with their default value is documented in the next lines in the provided link (copied here):
/**
* The default minimum duration of media that the player will attempt to ensure is buffered at all
* times, in milliseconds.
*/
public static final int DEFAULT_MIN_BUFFER_MS = 50_000;
/**
* The default maximum duration of media that the player will attempt to buffer, in milliseconds.
*/
public static final int DEFAULT_MAX_BUFFER_MS = 50_000;
/**
* The default duration of media that must be buffered for playback to start or resume following a
* user action such as a seek, in milliseconds.
*/
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;
/**
* The default duration of media that must be buffered for playback to resume after a rebuffer, in
* milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action.
*/
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000;
For the presentation delay refer to this link and the following lines in the file. The following snippet is copied from provided link to keep the answer complete:
/**
* The default target {#link Player#getCurrentLiveOffset() offset for live streams} that is used
* if no value is defined in the {#link MediaItem} or the manifest.
*/
public static final long DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS = 30_000;
/**
* #deprecated Use {#link #DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS} instead.
*/
#Deprecated public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30_000;
Keep in mind that depending on the version of the exoplayer library, default values for parameters are different, and parameters reported here are for the latest version.
I use SignalStength to get the quality of the signal, before sending sms:
signalStrength.getLevel()
I then wanted to compare the integer with a static constant in SignalStength:
if (signalStrengthLevel == SignalStrength.SIGNAL_STRENGTH_POOR) {
//...
}
But it doesn't compile in Android Studio. I realized that for some reason, these constants are marked as hidden in the source code:
/** #hide */
public static final int SIGNAL_STRENGTH_POOR
= TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // = 1
Which forces me to copy/paste these constants in one of my own classes...
I then wonder if anyone knows the reason why the developers decided to mark these constants as hidden?
In the documentation of the TelephonyManager, we can find getSignalStrength() method, which returns SignalStrength type, which has getLevel() method. In the documentation of getLevel() method, we can read the following information about returned integer value:
a single integer from 0 to 4 representing the general signal quality.
This may take into account many different radio technology inputs. 0
represents very poor signal strength while 4 represents a very strong
signal strength.
Taking that into consideration, I'd solve that in the following way:
create static values like:
private final static int VERY_POOR_SIGNAL = 0;
private final static int POOR_SIGNAL = 1;
private final static int MEDIUM_SIGNAL = 2;
private final static int STRONG_SIGNAL = 3;
private final static int VERY_STRONG_SIGNAL = 4;
and use this values to compare them with the integer value returned by:
telephonyManager.getSignalStrength().getLevel()
The word /** #hide */ just informs that the API is not accessible from SDK. I think it might be for security reasons.
Check this post
After logging in, it's generating a hash value, but still giving error "Some problem occurred! try again".
PayUmoneySdkInitilizer.PaymentParam.Builder builder =
new PayUmoneySdkInitilizer.PaymentParam.Builder();
builder.setAmount(10.0)
.setTnxId("0nf7" + System.currentTimeMillis())
.setPhone(<My phone>)
.setProductName("product_name")
.setFirstName(<My Name>)
.setEmail(<My email>)
.setsUrl("https://www.payumoney.com/mobileapp/payumoney/success.php")
.setfUrl("https://www.payumoney.com/mobileapp/payumoney/failure.php")
.setUdf1("").setUdf2("").setUdf3("").setUdf4("").setUdf5("")
.setIsDebug(false)
.setKey(<mykey>)
.setMerchantId(<my debug merchant id>);
String tnxId="0nf7" + System.currentTimeMillis();
PayUmoneySdkInitilizer.PaymentParam paymentParam = builder.build();
String hashSequence = "<...>|"+tnxId+"|10.0|product_name|<My name>|<My email>|||||||||||salt";
String serverCalculatedHash= hashCal("SHA-512", hashSequence);
Toast.makeText(getApplicationContext(),
serverCalculatedHash, Toast.LENGTH_SHORT).show();
paymentParam.setMerchantHash(serverCalculatedHash);
// calculateServerSideHashAndInitiatePayment(paymentParam);
PayUmoneySdkInitilizer.startPaymentActivityForResult(TrayActivity.this, paymentParam);
public static String hashCal(String type, String str) {
byte[] hashseq = str.getBytes();
StringBuffer hexString = new StringBuffer();
try {
MessageDigest algorithm = MessageDigest.getInstance(type);
algorithm.reset();
algorithm.update(hashseq);
byte messageDigest[] = algorithm.digest();
for (int i = 0; i<messageDigest.length; i++) {
String hex = Integer.toHexString(0xFF &messageDigest[i]);
if (hex.length() == 1) { hexString.append("0"); }
hexString.append(hex);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} return hexString.toString();
}
You use in the code:
.setTnxId("0nf7" + System.currentTimeMillis())
And then later:
String tnxId="0nf7" + System.currentTimeMillis();
Probably not the only problem, but do you really want to use two different values for these (the time may change between the two calls)? Didn't you want the same tnxId in both cases?
TransactionIdProvider.java:
import java.util.Locale;
public class TransactionIdProvider {
private final static String DEFAULT_PREFIX = "ID";
// Convenient prime number for incrementing the counter
private final static long ID_ADD = 0xF0AD; // "f*ck off and die"
// 64b counter with non-trivial start value
private static long idCounter = 0x0101F00DDEADBEEFL;
/**
* Returns ID consisting of prefix string and 64b counter interleaved
* with 32b per-4s-timestamp.
*
* May produce identical ID (collision) when:
* 1) class is reloaded within 4s
* (to fix: serialize "idCounter" upon shutdown/restart of VM, or
* modify prefix per start of VM)
* 2) more than 2^64 IDs are requested within 4s (no fix, unexpected)
* 3) more than 2^64 IDs are requested after cca. 550 years.
* (no fix, unexpected)
* 4) more than one static instance of TransactionIdProvider is used
* (two or more VMs running the app) (to fix put different prefix in
* every VM/server running this)
*
* Length of returned ID is prefix.length() + 24 alphanumeric symbols.
*/
public static synchronized String getNewId(final String prefix) {
idCounter += ID_ADD; // increment counter
// get 32b timestamp per ~4s (millis/4096) (good for ~550 years)
final int timeStamp = (int)(System.currentTimeMillis()>>12);
final int idPart1 = (int)(idCounter>>32);
final int idPart2 = (int)(idCounter);
return String.format(Locale.US, "%s%08X%08X%08X",
prefix, idPart1, timeStamp, idPart2);
}
public static String getNewId() {
return getNewId(DEFAULT_PREFIX);
}
}
Not sure how much usable is this one, and if the ID may be so long. Feel free to use/modify it any way you wish.
Also I wonder, whether I didn't forget about something important, but can't recall anything.
The security aspect of this one is still quite weak, as within 4s time span the ID will be like simple addition, but at least it's not producing 1, 2, 3... series.
Did found some SDK docs, looks like txnId may be 25 chars long, so you have 1 char for prefix only. Or cut down on timestamp, using %07X in format and masking value with 0x0FFFFFFF, that would make it repeat every ~34 years -> 2 letters for prefix. Or change counter to 32b int, should be still more than enough, unless you expect thousands of transactions per second -> that would remove 8 chars. Or base32/base64 the whole ID to shorten it (depends what alphabet is legal for content)...
Or whatever... already spent enough time with this. Hire a pro.
I am in need of assistance. I will be computing a measured variable, then taking the top 100 values of these and averaging them. Please remember, I have been teaching myself only for the past 6 weeks and what is obvious to some, will not necessarily be obvious to me.
In essence, say 'double x' is the variable, that I have many close values for. What I need is a way to compute the sum (then average) of the top 100 of these values.
In my research, the closest thing I can see that would suit what I need is 'nextAfter(double start, double direction); and before this, using 'max' to determine the maximum value, would this be the correct starting point:
double xm = max(x);
static double (xm, x < xm);
My question is how to get the sum of the top 100 values (the maximum and 99 nextAfter's) - averaging would be easy - just dividing by 100.
To compute the average of the largest n values you read from the source, you need to store at least these values. Since at any given point before the end you don't know whether some of the largest n values overall will come later, you need to keep track the largest n values seen so far.
A simple way to do that is to store the largest values in a heap or priority queue, since that allows easy adding of new values and finding (and removing) of the smallest of the stored values. The default PriorityQueue is well-suited for this task, since it uses the natural ordering of the elements, and thus polling removes the smallest of the stored elements. If one wanted to compute the average of the n smallest elements, one would need to use a PriorityQueue with a custom Comparator (or in this special case, simply negating all values and using the natural ordering would work too).
The lazy way (less code) to achieve the desired is to simply add each incoming value to the queue, and if the queue's size exceeds n [then it must be n+1] remove the smallest element from the queue:
// vp is the value provider
while(vp.hasNext()) {
// read the next value and add it to the queue
pq.add(vp.nextValue());
if (pq.size() > topSize) {
pq.poll();
}
A slightly more involved way is to first check whether the new value needs to be added, and only modify the queue when that is the case,
double newValue = vp.nextValue();
// Check if we have to put the new value in the queue
// that is the case when the queue is not yet full, or the smallest
// stored value is smaller than the new
if (pq.size() < topSize || pq.peek() < newValue) {
// remove the smallest value from the queue only if it is full
if (pq.size() == topSize()) {
pq.poll();
}
pq.add(newValue);
}
This way is potentially more efficient, since adding a value to the queue and removing the smallest are both O(log size) operations, while comparing to the smallest stored value is O(1). So if there are many values smaller than the n largest seen before, the second way saves some work.
If performance is critical, be aware that a PriorityQueue cannot store primitive types like double, so the storing (and retrieving for the average computation) involves boxing (wrapping a double value in a Double object) resp. unboxing (pulling the double value from a Double object), and consequently an indirection from the underlying array of the queue to the actual values. Those costs could be avoided by implementing a heap-based priority queue using a raw double[] yourself. (But that should rarely be necessary, usually, the cost of the boxing and indirections would constitute only a minute part of the overall processing.)
A simple-minded complete working example:
import java.util.PriorityQueue;
/**
* Example class to collect the largest values from a stream and compute their
* average.
*/
public class Average {
// number of values we want to save
private int topSize;
// number of values read so far
private long count = 0;
// priority queue to save the largest topSize values
private PriorityQueue<Double> pq;
// source of read values, could be a file reader, a device reader, or whatever
private ValueProvider vp;
/**
* Construct an <code>Average</code> to sample the largest <code>n</code>
* values from the source.
*
* #param tops Number of values to save for averaging.
* #param v Source of the values to sample.
*
* #throws IllegalArgumentException when the specified number of values is less than one.
*/
public Average(int tops, ValueProvider v) throws IllegalArgumentException {
if (tops < 1) {
throw new IllegalArgumentException("Can't get average of fewer than one values.");
}
topSize = tops;
vp = v;
// Initialise queue to needed capacity; topSize + 1, since we first add
// and then poll. Thus no resizing should ever be necessary.
pq = new PriorityQueue<Double>(topSize+1);
}
/**
* Compute the average of the values stored in the <code>PriorityQueue<Double></code>
*
* #param prio The queue to average.
* #return the average of the values stored in the queue.
*/
public static double average(PriorityQueue<Double> prio) throws IllegalArgumentException {
if (prio == null || prio.size() == 0) {
throw new IllegalArgumentException("Priority queue argument is null or empty.");
}
double sum = 0;
for(Double d : prio) {
sum += d;
}
return sum/prio.size();
}
/**
* Reads values from the provider until exhausted, reporting the average
* of the largest <code>topSize</code> values read so far from time to time
* and when the source is exhausted.
*/
public void collectAverage() {
while(vp.hasNext()) {
// read the next value and add it to the queue
pq.add(vp.nextValue());
++count;
// If the queue was already full, we now have
// topSize + 1 values in it, so we remove the smallest.
// That is, conveniently, what the default PriorityQueue<Double>
// gives us. If we wanted for example the smallest, we'd need
// to use a PriorityQueue with a custom Comparator (or negate
// the values).
if (pq.size() > topSize) {
pq.poll();
}
// Occasionally report the running average of the largest topSize
// values read so far. This may not be desired.
if (count % (topSize*25) == 0 || count < 11) {
System.out.printf("Average of top %d values after collecting %d is %f\n",
pq.size(), count, average(pq));
}
}
// Report final average. Returning the average would be a natural choice too.
System.out.printf("Average of top %d values of %d total is %f\n",
pq.size(), count, average(pq));
}
public static void main(String[] args) {
Average a = new Average(100, new SimpleProvider(123456));
a.collectAverage();
}
}
using the interface
/**
* Interface for a source of <code>double</code>s.
*/
public interface ValueProvider {
/**
* Gets the next value from the source.
*
* #return The next value if there is one.
* #throws RuntimeException if the source is exhausted.
*/
public double nextValue() throws RuntimeException;
/**
* Checks whether the source has more values to deliver.
*
* #return whether there is at least one more value to be obtained from the source.
*/
public boolean hasNext();
}
and implementing class
/**
* Simple provider of a stream of <code>double</code>s.
*/
public class SimpleProvider implements ValueProvider {
// State determining which value to return next.
private long state = 0;
// Last allowed state.
private final long end;
/**
* Construct a provider of <code>e</code> values.
*
* #param e the number of values to yield.
*/
public SimpleProvider(long e) {
end = e > 0 ? e : 0;
}
/**
* Default constructor to provide 10000 values.
*/
public SimpleProvider() {
this(10000);
}
public double nextValue() {
++state;
return Math.log(state)*Math.sin(state) + Math.cos(state/2.0);
}
public boolean hasNext() {
return state < end;
}
}
I'm adding TextViews programmatically in a for-loop and add them to an ArrayList.
How do I use TextView.setId(int id)? What Integer ID do I come up with so it doesn't conflict with other IDs?
From API level 17 and above, you can call: View.generateViewId()
Then use View.setId(int).
If your app is targeted lower than API level 17, use ViewCompat.generateViewId()
You can define the ID's you'll use later in R.id class using an xml resource file, and let Android SDK set the actual unique values during compile time.
res/values/ids.xml
<item name="my_edit_text_1" type="id"/>
<item name="my_button_1" type="id"/>
<item name="my_time_picker_1" type="id"/>
To use it in the code:
myEditTextView.setId(R.id.my_edit_text_1);
According to View documentation
The identifier does not have to be unique in this view's hierarchy. The identifier should be a positive number.
So you can use any positive integer you like, but in this case there can be some views with equivalent id's. If you want to search for some view in hierarchy calling to setTag with some key objects may be handy.
Also you can define ids.xml in res/values. You can see an exact example in android's sample code.
samples/ApiDemos/src/com/example/android/apis/RadioGroup1.java
samples/ApiDemp/res/values/ids.xml
Since API 17, the View class has a static method generateViewId() that will
generate a value suitable for use in setId(int)
This works for me:
static int id = 1;
// Returns a valid id that isn't in use
public int findId(){
View v = findViewById(id);
while (v != null){
v = findViewById(++id);
}
return id++;
}
(This was a comment to dilettante's answer but it got too long...hehe)
Of course a static is not needed here. You could use SharedPreferences to save, instead of static. Either way, the reason is to save the current progress so that its not too slow for complicated layouts. Because, in fact, after its used once, it will be rather fast later. However, I dont feel this is a good way to do it because if you have to rebuild your screen again (say onCreate gets called again), then you probably want to start over from the beginning anyhow, eliminating the need for static. Therefore, just make it an instance variable instead of static.
Here is a smaller version that runs a bit faster and might be easier to read:
int fID = 0;
public int findUnusedId() {
while( findViewById(++fID) != null );
return fID;
}
This above function should be sufficient. Because, as far as I can tell, android-generated IDs are in the billions, so this will probably return 1 the first time and always be quite fast.
Because, it wont actually be looping past the used IDs to find an unused one. However, the loop is there should it actually find a used ID.
However, if you still want the progress saved between subsequent recreations of your app, and want to avoid using static. Here is the SharedPreferences version:
SharedPreferences sp = getSharedPreferences("your_pref_name", MODE_PRIVATE);
public int findUnusedId() {
int fID = sp.getInt("find_unused_id", 0);
while( findViewById(++fID) != null );
SharedPreferences.Editor spe = sp.edit();
spe.putInt("find_unused_id", fID);
spe.commit();
return fID;
}
This answer to a similar question should tell you everything you need to know about IDs with android: https://stackoverflow.com/a/13241629/693927
EDIT/FIX: Just realized I totally goofed up the save. I must have been drunk.
The 'Compat' library now also supports the generateViewId() method for API levels prior 17.
Just make sure to use a version of the Compat library that is 27.1.0+
For example, in your build.gradle file, put :
implementation 'com.android.support:appcompat-v7:27.1.1
Then you can simply use the generateViewId() from the ViewCompat class instead of the View class as follow:
//Will assign a unique ID
myView.id = ViewCompat.generateViewId()
Happy coding !
Just an addition to the answer of #phantomlimb,
while View.generateViewId() require API Level >= 17,
this tool is compatibe with all API.
according to current API Level,
it decide weather using system API or not.
so you can use ViewIdGenerator.generateViewId() and View.generateViewId() in the
same time and don't worry about getting same id
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 fantouchx#gmail.com
*/
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();
}
}
}
In order to dynamically generate View Id form API 17 use
generateViewId()
Which will generate a value suitable for use in setId(int). This value will not collide with ID values generated at build time by aapt for R.id.
int fID;
do {
fID = Tools.generateViewId();
} while (findViewById(fID) != null);
view.setId(fID);
...
public class Tools {
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
public static int generateViewId() {
if (Build.VERSION.SDK_INT < 17) {
for (;;) {
final int result = sNextGeneratedId.get();
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();
}
}
}
inspired by #dilettante answer, here's my solution as an extension function in kotlin:
/* sets a valid id that isn't in use */
fun View.findAndSetFirstValidId() {
var i: Int
do {
i = Random.nextInt()
} while (findViewById<View>(i) != null)
id = i
}
public String TAG() {
return this.getClass().getSimpleName();
}
private AtomicInteger lastFldId = null;
public int generateViewId(){
if(lastFldId == null) {
int maxFld = 0;
String fldName = "";
Field[] flds = R.id.class.getDeclaredFields();
R.id inst = new R.id();
for (int i = 0; i < flds.length; i++) {
Field fld = flds[i];
try {
int value = fld.getInt(inst);
if (value > maxFld) {
maxFld = value;
fldName = fld.getName();
}
} catch (IllegalAccessException e) {
Log.e(TAG(), "error getting value for \'"+ fld.getName() + "\' " + e.toString());
}
}
Log.d(TAG(), "maxId="+maxFld +" name="+fldName);
lastFldId = new AtomicInteger(maxFld);
}
return lastFldId.addAndGet(1);
}
My Choice:
// Method that could us an unique id
int getUniqueId(){
return (int)
SystemClock.currentThreadTimeMillis();
}