子线程中定义Handler,Handler定义在哪个线程中,就跟那个线程绑定,在线程中绑定Handler需要调用Looper.prepare(); 方法,主线程中不调用是因为主线程默认帮你调用了 :
public class LoopThread implements Runnable { public Handler mHandler = null; @Override public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { String result = NetUtil.getJsonContent("北京"); //完成了获取北京天气的操作; Log.i("test", "handler"+result); } }; Looper.loop(); } }
其中Looper.prepare();和Looper.loop();维护了一个消息队列,等待消息注入并在子线程中执行;
主线程中这样调用
lThread.mHandler.sendEmptyMessage(0);
主线程向子线程发消息,让子线程执行指定的操作,在Android中还有一种方法,即:HandlerThread,看下面的例子:
HandlerThread handlerThread = new HandlerThread("jerome"); handlerThread.start(); /** * 这里要将HandlerThread创建的looper传递给threadHandler,即完成绑定; */ threadHandler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: 这儿可以做耗时的操作; Log.i("jerome", "hello,I am sub thread"); break; default: break; } } };
主线程中定义Handler:
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: //do something,refresh UI; break; default: break; } } };
子线程处理完耗时操作之后发消息给主线程,更新UI:
mHandler.sendEmptyMessage(0);
这样在子线程与主线程任务分工的条件下完成了消息交互;
创建线程 ThreadA 用来接受线程 ThreadB 传递的消息
class ThreadA implements Runnable{ private Handler mHandler; //run运行后才不为null在main里判断 public Handler getHandler(){ return mHandler; } @SuppressLint("HandlerLeak") @Override public void run() { Looper.prepare(); mHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 1: Log.e("线程A","线程B发过来消息了--"+msg.obj); break; } } }; Looper.loop(); } }
创建线程 ThreadB 发送消息到线程 ThreadA 中
class ThreadB implements Runnable{ @Override public void run() { Message mess=Message.obtain(); mess.what=1; mess.obj= "线程B"+System.currentTimeMillis(); handler.sendMessage(mess); } }
在activity onCreate方法里 , 分别调用线程 ThreadA 和 ThreadB
ThreadA threadA = new ThreadA(); ThreadB threadb = new ThreadB(); new Thread(threadA).start(); if(threadA.getHandler() == null) { try { Thread.sleep(1000); handler = threadA.getHandler(); } catch (InterruptedException e) { e.printStackTrace(); } } new Thread(threadb).start();
主线程主要来完成UI绘制和响应用户的操作 , 大多数情况都习惯在 onCreate()、onResume()、onCreateView() 中启动我们的逻辑 , 导致代码运行在主线程中,容易导致ANR(Application Not Responding),这些逻辑包括文件读写, 数据库读写, 网络查询等。
开启一个子线程来完成一个耗时操作,以避免阻塞主线程而出现卡顿甚至ANR导致闪退。
子线程执行完要更新UI的时候,我们又必须回到主线程来更新,实现这一功能常用的方法是执行
Activity的runOnUiThread() 方法:runOnUiThread(new Runnable() { void run() { // do something } });
Fragment/Presentation/Dialog中使用:
((MainActivity)getActivity()).runOnUiThread(new Runnable() { @Override public void run() { //在此进行更新UI的操作 } });
深入理解runOnUiThread()
一个
Android
已封装好的轻量级异步类。属于抽象类,即使用时需实现子类。public abstract class AsyncTask
{ ... }
作用
- 实现多线程:在工作线程中执行任务,如 耗时任务
- 异步通信、消息传递:实现工作线程 & 主线程(
UI
线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI
操作,保证线程安全。
优点
- 方便实现异步通信
不需使用 “任务线程(如继承Thread
类) +Handler
”的复杂组合- 节省资源
采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销
类定义
public abstract class AsyncTask
{ ... } // 类中参数为3种泛型类型 // 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型 // 具体说明: // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数 // b. Progress:异步任务执行过程中,返回下载进度值的类型 // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致 // 注: // a. 使用时并不是所有类型都被使用 // b. 若无被使用,可用java.lang.Void类型代替 // c. 若有不同业务,需额外再写1个AsyncTask的子类 }
核心方法 execute()
作用 : 触发执行异步线程任务
调用时刻 : 手动调用
使用场景 : 必须在UI线程调用 , 运行在主线程
核心方法 onPreExecute()
作用 : 执行 线程任务前的任务操作
调用时刻 : 执行 线程任务前 自动调用 , 即 execute() 执行前调用
使用场景 : 用于界面的初始化操作 , 如 : 显示进度条的对话框
核心方法 doInBackground()
作用 : 接收输入参数 , 执行任务中的耗时操作 , 返回任务中执行的结果
调用时刻 : 执行 线程任务时 自动调用 , 即 onPreExecute() 执行后 自动调用
使用场景 : 不能更改UI组件的信息 , 执行过程中可以调用 publishProgress() 更新进度信息
核心方法 onProgressUpdate()
作用 : 在主线程中显示线程任务执行的进度
调用时刻 : 调用publishProgress(Progress ... values) 时 自动调用
核心方法 onPostExecute()
作用 : 接受线程任务执行的结果 , 将执行结果显示到UI组件上
调用时刻 : 线程任务结束时 自动调用
核心方法 onCancelled()
作用 : 将异步任务设置为取消状态
调用时刻 : 异步任务被取消时 即自动调用
使用场景 : 该方法调用时 onPostExecute() 就不会被调用
View.post方法可以在UI线程上安排一个Runnable,确保在某些情况下,如视图大小改变后,执行特定操作。然而,使用View.post时,可能会遇到一些问题。以下是一些可能遇到的坑及其解决方法:
生命周期问题:在使用View.post时,如果Activity或Fragment的生命周期发生变化,如onDestroy或onDetach,可能导致内存泄漏。解决方法是在生命周期方法中移除所有已post的Runnable。
@Override protected void onDestroy() { super.onDestroy(); yourView.removeCallbacks(yourRunnable); }
- 延迟执行:View.post可能会在稍后执行Runnable,这意味着如果您希望立即执行操作,可能会遇到问题。解决方法是使用View.post()之外的其他方法,如在onLayout或onSizeChanged中执行操作。
- 视图不可见:如果使用View.post时视图不可见(例如,它已被隐藏或从窗口分离),Runnable可能无法执行。解决方法是在执行操作前检查视图的可见性和附加状态。
- 多次执行:如果使用View.post多次调度相同的Runnable,可能会导致多次执行。为避免这种情况,请在调度新Runnable之前移除旧的Runnable。
- 视图未初始化:View.post在视图初始化之前可能无法正常工作。解决方法是确保在视图完全初始化之后再调用View.post。
总之,在使用View.post时要注意生命周期问题、视图可见性、多次执行以及视图初始化等潜在问题。了解这些问题并采取相应的解决方法,可以帮助您更有效地使用View.post
view.post()主要有两个作用:更新UI、获取view的实际宽高。
UI控件延迟显示 View.postDelayed()
@SuppressLint("NewApi") public class TestFragment extends DialogFragment { private Button mButton; //默认初始值为true private boolean isSucess = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //NetworkUtil.requestNet是开启了一个异步线程做请求任务 //执行网络请求,根据返回成功与否(true or false) 来设定 button上的文字 //如下为 伪代码 isSucess = NetworkUtil.requestNet(); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View viewLayout = inflater.inflate(R.layout.fragment_test, container); mButton = viewLayout.findViewById(R.id.button2); return viewLayout; } @Override public void onResume() { super.onResume(); //存在bug的代码 /* if (isSucess) { mButton.setText("返回结果成功"); } else { mButton.setText("返回结果失败"); }*/ //可以用 View.postDelayed(Runnable action, long delayMillis)方法来解决此问题 mButton.postDelayed(new Runnable() { @Override public void run() { if (isSucess) { mButton.setText("返回结果成功"); } else { mButton.setText("返回结果失败"); } } }, 1000); //这里延时时间根据网络环境的好坏设置 } }
自定义view中,postDelayed执行失败
private boolean mAttached; @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mAttached = true; } @Override protected void onDetachedFromWindow() { mAttached = false; super.onDetachedFromWindow(); } public void show() { //.... if (mAttached) { postDelayed(mDelayedShow, MIN_DELAY); } else { Handler handler = new Handler(); handler.post(mDelayedShow); } }
优点 :
简化组件之间的通信
体积小
将事件的发送者和接受者分离
在activity fragment 线程之间性能优良
避免复杂且容易出错的依赖关系和生命周期问题
代码简单方便
粘性广播
粘性广播有什么作用?怎么使用? 粘性广播主要为了解决,在发送完广播之后,动态注册的接收者,也能够收到广播。
举个例子首先发送一广播,我的接收者是通过程序中的某个按钮动态注册的。
如果不是粘性广播,我注册完接收者肯定无法收到广播了。
这是通过发送粘性广播就能够在我动态注册接收者后也能收到广播。
EeventBus 粘性事件和普通事件的区别?
StickyEvent与普通Event的普通就在于,EventBus会自动维护被作为StickyEvent被post出来(即在发布事件时使用EventBus.getDefault().postSticky(new MyEvent())方法)的事件的最后一个副本在缓存中。
任何时候在任何一个订阅了该事件的订阅者中的任何地方(可以在任何函数中,而不仅仅是在onEventXXX方法中),都可以通过EventBus.getDefault().getStickyEvent(MyEvent.class)来取得该类型事件的最后一次缓存。
同时,即便事件已经在所有订阅者中传递完成了,如果此时再创建一个新的订阅者(如一个注册了该StickyEvent的Activity),则在订阅者启动后,会自动调用一次该订阅者的noEventXXX方法来处理该StickyEvent。也可以在需要的时候,利用removeStickyEvent方法来移除对某种StickyEvent的缓存。
Android Studio 配置的module的gradle内:
compile 'org.greenrobot:eventbus:3.0.0'
定义一个消息事件 :
public class User { private String name; private String pass; public User(String name, String pass) { this.name = name; this.pass = pass; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPass() { return pass; } public void setPass(String pass) { this.pass = pass; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", pass='" + pass + '\'' + '}'; } }
普通事件(a--->b---值-->a),相当于数据回传
1、注册和取消订阅事件(PuTong1Activity .java):
public class PuTong1Activity extends AppCompatActivity implements View.OnClickListener { private Button putong1_btn; private TextView putong1Text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pu_tong1); initView(); //注册事件 EventBus.getDefault().register(this); } private void initView() { putong1_btn = (Button) findViewById(R.id.putong1_btn); putong1Text = (TextView) findViewById(R.id.putong1Text); putong1_btn.setOnClickListener(this); } @Override public void onClick(View v) { startActivity(new Intent(PuTong1Activity.this,PuTong2Activity.class)); } //事件订阅者处理事件 @Subscribe(threadMode = ThreadMode.MAIN) public void onMoonEvent(User user){ putong1Text.setText(user.getName()+"---接收到的值----"+user.getPass()); } @Override protected void onDestroy() { super.onDestroy(); //取消注册事件 EventBus.getDefault().unregister(this); } }
2、事件发布者发布事件PuTong2Activity 类
public class PuTong2Activity extends AppCompatActivity implements View.OnClickListener { private Button putong2_btn; private EditText nameEdit; private EditText passEdit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pu_tong2); initView(); } private void initView() { putong2_btn = (Button) findViewById(R.id.putong2_btn); nameEdit = (EditText) findViewById(R.id.nameEdit); passEdit = (EditText) findViewById(R.id.passEdit); putong2_btn.setOnClickListener(this); } @Override public void onClick(View v) { String nameStr = nameEdit.getText().toString().trim(); String passStr = passEdit.getText().toString().trim(); //普通时间发送消息给putong1用post方法 EventBus.getDefault().post(new User(nameStr,passStr)); finish(); } }
粘性事件(相当于直接从a—值—>b,与intent跳转传值类似)
1.发送黏性事件(MainActivity .java)
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private EditText nameEdit; private EditText passEdit; private Button login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { nameEdit = (EditText) findViewById(R.id.name); passEdit = (EditText) findViewById(R.id.pass); login = (Button) findViewById(R.id.login); login.setOnClickListener(this); } @Override public void onClick(View v) { String nameStr = nameEdit.getText().toString().trim(); String passStr = passEdit.getText().toString().trim(); if(null==nameStr&&nameStr.equals("")||null==passStr&&passStr.equals("")){ Toast.makeText(this,"用户名密码不能为空",Toast.LENGTH_SHORT).show(); }else{ //2.发送消息粘性事件用postSticky EventBus.getDefault().postSticky(new User(nameStr,passStr)); //跳转 Intent intent = new Intent(MainActivity.this, ResultActivity.class); startActivity(intent); } } }
2.订阅粘性事件(ResultActivity.java)
public class ResultActivity extends AppCompatActivity implements View.OnClickListener { private Button getResult; private TextView text; private String nameStr; private String passStr; //在接收消息的页面 注册EventBus @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_result); initView(); } private void initView() { getResult = (Button) findViewById(R.id.getResult); text = (TextView) findViewById(R.id.text); getResult.setOnClickListener(this); } @Override public void onClick(View v) { //注册EventBus EventBus.getDefault().register(this); } //订阅者处理粘性事件 @Subscribe(threadMode = ThreadMode.MAIN,sticky = true) public void onEventMainThread(User user) { String msg = "账号:" + user.getName()+"---密码:"+user.getPass(); Log.d("ResultActivity", msg); text.setText(msg); Toast.makeText(ResultActivity.this, msg, Toast.LENGTH_LONG).show(); } @Override protected void onDestroy() { super.onDestroy(); //取消注册 EventBus.getDefault().unregister(this); } }