Handler: Method of updating UI

It always feels like UI updates in android are confusing! To sum up for yourself, it's like throwing a brick to attract jade.

Before reading this article, suppose you understand threads and the use of Handler.

At the end of the article, a sketch is appended to illustrate the relationship between Handler, Message, MessageQueue and Looper.

1. Open thread update UI in onCreate() method

public class MasterActivity extends Activity { 
TextView tv = null; Button btn = null; 
@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 
    System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId()); 
    tv = (TextView)findViewById(R.id.text); 
    btn = (Button)findViewById(R.id.btn); /*onCreate Open a new thread and update the UI. No error or abnormal information!*/ 
    Thread thread = new Thread(new Runnable() { 
        @Override public void run() { 
            System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId()); 
            tv.setText("update UI is success!"); 
            btn.setText("update UI is success!"); 
        }
    }); 
    thread.start(); 
}  

No errors or abnormalities!

Think that the open thread and the UI thread (main thread) are the same thread, but unfortunately, their thread id is totally unrelated!

You can track the android source code, mainly because when loading Activity, there is no trigger to check the single-threaded model (that is, sub-threads can not update the ui).

If you don't believe it, you can `while true'in the thread above, then you will make an error.

 

2. Callback methods starting with on in activity such as onResume, onStart

@Override
protected void onRestart() {
    super.onRestart(); /*onRestart Open new threads and update UI*/
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId());
            tv.setText("update UI is success!");
            btn.setText("update UI is success!");
        }
        
    });
    thread.start();
}

Sorry, press the return button to start the program, or press the Home key to restart the program, just toss around a few times, you will pack the exception! The information is as follows:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Meaning: Only the main thread can update the UI.

Solution: Add the `postInvalidate()'method.

@Override protected void onRestart() { 
    super.onRestart(); /*onRestart Open new threads and update UI*/ 
    Thread thread = new Thread(new Runnable() { 
        @Override public void run() { 
            System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId()); 
            tv.postInvalidate(); btn.postInvalidate(); 
            tv.setText("update UI is success!"); 
            btn.setText("update UI is success!"); 
        }
    }); 
    thread.start(); 
} 

` postInvalidate()` method, source code:

public void postInvalidate() { 
    postInvalidateDelayed(0); 
} 

public void postInvalidateDelayed(long delayMilliseconds) { 
    // We try only with the AttachInfo because there's no point in invalidating 
    // if we are not attached to our window 
    if (mAttachInfo != null) { 
        Message msg = Message.obtain(); 
        msg.what = AttachInfo.INVALIDATE_MSG; 
        msg.obj = this; 
        mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); 
    }
} 

Actually, it's a mechanism that calls Handler to process messages! This method can be used to update UI directly in sub-threads. There's another method, invalidate (), which I'll talk about later!

3. Open threads and update UI in Button events

public class MasterActivity extends Activity {
    TextView tv = null; Button btn = null; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId()); 
        tv = (TextView)findViewById(R.id.text); 
        btn = (Button)findViewById(R.id.btn); 
        btn.setOnClickListener(new OnClickListener() { 
            @Override 
            public void onClick(View v) { 
                Thread thread = new Thread(new Runnable() { 
                    @Override public void run() { 
                    System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getId()); 
                    tv.setText("update UI is success!"); 
                    btn.setText("update UI is success!"); 
                }
            }); 
            thread.start(); 
        }
     }); 
} 

Sorry, report an error! Even if you add the `postInvalidate()'method, the error will be reported.

 android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

 

 

4. Use Handler to update UI with multithreading

A. Open a thread to notify Handler in the run method

b. Handler updates UI using handleMessage method

 

5. Handler and invalidate methods update UI with multithreading

Method `invalidate'is mainly used in the main thread (i.e. UI thread), but not in the sub-thread. If you need to use the postInvalidate method in a subthread.

The api of sdk is described as follows:

public void invalidate () Since: API Level 1 Invalidate the whole view.

If the view is visible, onDraw(Canvas) will be called at some point in the future.

This must be called from a UI thread. To call from a non-UI thread, call postInvalidate(). 

Look at the source code of this method:

public void invalidate() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
        }
        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
            final ViewParent p = mParent;
            final AttachInfo ai = mAttachInfo;
            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop); // Don't call invalidate -- we don't want to internally scroll // our own bounds p.invalidateChild(this, r); } } } 
            }
        }
    }

` invalidate `method if you call it directly in the main thread, you can't see any updates. Need to be combined with Handler!

Thank you for Lei Feng, a good example: http://disanji.net/2010/12/12/android-invalidate-ondraw/

It's just a little bit modified by me. Join times and see how many times onDraw actually runs.

Android handles drawing in onDraw event, and invalidate() function can trigger onDraw event again, and then do drawing action again.

public class MasterActivity extends Activity {
	static int times = 1;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        setContentView( new View(null){
 
            Paint vPaint = new Paint();  //Drawing style objects
            private int i = 0;           //Arc angle
 
            @Override
            protected void onDraw (Canvas canvas) {
                super.onDraw(canvas);
                System.out.println("this run " + (times++) +" times!");
 
                // Setting Drawing Style
                vPaint.setColor( 0xff00ffff ); //stroke color
                vPaint.setAntiAlias( true );   //Anti-aliasing
                vPaint.setStyle( Paint.Style.STROKE );
 
                // Draw an arc
                canvas.drawArc(new RectF(60, 120, 260, 320), 0, i, true, vPaint );
 
                // Arc angle
                if( (i+=10) > 360 ) {
                    i = 0;
                }
 
                // Redraw and execute onDraw again
                invalidate();
            }
        });
    }
}

After testing, it is found that time has been ++, indicating that onDraw has been called many times, and consistently in the drawing!

The API of SDK is sometimes depressing and speechless.... The use of invalidate remains to be explored. The revolution has not yet succeeded, comrades still need to work hard!

 

Blog Update, Recommended Articles:

View Programming (2): Re-exploration of invalidate ()

View programming (3): invalidate() source code analysis

 

Appendix: The relationship between Handler, Message, MessageQueue and Looper

 

Here's a description

1. Looper uses an infinite loop to fetch messages, which is controlled by android os.

2. android threads are insecure, that is, do not update the UI in sub-threads.

3. Looper takes out the message, handler can get their own message by what, obj and so on, so it is recommended to use these quantities.

Welcome to pay attention to my personal Wechat Public Number: ITMan, Wechat Public Number Search: ITManMark, or scan the following two-dimensional code.

I stumbled upon a giant bull's AI tutorial and couldn't help sharing it with you. The course is not only zero-based, easy to understand, but also very funny and humorous, like reading a novel! I think it's too good, so I'll share it with you. Point Here You can skip to the tutorial.

 

Keywords: Android SDK Programming

Added by wookie on Tue, 23 Jul 2019 10:45:36 +0300