围绕Android中的进程与线程做了简要的概述。
进程
进程保活的方式:这篇文章
Foreground process
- 有一个Activity且它
- 正在交互
- 有一个Service且它
- 绑定到正在交互的Activity
- “前台运行”,startForeground()
- 正在执行生命周期回调 onCreate() onStart() onDestroy()
- 有一个BroadcastReceiver且它
- 正在执行onReceive()
Visible process
- 有一个Activity且它
- 不在交互,但仍可见
- 有一个Service且它
- 绑定到可见Activity
Service process
- 普通Service
故而对于耗时的比如上传等,新建一个Service是比在activity中新建一个线程好得多的。
Background process
- 所有Activity都对用户不可见
会被保存在LRU列表中,即最近查看的最晚被终止
Empty process
系统有时候会使用空进程做为缓存,以缩短下一次在其中运行组建所需的启动时间。
额外说明
若一个进程A依赖于另一个进程B,则进程B的优先级可能会被提升并保证B的优先级高于A顶优先级。
举例 (A优先级永远高于B):
- A中的ContentProvider提供数据给B
- A中的某个service绑定到B的某个组件
进程间通信
IPC InterProcess Communication
RPC Remote Procedure Call
Android默认每个app是一个进程,但也可以通过android:process属性使每个app有多个进程,或者多个app共享某个进程。
多进程的特性
- 不同的内存空间,数据无法共享
- 需要谨慎处理代码中的线程同步
- 需要提防多进程并发导致的文件所和数据库锁实效的问题
进程间通信方式
摘自任玉刚的《把玩Android多进程》ppt
Screen Shot 2017-01-07 at 16.37.37
线程
Android中的线程
Android系统基于精简过后的linux内核,Linux系统的调度器在分配time slice的时候,采用的CFS(Completely fair scheduler)策略,不仅会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量。优先级高的线程不一定能在争取timeslice上有绝对的优势。
Android将进程分为多个group,其中有两种比较重要:
- default group
- 能获得绝大部分的timeslice(UI线程就属于此列)
- background group
- 工作线程,最多被分配10%的timeslice
其中background group需要开发者显示的归位(官方建议)
new Thread(new Runnable(){
@Override
public void run(){
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// do sth
}
}).start();
UI / Main thread
系统启动时创建的线程,用来处理页面的绘制。
不可以把耗时操作放在ui线程中,如网络请求、数据库的读写等等,阻塞超过5s会发生ANR错误。
因为Android UI toolkit不是线程安全的,故而所有的页面绘制都必须放在UI线程中做。
有个黑科技是可以在Activity的onResume()前使用非UI线程绘制UI,因为检测线程是否是UI线程是在ViewRootImpl中进行检测的,而ViewRootImpl是在onResume()时才会进行初始化的
仅限了解,请勿在实际项目中尝试。
在非UI线程中更新UI
简单情况
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable,long)
- handler
- AsyncTask
Handler介绍
Screen Shot 2017-01-07 at 17.25.19
主要分为三个部分:
- Looper
- 工人,完成MessageQueue里面的任务
- MessageQueue
- 任务队列,采用FIFS
- Handler
- 将消息放入MessageQueue
注意点 :
Can’t create handler inside thread that has not called Looper.prepare()
handler所在的线程必须调用过Looper.prepare方法,否则没有looper来进行工作
public static final void prepare(){
if(sThreadLocal.get()!=null){
throw new RuntimeException("Only one Looper may be created per thread");
}
//关于ThreadLocal的介绍,放在文章末尾,此处可以理解为一个referfence,可以set和get
sThreadLocal.set(new Looper());
}
所以需要在新开的线程中显示调用Looper.prepare()方法,否则无法在此线程中新建handler(不然sThreadLocal中是取不到looper来进行更新操作的)
Android中有一个现成的类HandlerThread,可以方便使用
HandlerThread handlerThread = new HandlerThread("thread_name");
handlerThread.start();
//MyHandler extends android.os.Handler
mHandler = new MyHandler(handlerThread.getLooper());
Thread类
- 启动了新的线程,没有任务的概念,不能做状态的管理。
- start之后,run当中的代码就一定会执行到底,中途无法取消。
- 作为匿名内部类持有了外部类的引用,在线程退出之前,会阻碍GC的回收,在一段时间内造成内存泄露
- 没有线程切换的接口,要传递处理结果到UI线程,需要些额外的线程切换代码
- 如果从UI线程启动,该线程优先级默认为Default
AsyncTask
重写方法:
- doInBackground(Params…)
- 在后台线程执行,返回Result,执行过程中调用publicProgress(Progress)来进行任务进度的更新
- onPostExecute(Result)
- 在UI线程中执行
- onProgressUpdate(Progress…)
- 在主线程执行,在publishProgress方法调用后执行
在不同的系统版本上串行与并行的执行行为不一致
必须遵守的规则:
- Task实例必须在UI线程中创建
- execute方法必须在UI线程中调用
- 该Task只能被执行一次,多次调用时会出现异常
- 不要手动调用onPreExecute….等生命周期方法,使用publishProgress()更新进度
ThreadPoolExecutor
Thread、AsyncTask适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务时,ThreadPoolExecutor是更好的选择。
提供复用机制(线程池)
public static Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
//execute
THREAD_POOL_EXECUTOR.execute(Runnable XX);
代价
- 每一个新线程至少消耗64kb内存
- 线程的切换回带来额外开销(switch context)
- 尽量服用已有的工作线程
ThreadLocal
很多地方出现这个东西,其实它是一个容器,用来存放线程的静态局部变量,保证每一个线程都拥有单独的静态成员变量,保证了线程安全。
ThreadLocal 可以近似的认为是Map<Thread,T>,它的get方法就是以当前线程为key去map中取对应的T
ThreadLocal为每一个线程提供了一个独立的副本。
Sample
比如说下面这段代码为每个线程创建一个计数器,这时使用不同的线程获得的number就不同。
public interface Sequence{
int getNumber();
}
public class SequenceByThreadLocal implements Squence{
private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 0;
}
};
public int getNumber(){
numberContainer.set(numberContainer.get()+1);
return numberContainer.get();
}
}
一个简易实现
其实这个数据结构很简单,可以用代码做一个简易的实现
public class MyThreadLocal<T>{
private Map<Thread,T> container = Collections.synchronizedMap(new HashMap<Thread,T>());
public void set(T vaule){
container.put(Thread.currentThread(),value);
}
public T get(){
Thread thread = Thread.currentThread();
T value = container.get(thread);
if(value == null && !container.containsKey(key){
valuee = initValue();
container.put(thread,value);
}
return value;
}
public void remove(){
container.put(Thread.currentThread());
}
protected T initialValue(){
return null;
}
}