什么时候使用 this
三种情况下我们使用 this 关键字
1. 区分对象对象属性和局部变量
class A {
private int age;
public void setAge(int age){
this.age = age;
}
}
这里区分外部调用传入的 age 变量和对象的属性age。如果我们对象属性有良好的命名格式区分的话,那就可以不用this。
class A {
private int mAge;
public void setAge(int age){
mAge = age;
}
}
2. 将当前对象作为参数传递给其他方法时
class B {
A.download(this);
}
3. 构造器重载时
class A {
public A(){
this("Constructor");
}
public A (String str){
System.println(str);
}
}
从子线程切换回 UI 线程
1. 使用runOnUiThread 方法
通过 Activity 使用 runOnUiThread 方法来切换回主线程,进行 UI 更新的操作
MainActivity.this.runOnUiThread(new Runnable(){
@Override
public void run(){
//do something here
}
});
内部原理同样是使用 Handler 机制,源码如下:
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
先判断调用此方法的线程是否是 UI 线程,如果是的话,那就直接 run();如果不是,那就用 UI 的Handler 也就是 mHandler 执行这个动作。
mHandler是在 Activity.java 文件中,初始化的操作 mHandler = new Handler() 是在主线程中执行的(即是说,实例化操作是在主线程的 Looper 中执行的),所以他获取到的是主线程的Hanlder。
而 Handler 会隐式关联(implicitly associated)实例化它的Looper,所以使用它来执行的动作的话,就会在主线程队列中执行了。
2. View.post()方法
这个方法实际上原理也是和 runOnUiThread() 方法一样,我们看一下源码:
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
在注释中,我们看到了:
因为 Runnable 已经被添加到 message queue,所以 runnable 将会在 ui 线程运行
这里其实源码中给的是 attachInfo.mHandler.post,那让我们看一下这个attachInfo 是什么意思,然后为什么他的 mHandler 就是和主线程的 looper 相关联的。
attachInfo变量是由 mAttachInfo 赋予的,两者都是 AttachInfo 的实例变量,下面看看一些 AttachInfo 的源码:
/**
* A set of information given to a view when it is attached to its parent
* window.
*/
final static class AttachInfo {
...
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
Context context) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
mTreeObserver = new ViewTreeObserver(context);
}
}
这里我只截取了 AttachInfo 的构造器部分。我们先看最开头的注释:
当 view 附加到它的父窗口时,view 得到的一些信息的集合
然后我们来看构造器的注释:
使用指定的事件 handler 和 线程来创建一个新的信息集合
这个方法就是父节点附加本节点的时候会调用的方法,传入父节点的信息。父节点是 UI,那么他的操作肯定是在 UI 线程操作的,所以这里的 mHandler 传入的也即是与主线程 Looper 相关联的 handler 了。
所以其实这里的原理和第一步里的也是一样的。这里获取了父节点的 handler,然后用它执行动作。(有可能父节点是递归向上获取handler 直到获取 Activity 的handler)
3. 手动使用 Handler
前两者内部原理都是Handler,我们理解了原理之后,其实我们也可以自己实现这么一个操作。并且可以更加灵活。
我们得首先了解一下,如何切换回主线程。我们前面知道的,action是由 hanlder 管理,handler将 action 加入到和 hanlde 相关联的 message queue 中。所以我们切换回主线程操作的关键步骤就是获取主线程的 looper,然后利用这个looper 来创建一个
handler 然后来管理和执行 action。幸而获取主线程的Looper 非常简单,只需要两句:
Handler mHander = new Hander(Looper.getMainLooper());
这样得到的 handler 就是和主线程相关联的 handler 了。
于是我们就可以在子线程切换回主线程并更新 UI:
// 子模块
Handler mHandler = new Handler(Looper.getMainLooper());
mHander.post(new Runnable(){
@Override
public void run() {
// update ui
}
});
是不是超级简单啊。不要觉得这个方法很奇怪哦,AsyncTask内部也是这么写的哦:
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
资料索引:
- Understanding Android Core: Looper, Handler, and HandlerThread
- Understanding Activity.runOnUiThred()
OKHTTP 下载文件频繁切换线程导致性能开销
我在写下载模块的时候,使用 OkHTTP 作为网络框架。下载操作实现基本都差不多,但是实际中遇到的问题是,更新下载进度的时候,CPU 开销飙升到 50%+。
经过排查发现,我的代码中,每当接受到一个 bytes[] 时,就会计算百分比并更新进度条。实际上因为网速良好的情况下,接受字节块几乎是每时每刻都在进行,然后计算出来及时更新,会频繁切换主副线程从而导致 CPU 占用率飙升。
找到问题之后,我的做法是:
- 添加一个变量存储之前的进度值
- 计算出当前进度值之后进行对比,如果进度值相同,那么不更新
为什么进度值会相同呢?因为我们的进度条是 100%,刻度单位是 1%,所以如果进度是在 1 ~ 2 之间的大小的话,他们取整的结果是一样的,而原本的做法是即便一样也会更新,导致了更新频率高的问题。
后面修改了之后,CPU 占用立刻下来了,即便是峰值也不到 25%。应该是正常水平了。
线程池的使用
- 创建线程池之后,可以使用
for循环,在里面执行
资料索引:
接口与回调相关
- 子模块定义接口
- 子模块本身调用时,创建一个接口变量,并在构造器获取一个实例化的接口实现赋予该变量
- 主模块实现实现接口并传入实现好的接口
- 子模块调用,传入参数