默认情况下, 同一个application中的所有component运行在同一个linux进程下. 启动一个component A时, 如果已存在处于运行状态中的component B, 且A和B属于同一个application, 那么component A将在component B所在的进程下运行. 否则将为A创建一个新的linux进程.
开发者也可以为application中的component指定不同的运行进程. manifest.xml文件中的, , , 标签都支持android:PRocess属性, 通过这个属性, 可以为component指定运行的进程. 标签也支持设定android:process属性, 用于为application下的所有component指定默认的运行进程.
当系统的内存不足时, android系统将根据进程优先级选择杀死一些不太重要的进程. 进程优先级从高到低分别为:
前台进程. 以下的进程为前台进程:a. 进程中包含处于前台的正与用户交互的activity;
b. 进程中包含与前台activity绑定的service;
c. 进程中包含调用了startForeground()方法的service;
d. 进程中包含正在执行onCreate(), onStart(), 或onDestroy()方法的service;
e. 进程中包含正在执行onReceive()方法的BroadcastReceiver.
系统中前台进程的数量很少, 前台进程几乎不会被杀死. 只有当内存低到无法保证所有的前台进程同时运行时才会选择杀死某个前台进程.
可视进程. 以下进程为可视进程:a. 进程中包含未处于前台但仍然可见的activity(调用了activity的onPause()方法, 但没有调用onStop()方法). 典型的情况是运行activity时弹出对话框, 此时的activity虽然不是前台activity, 但其仍然可见.
b. 进程中包含与可见activity绑定的service.
可视进程不会被系统杀死, 除非为了保证前台进程的运行而不得已为之.
服务进程. 进程中包含已启动的service.
后台进程. 进程中包含不可见的activity(onStop()方法调用后的activity). 后台进程不会直接影响用户体验, 为了保证前台进程/可视进程/服务进程的运行, 系统随时都有可能杀死一个后台进程. 一个正确的实现了生命周期方法的activity处于后台时被系统杀死, 可以在用户重新启动它时恢复之前的运行状态.
空进程. 不包含任何处于活动状态的进程是一个空进程. 系统经常杀死空进程, 这不会造成任何影响. 空进程存在的唯一理由是为了缓存一些启动数据, 以便下次可以更快的启动.
系统会赋予进程尽可能高的优先级. 例如一个进程既包含已启动的service, 也包含前台activity, 则这个进程会被视为前台进程.
由于组件之间的依赖性, 进程的优先级有可能被提高. 假如进程A服务于进程B, 则进程A的优先级不能低于进程B. 比如, 进程A的ContentProvider组件正在服务于进程B的某个组件, 或者进程A的service组件和进程B的某个组件绑定等, 这些情况下, 进程A的优先级都不会低于进程B(如果按照优先级规则, 进程A的优先级确实低于进程B, 则系统会选择提高进程A的优先级到和进程B相同).
由于服务进程的优先级高于后台进程, 因此如果activity需要执行耗时操作, 最好还是启动一个service来完成. 当然, 在activity中启动子线程完成耗时操作也可以, 但是这样做的缺点在于, 一旦activity不再可见, activity所在的进程成为后台进程, 而内存不足时后台进程随时都有可能被系统杀死(但是启动service完成耗时操作会带来数据交互的问题, 比如耗时操作需要实时更新UI控件的状态的话, service就不是一个好的选择). 基于同样的考虑, 在BroadcastReceiver中也不应该执行耗时操作, 而应该启动service来完成(当然, BroadcastReceiver的生命周期过于短暂, 也决定了不能在其中执行耗时操作).
系统不会为进程中的每一个组件启动一个新的线程, 进程中的所有组件都在UI线程中实例化.
永远要记得:
不要阻塞UI线程. 如果在UI线程中执行阻塞或者耗时操作会导致UI线程无法响应用户请求.
不能在非UI线程(也称为工作线程)中更新UI, 这是因为android的UI控件都是线程不安全的.
由上所述, 开发者经常会启动工作线程完成耗时操作或阻塞操作, 如果需要在工作线程的执行期间更新UI状态, 则应该通知UI线程来进行.
请看下面的代码:
public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }上面的代码是错误的, mImageView.setImageBitmap(b)违反了第二条准则–不能在工作线程中更新UI.
线程间通信可以解决工作线程如何通知UI线程更新控件的问题. android提供了3种线程间通信的方案:
调用以下方法:Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
如果在工作线程中调用了这3个方法, 那么方法中Runnable参数封装的操作会在UI线程中执行.
使用这种方式可以修正例子中的错误之处:
public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { // run方法会在UI线程中执行 public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }Handler机制. Handler机制允许开发者在工作线程中调用与UI线程绑定的handler对象的sendMessage()方法向UI线程的消息队列发送一条消息, UI线程会在适当的时候从消息队列中取出消息并完成处理.
使用AsyncTask类. 创建一个AsyncTask类的子类, 并根据需要选择覆写onPreExecute(), doInBackground(), onProgressUpdate(), onPostExecute()方法.AsyncTask类的具体使用方法请参看文档, 以下是一些大概的说明:
a. AsyncTask类是一个泛型类, 存在3个泛型参数. 第一个参数指定execute方法的参数类型, 第二个参数指定onProgressUpdate()方法的参数类型, 第三个参数指定 doInBackground()方法的返回值类型以及onPostExecute()方法的参数类型.
b. 执行流程: 在UI线程中调用AsyncTask类的execute方法(只有该步骤是由程序员控制的)–>系统调用onPreExecute(), 这个方法在UI线程中执行–>系统调用doInBackground()方法, 这个方法在工作线程中执行–>在doInBackground()方法中每调用一次publishProgress()方法, 就会在UI线程中执行一次onProgressUpdate()方法–>doInBackground()方法执行完成后, 系统将调用 onPostExecute()方法, 并将doInBackground()方法的返回值传递给 onPostExecute()方法的形参. onPostExecute()方法在UI线程中执行.
采用这种方式也可以修正例子中的错误之处:
public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }耗时操作使用多线程, 耗时操作放在UI线程中会导致用户的操作无法得到响应.
阻塞操作使用多线程, 理由同上.
多核CUP的设备使用多线程, 可以有效提高CPU的利用率.
并行操作使用多线程.
Looper类用来创建消息队列. 每个线程最多只能有一个消息队列, android中UI线程默认具有消息队列, 但非UI线程在默认情况下是不具备消息队列的. 如果需要在非UI线程中开启消息队列, 需要调用Looper.prepare()方法, 在该方法的执行过程中会创建一个Looper对象, 而Looper的构造函数中会创建一个MessageQueue instance(Looper的构造函数是私有的, 在Looper类之外无法创建其对象). 此后再为该线程绑定一个Handler instance, 然后调用Looper.loop()方法, 就可以不断的从消息队列中取出消息和处理消息了. Looper.myLoop()方法可以得到线程的Looper对象, 如果为null, 说明此时该线程尚未开启消息队列.
新闻热点
疑难解答