📜 ⬆️ ⬇️

"Smart" queue size in android

In one of the projects, a seemingly trivial task arose at work: upload pictures and descriptions to them from the server so that the user could switch them without delay. For this, the method was used, which, with each switch, checked how many items remained in the queue, and, if there was less than a certain number left, loaded the next item. The case was solved by a constant equal to 3. But, as you know, android devices vary greatly in performance, and on other phones that number was not enough, but to set a very large number is inefficient, since the user could generally look at one or two elements and leave from the screen. Then I thought, why not determine this number in an intelligent way?

Description

Determine the size of the queue will be a small decision-making system, which for convenience I will call a single-layer trained neural network.
I found that the following data would be useful at the entrance:

I immediately refused to perform calculations in the GUI thread, for example, during the switch itself, because the queue size does not always need to be measured as often as the user can click on the button, and sometimes, on the contrary, you need to prepare in advance. So the whole procedure is carried out in a separate stream with a lower priority, with an interval of sleep, which will also change.

Let's get started

Since the data varies greatly in size (for example, the remaining memory is measured in bytes, so there will be numbers with more than six digits, and the type of connection is a single-valued constant), for convenience, several constants are entered to normalize the values ​​that help to bring all numbers to the range ~ [-20; 20]. In addition to constants, sometimes the difference between a manually determined normal value and the current one is used, see below.

Read more
Internet connection type (Variable private double connectionType=0; )
An instance of the NetworkInfo class is required:
NetworkInfo activeNetwork = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
At each iteration of calculations, we first 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 get 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 items. 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 updating.
')
Switching delay (Variables private long delayInterval = 500; private long finishDelay = 0; private long startDelay = 0; )
For getting used methods
 public synchronized void setStartDelay(long start) { this.startDelay = start; } public synchronized void setFinishDelay(long finish) { this.finishDelay = finish; if (finishDelay > startDelay) { delayInterval = finishDelay - startDelay; } } 
Here the last delay is important, 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
For storing normalized values ​​and their weights, the array is Relations / double[][] RW = new double[2][5]; For normalization, a series of constants and formulas are used.
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 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 inhibitory or simple.

Calculation
In the following method, the standard queue formula calculates the queue size and rounds to a larger integer. In addition, the refresh interval is calculated here. It depends on the queue change. If it has changed by more than the value of the constant int 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, switching delays disappeared. When testing on different devices, the queue changed in the range from 2 to 11. The 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 challenge.

Source: https://habr.com/ru/post/171173/


All Articles