AndroidのスレッドがUI更新を実行する方法とHandlerの初歩的な理解

7140 ワード

アンドロイドでは、ネットワークがデータをロードしたり、データを準備したりしてインタフェースに表示するのは頻繁なニーズです.一般的には、オンラインでデータをダウンロードした後、uiスレッド内でuiインタフェースを更新する必要があります.私たちは以下の2つの点を避ける必要があります.
決してUI Threadで時間のかかる操作をしてはいけません.私たちのUI Thread をブロックしてはいけません.
ui Threadで時間のかかる操作を行い、ブロック時間が5秒を超えるとANR(Application Not Responding)という現象が発生し、アプリケーションはボックスをポップアップし、ユーザーにプログラムを終了するかどうかを選択させる.Android開発にとってANRの出現は絶対に許されない
UI Thread以外のスレッドでは、UI要素を操作できません.
一、Handlerのui更新方式
Android UIコントロールはスレッドが安全ではないため、UI Thread以外のスレッドでUIコントロールを操作することはできません.具体的な動作でもエラーが発生するので、避ける必要があります.
では、私たちの実際のコードでは、どのような方法で良いスレッドの消費時間操作+uiスレッドでインタフェースを更新するか、方法は実はたくさんあります.
私が以前仕事をしていたように言えば、new Thread()はスレッド内で時間のかかる操作を行い、操作が完了した後、
new Handler(context.getMainLooper()).post(new Runnable() {
 
    @Override
    public void run() {
        //                      ui              ui
 
    }
});
スレッド内の操作インタフェースのリフレッシュエラーを防止する方法ですが、コードの中でこのように書くのはつらいと言えます(だから私が以前書いたコードを見て本当に自分を切りたいと思っています)、だから私は新しい良い方法で回避する必要があります.私はHandlerを深く理解しました.ここには最も簡単な例があります.
public class MainActivity extends Activity{
	
	private Handler handler;
	private TextView mTextView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		handler = new Handler(){
			public void handleMessage(Message msg) {
				Log.i("test", "    :" + msg.what);
				A a = (A)msg.obj;
				Log.i("test", "      :" + a.getId() + ",  :" + a.getName());
				mTextView.setText(a.getName());
			}
		};
		mTextView = (TextView)findViewById(R.id.activity_main_text_title);
		initClick();
	}
	
	private void initClick(){
		Log.i("test", "  :" + mTextView);
		mTextView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {

				new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							Thread.sleep(2000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					//        Message  ,               Message  
						Message msg = Message.obtain(); 
						msg.what = 1;
						msg.obj = new A("3","lilei");
						handler.sendMessage(msg);
					}
				}).start();
			}
		});
	}
}

そして次は出力
08-16 16:31:31.196: I/test(31963):     :1
08-16 16:31:31.196: I/test(31963):       :3,  :lilei

二、Handlerのスレッド間通信方法
ここでHandler呼び出しの方法としては、post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)、sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message,long)、sendMessageDelayed(Message,long)、sendMessageDelayed(Message,long)などがありますが、方法は一つ一つ紹介しないので、方法名を見て少し推測できます
しかし、ここでTitleを修正する方法は、実はメインスレッドにあるので、ソースコードを見なくてもわかります.コールバック時に実行されるコードはメインスレッドの中にあるので、Handlerがどこで新築され、コールバックが対応スレッドで実行されるかを推測することができます.
Handlerのコンストラクタから見れば、この点を発見することができます.
public Handler() {  
        this(null, false);  
}  
public Handler(Callback callback, boolean async) {  
        if (FIND_POTENTIAL_LEAKS) {  
            final Class<? extends Handler> klass = getClass();  
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
                    (klass.getModifiers() & Modifier.STATIC) == 0) {  
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
                    klass.getCanonicalName());  
            }  
        }  
  
        mLooper = Looper.myLooper();  
        if (mLooper == null) {  
            throw new RuntimeException(  
                "Can't create handler inside thread that has not called Looper.prepare()");  
        }  
        mQueue = mLooper.mQueue;  
        mCallback = callback;  
        mAsynchronous = async;  
    }  

Looper.myLooper()は、現在のスレッドに保存されているLooperインスタンスを取得し、このLooperインスタンスに保存されているMessageQueue(メッセージキュー)を取得します.これにより、handlerのインスタンスが私たちのLooperインスタンスのMessageQueueに関連付けられることが保証されます.
そして下を見て最後にhandleMessageを呼び出した場所を探せばそれを確認できます
Handlerはどのスレッドで新規作成され、最後のhandleMessageはどのスレッドで実行されますか.
ここでも少し気づきますが、
 if (mLooper == null) {  

この一言は、一般的には無の矢を放つことはありません.それは、取得した現在のmLooperが空である可能性があることを示しています.では、どのような場合に空になるのでしょうか.最後に、このような状況が私たち自身が新しく作ったワークスレッドの中で
Androidのuiスレッドを除くと、作成されたワークスレッドのデフォルトはメッセージループとメッセージキューがありません.このスレッドにメッセージキューとメッセージループを持たせるには、スレッドでまずLooperを呼び出す必要があります.prepare()はメッセージキューを作成し、Looperを呼び出します.loop()メッセージループへ
class LooperThread extends Thread {    
    public Handler mHandler;    
    
    public void run() {    
        Looper.prepare();    
    
        mHandler = new Handler() {    
            public void handleMessage(Message msg) {    
                // process incoming messages here    
            }    
        };    
        Looper.loop();    
    }    
}    

では、これらを収集して、スレッド間の通信を行うことができますか?この点について、コードで試してみましたが、結論は可能です.
public class MainActivity extends Activity{
	
	private Handler handler;
	private TextView mTextView;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		new Thread(new Runnable() {
			@Override
			public void run() {
				final A outTag = new A("15","lilei");
				 Looper.prepare(); 
				 handler = new Handler(){
					 public void handleMessage(Message msg) {
						 Log.i("test","callback1:" + msg.what);
						 A a = (A)msg.obj;
						 outTag.setId(a.getId());
						 outTag.setName(a.getName());
						 Log.i("test","callback2:" + a.getId() + ",aname:" + a.getName());
					 }
				 };
				 Looper.loop();
				 
			}
		}).start();
		mTextView = (TextView)findViewById(R.id.activity_main_text_title);
		initClick();
	}
	
	
	private void initClick(){
		mTextView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				new Thread(new Runnable() {
					@Override
					public void run() {
						Message msg = Message.obtain();
						msg.what = 1;
						msg.obj = new A("50","hanmeimei");
						handler.sendMessage(msg);
					}
				}).start();
			}
		});
	}
}

コールバックをクリックした後、最初の新しいスレッド内でクリックを出力したときに新しいスレッドが過去の値を渡し、Looper.prepare(); ロoperとloop();取り除くと誤報になります
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
これにより,サブスレッド内でデータ操作を行った後ui更新インタフェースでも同様の法則が見出される.
Handlerは指定されたスレッドにバインドされ、他のスレッドで処理されたデータを渡すか、処理が終了したことを通知します.
三、スレッドのキュー実行
ここにしっぽを残しておくのはHandlerのキュー実行可能な方法です
 HandlerThread handlerThread = new HandlerThread("myHandlerThread");     
 handlerThread.start();     
 handler = new Handler(handlerThread.getLooper());     
<pre name="code" class="java"> handler<span style="font-family: Arial; font-size: 14px; line-height: 26px;">.sendEmptyMessage(1);</span>

これにより、スレッド内のキューでタスクを実行でき、便利な方法です.
時間を見つけてHandlerについて詳しく知る