
Smart queue size in android
In one of the projects at work, a seemingly trivial task arose: to upload pictures and descriptions to them from the server so that the user could switch them without delay. To do this, we used a method that, with each switching, checked how many elements remained in the queue, and if there was less than a certain number, loaded another element. The case was decided by a constant equal to 3. But, as you know, android devices vary greatly in performance, and on other phones this number was not enough, but setting a very large number was inefficient, since the user could generally view one or two items and leave from the screen. Then I thought, why not determine this number in a smart way?
I found that the following data is useful at the input:
I immediately refused the calculations in the GUI stream, for example, during the switch itself, because the size of the queue does not always need to be measured as often as the user can click on the button, and sometimes, on the contrary, it needs to be prepared in advance. So the whole procedure is carried out in a separate thread with a lower priority, with a sleep interval, which will also change.
instance of the NetworkInfo class is required:
At each iteration of the calculations, first of all, we check
further
Switching interval (Variables
To obtain the interval, use the method
As you noticed, the average switching value is used.
Switch Delay (Variables
Methods used to get
Free RAM (Variable
I repeat that the current balance allocated to the process is measured, and not the total size of the remaining RAM.
As a result, the queue size is always an inhibitory bond, and the switching interval can be either an inhibitory or a simple bond.
Thanks to everyone who read to the end. It was nice to share an interesting task.
Description
The size of the queue will be determined by a small decision-making system, which for convenience I will call a single-layer trained neural network.I found that the following data is useful at the input:
- Type of Internet connection (We are only interested in wifi or edge, since the operation of determining the connection speed is too time-consuming)
- Switch Interval (How often does the user switch pictures)
- Switching delay (When everything is fine, it should be 1ms in my case)
- Free RAM (I’ll immediately note that the current balance allocated to the process is measured, and not the total size of the remaining RAM, so this parameter is not very important)
- Queue size (Feedback is important to inhibit an overgrown queue)
I immediately refused the calculations in the GUI stream, for example, during the switch itself, because the size of the queue does not always need to be measured as often as the user can click on the button, and sometimes, on the contrary, it needs to be prepared in advance. So the whole procedure is carried out in a separate thread with a lower priority, with a sleep interval, which will also change.
Let's get started
Since the data varies greatly in size (for example, the rest of the memory is measured in bytes, so that there will be numbers with more than six digits, and the type of connection is an unambiguous constant), for convenience, several constants have been introduced to normalize the values, which help to bring all numbers to the range ~ [-20; 20]. In addition to constants, the difference between the manually determined normal value and the current one is sometimes used, more on that below.More details
Type of Internet connection (Variableprivate double connectionType=0;
) An instance of the NetworkInfo class is required:
NetworkInfo activeNetwork = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
At each iteration of the calculations, first of all, we check
if (!activeNetwork.isConnectedOrConnecting()) return;
further
connectionType = (activeNetwork.getType()); // EDGE = 0; WiFi = 1
Switching interval (Variables
private long tapInterval = 5000; private byte tapTrigger = 0; private double tapAssemblyAverage = 5000;private long nextTap = 0; private long lastTap = 0;
) To obtain the interval, use the method
public synchronized void setNextTap(long nextTap) {
this.lastTap=this.nextTap;
this.nextTap = nextTap;
if (nextTap > lastTap) {
tapInterval = nextTap - lastTap;
lastTap = nextTap;
}
if (tapInterval > TAP_INTERVAL_UPPER_THRESHOLD) tapInterval = TAP_INTERVAL_UPPER_THRESHOLD;
if (tapInterval < 100) tapInterval = 100;
tapAssemblyAverage = (tapAssemblyAverage * tapTrigger + tapInterval) / (++tapTrigger);
}
which is called from the place where the user switches the elements. TAP_INTERVAL_UPPER_THRESHOLD
Is a constant, in my case, equal to 10,000 ms. As you noticed, the average switching value is used.
tapTrigger
reset when updated. Switch Delay (Variables
private long delayInterval = 500; private long finishDelay = 0; private long startDelay = 0;
) Methods used to get
public synchronized void setStartDelay(long start) {
this.startDelay = start;
}
public synchronized void setFinishDelay(long finish) {
this.finishDelay = finish;
if (finishDelay > startDelay) {
delayInterval = finishDelay - startDelay;
}
}
The last delay is important here, not the average value of the delays. Free RAM (Variable
private long freeRam = 5000000;
) I repeat that the current balance allocated to the process is measured, and not the total size of the remaining RAM.
freeRam = Runtime.getRuntime().freeMemory();
Normalization
An array of Relations / Weights isdouble[][] RW = new double[2][5];
used to store normalized values and their weights. A number of constants and formulas are used to normalize.Constants
private static final int TAP_EQUALIZER = 1000;
private static final int DELAY_EQUALIZER = 1000;
private static final int RAM_EQUALIZER = 1000000;
private static final int TAP_INTERVAL_UPPER_THRESHOLD = 10000;
private static final int DELAY_INTERVAL_UPPER_THRESHOLD = 5000;
private static final int NORMAL_TAP_INTERVAL = 9;
private static final int TAP_I = 0;
private static final int DELAY_I = 1;
private static final int CONNECTION = 2;
private static final int RAM = 3;
private static final int QUEUE = 4;
private synchronized void normalization() {
if (delayInterval > DELAY_INTERVAL_UPPER_THRESHOLD) delayInterval = DELAY_INTERVAL_UPPER_THRESHOLD;
if (delayInterval < 0) delayInterval = 0;
if (connectionType > 1 || connectionType < 0) connectionType = 0.5;
connectionType = (connectionType - 2) * 2;
if (freeRam < 100000 || freeRam > 10000000) freeRam = 5000000;
}
private void cast() {
RW[0][TAP_I] = (NORMAL_TAP_INTERVAL - (tapAssemblyAverage / TAP_EQUALIZER));
RW[0][DELAY_I] = (double) (delayInterval / DELAY_EQUALIZER);
RW[0][CONNECTION] = connectionType;
RW[0][RAM] = (double) (freeRam / RAM_EQUALIZER);
RW[0][QUEUE] = (double) (count);
}
Weights
It remains to say about the scales. They can be selected in two ways: analytically or by the simplex method, for example, in Excel. I selected weights analytically, so I’ll just give the results:private void setInitialWeights() {
RW[1][TAP_I] = 0.5;
RW[1][DELAY_I] = 1;
RW[1][CONNECTION] = -1;
RW[1][RAM] = 0.1;
RW[1][QUEUE] = -0.1;
}
As a result, the queue size is always an inhibitory bond, and the switching interval can be either an inhibitory or a simple bond.
Calculation
In the next method, the queue size is calculated using the standard formula and rounded to a larger integer. In addition, the update interval is also calculated here. It depends on changing the queue. If it has changed more than by a constant valueint SLEEP_COMPARISON_THRESHOLD = 2
, then the interval decreases, otherwise it increases.Constants and variables
private static final int SLEEP_COMPARISON_THRESHOLD = 2;
private static final int SLEEP_ADDITION_INC_STEP = 100;
private static final int SLEEP_ADDITION_DEC_STEP = -500;
private long sleepInterval = 500;
private int activation() {
double value = 0;
for (int i = 0; i < 5; i++) value += RW[0][i] * RW[1][i];
sleepInterval += ((Math.abs(count - value) > SLEEP_COMPARISON_THRESHOLD || sleepInterval > 10000)) ? SLEEP_ADDITION_DEC_STEP : SLEEP_ADDITION_INC_STEP;
if (sleepInterval < 500) sleepInterval = 500;
Log.d("QUEUE", "sleep: " + String.valueOf(sleepInterval));
if (value < 1) value = 1;
Log.d("QUEUE", "queue: " + String.valueOf(value));
return (int) Math.ceil(value);
}
Full code
public class IntellijQueue extends Thread {
private static final int TAP_I = 0;
private static final int DELAY_I = 1;
private static final int CONNECTION = 2;
private static final int RAM = 3;
private static final int QUEUE = 4;
private static final int TAP_EQUALIZER = 1000;
private static final int DELAY_EQUALIZER = 1000;
private static final int RAM_EQUALIZER = 1000000;
private static final int SLEEP_COMPARISON_THRESHOLD = 2;
private static final int SLEEP_ADDITION_INC_STEP = 100;
private static final int SLEEP_ADDITION_DEC_STEP = -500;
private static final int TAP_INTERVAL_UPPER_THRESHOLD = 10000;
private static final int DELAY_INTERVAL_UPPER_THRESHOLD = 5000;
private static final int NORMAL_TAP_INTERVAL = 9;
public volatile int count = 3;
private long finishDelay = 0;
private long startDelay = 0;
private long nextTap = 0;
private long lastTap = 0;
private long sleepInterval = 500;
private double connectionType = 0;
private long tapInterval = 5000;
private long delayInterval = 500;
private long freeRam = 5000000;
private double RW[][];
private byte tapTrigger = 0;
private double tapAssemblyAverage = 5000;
private NetworkInfo activeNetwork;
public IntellijQueue(Context context) {
this.setPriority(MIN_PRIORITY);
this.setDaemon(true);
activeNetwork = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
RW = new double[2][5];
setInitialWeights();
lastTap = System.currentTimeMillis();
}
private void setInitialWeights() {
RW[1][TAP_I] = 0.5;
RW[1][DELAY_I] = 1;
RW[1][CONNECTION] = -1;
RW[1][RAM] = 0.1;
RW[1][QUEUE] = -0.1;
}
private void cast() {
RW[0][TAP_I] = (NORMAL_TAP_INTERVAL - (tapAssemblyAverage / TAP_EQUALIZER));
RW[0][DELAY_I] = (double) (delayInterval / DELAY_EQUALIZER);
RW[0][CONNECTION] = connectionType;
RW[0][RAM] = (double) (freeRam / RAM_EQUALIZER);
RW[0][QUEUE] = (double) (count);
}
private int activation() {
double value = 0;
for (int i = 0; i < 5; i++) value += RW[0][i] * RW[1][i];
sleepInterval += ((Math.abs(count - value) > SLEEP_COMPARISON_THRESHOLD || sleepInterval > 10000)) ? SLEEP_ADDITION_DEC_STEP : SLEEP_ADDITION_INC_STEP;
if (sleepInterval < 500) sleepInterval = 500;
Log.d("QUEUE", "sleep: " + String.valueOf(sleepInterval));
if (value < 1) value = 1;
Log.d("QUEUE", "queue: " + String.valueOf(value));
return (int) Math.ceil(value);
}
private synchronized void updateValues() {
if (!activeNetwork.isConnectedOrConnecting()) return;
connectionType = (activeNetwork.getType()); // EDGE = 0; WiFi = 1
tapTrigger=0;
freeRam = Runtime.getRuntime().freeMemory();
Log.d("QUEUE", "tap interval: " + String.valueOf(tapInterval));
Log.d("QUEUE", "delay interval: " + String.valueOf(delayInterval));
Log.d("QUEUE", "free RAM: " + String.valueOf(freeRam));
normalization();
cast();
}
private synchronized void normalization() {
if (delayInterval > DELAY_INTERVAL_UPPER_THRESHOLD) delayInterval = DELAY_INTERVAL_UPPER_THRESHOLD;
if (delayInterval < 0) delayInterval = 0;
if (connectionType > 1 || connectionType < 0) connectionType = 0.5;
connectionType = (connectionType - 2) * 2;
if (freeRam < 100000 || freeRam > 10000000) freeRam = 5000000;
}
@Override
public void run() {
try {
while (true) {
updateValues();
count = activation();
sleep(sleepInterval);
}
} catch (InterruptedException e) {
}
}
public synchronized void setStartDelay(long start) {
this.startDelay = start;
Log.d("QUEUE", "S Ok.");
}
public synchronized void setFinishDelay(long finish) {
this.finishDelay = finish;
Log.d("QUEUE", "F Ok.");
if (finishDelay > startDelay) {
delayInterval = finishDelay - startDelay;
}
}
public synchronized void setNextTap(long nextTap) {
this.lastTap=this.nextTap;
this.nextTap = nextTap;
if (nextTap > lastTap) {
tapInterval = nextTap - lastTap;
lastTap = nextTap;
}
if (tapInterval > TAP_INTERVAL_UPPER_THRESHOLD) tapInterval = TAP_INTERVAL_UPPER_THRESHOLD;
if (tapInterval < 100) tapInterval = 100;
tapAssemblyAverage = (tapAssemblyAverage * tapTrigger + tapInterval) / (++tapTrigger);
}
}
Total
After connecting the class to the project, the delay in switching disappeared. When testing on different devices, the queue ranged from 2 to 11. Tests were conducted on: Samsung Galaxy S2, Galaxy S3, Samsung Gio, Motorolla Atrix 2, Nexus, Explay tablet.Thanks to everyone who read to the end. It was nice to share an interesting task.