android: avoid excessive UI updates

On some devices, updating notifications and status text at a high rate
causes whole-system UI lag and even complete system freezes. On a
Nexus 5, the uploader is completely unusable and requires a hard phone
restart almost every time "upload all" is triggered from the UI. The
device freezes happen across multiple stock Android releases.

This commit reduces the update rate of the UI significantly by updating
the notification only when the integer percent value of the upload
progress changes. It also reduces the update rate of the status text to
around 30 fps, which should still be plenty. With both of the changes,
the uploader behaves as expected. Either change alone is not enough to
get rid of the device freezes.

Change-Id: I2c170772d29c4670c8c10d09cdaea96207fc9e61
This commit is contained in:
Tilman Dilo 2016-09-05 21:27:58 +02:00
parent e44c2571e0
commit bf4ebe0fe3
2 changed files with 15 additions and 7 deletions

View File

@ -205,14 +205,15 @@ public class CamliActivity extends Activity {
public void setUploadStatsText(final String text) throws RemoteException {
// We were getting these status updates so quickly that the calls to TextView.setText
// were consuming all CPU on the main thread and it was stalling the main thread
// for seconds. Ridiculous. So instead, only update this every 5 milliseconds,
// otherwise wait for the looper to be idle to update it.
// for seconds, sometimes even triggering device freezes. Ridiculous. So instead,
// only update this every 30 milliseconds, otherwise wait for the looper to be idle
// to update it.
mHandler.post(new Runnable() {
@Override
public void run() {
mStatusTextWant = text;
long now = System.currentTimeMillis();
if (mLastStatusUpdate < now - 5) {
if (mLastStatusUpdate < now - 30) {
mStatusTextCurrent = mStatusTextWant;
textStats.setText(mStatusTextWant);
mLastStatusUpdate = System.currentTimeMillis();

View File

@ -71,6 +71,7 @@ public class UploadService extends Service {
// thread exits
private Notification.Builder mNotificationBuilder; // null until upload is
// started/resumed
private int mLastNotificationProgress = 0; // last computed value of the uploaded bytes, to avoid excessive notification updates
private final Map<QueuedFile, Long> mFileBytesRemain = new HashMap<QueuedFile, Long>();
private final LinkedList<QueuedFile> mQueueList = new LinkedList<QueuedFile>();
private final Map<String, Long> mStatValue = new TreeMap<String, Long>();
@ -408,11 +409,16 @@ public class UploadService extends Service {
Notification notification = null;
synchronized (this) {
if (mNotificationBuilder != null) {
int kBUploaded = (int)(mBytesUploaded / 1024L);
int kBTotal = (int)(mBytesTotal / 1024L);
int progress = (int)(100 * (double)mBytesUploaded/(double)mBytesTotal);
mNotificationBuilder.setProgress(kBTotal, kBUploaded, false);
notification = mNotificationBuilder.build();
// Only build new notification when progress value actually changes. Some
// devices slow down and finally freeze completely when updating too often.
if (mLastNotificationProgress != progress) {
mLastNotificationProgress = progress;
mNotificationBuilder.setProgress(100, progress, false);
notification = mNotificationBuilder.build();
}
}
try {
mCallback.setByteStatus(mBytesUploaded, mBytesInFlight, mBytesTotal);
@ -700,6 +706,7 @@ public class UploadService extends Service {
.setContentText("Camlistore uploader running")
.setSmallIcon(android.R.drawable.stat_sys_upload);
mNotificationManager.notify(NOTIFY_ID_UPLOADING, mNotificationBuilder.build());
mLastNotificationProgress = -1;
mUploading = true;
mUploadThread = new UploadThread(UploadService.this, hp, mPrefs.trustedCert(), mPrefs.username(), mPrefs.password());