前言
一 进程
1 UI组件与进程
默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,如果需要指定某个特定组件所属的进程,则可以利用manifest 文件来达到目的。
manifest文件中的每种组件元素——<activity>、<service>、<receiver>和<provider>——都支持定义android:process属性,用于指定组件运行的进程。设置此属性即可实现每个组件在各自的进程中运行,或者某几个组件共享一个进程而其它组件运行于独立的进程。设置此属性也可以让不同应用程序的组件运行在同一个进程中——实现多个应用程序共享同一个Linux用户ID、赋予同样的权限。
<application>元素也支持android:process属性,用于指定所有组件的默认进程。
如果内存不足,可又有其它为用户提供更紧急服务的进程需要更多内存,Android可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会因此被销毁。当需要再次工作时,会为这些组件重新创建一个进程。
在决定关闭哪个进程的时候,Android系统会权衡它们相对用户的重要程度。比如,相对于一个拥有可见activity的进程,更有可能去关闭一个activity已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。
2 进程的生命周期
Android系统试图尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除过时进程来回收内存。为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进程都划入一个“重要性层次结构”中。重要性最低的进程首先会被清除,然后是下一个最低的,依此类推,这都是回收系统资源所必需的。
重要性层次结构共有5级,以下列表按照重要程度列出了各类进程(第一类进程是最重要的,将最后一个被终止):
(1)前台进程
用户当前操作所必须的进程。满足以下任一条件时,进程被视作处于前台:
a:其中运行着正与用户交互的Activity(Activity对象的onResume()方法已被调用)。
b:其中运行着被正与用户交互的activity绑定的服务Service(这句话可以理解为一个Service,这个Service被绑定在当前正在与用户交互的Activity上,这个Service已经被启动并且正在运行)。
c:其中运行着“前台”服务Service——服务以startForeground()方式被调用。
d:其中运行着正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的服务Service。
e:其中运行着正在执行onReceiver()方法的BroadcastReceiver。
一般而言,任何时刻前台进程都是为数不多的,只有作为最后的策略——当内存不足以维持它们同时运行时——才会被终止。通常,设备这时候已经到了内存分页状态(memory paging state)的地步,终止一些前台进程是为了保证用户界面的及时响应.
(2) 可见进程
没有前台组件、但仍会影响用户在屏幕上所见内容的进程。满足以下任一条件时,进程被认为是可见的:
a:其中运行着不在前台的activity,但用户仍然可见到此activity(onPause()方法被调用了)。比如以下场合就可能发生这种情况:前台activity打开了一个Dialog(对话框),而之前的activity还允许显示在后面。
b:其中运行着被可见(或前台)activity绑定的服务Service。
可见进程被认为是非常重要的进程,除非无法维持所有前台进程同时运行了,它们是不会被终止的。
(3) 服务进程
此进程运行着由startService()方法启动的服务,它不会升级为上述两级别。尽管服务进程不直接和用户所见内容关联,但他们通常在执行一些用户关心的操作(比如在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台、可见进程同时运行,系统会保持服务进程的运行。
(4) 后台进程
包含目前用户不可见activity(Activity对象的onStop()方法已被调用)的进程。这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用。通常会有很多后台进程在运行,所以它们被保存在一个LRU(最近最少使用)列表中,以确保最近被用户使用的activity最后一个被终止。如果一个activity正确实现了生命周期方法,并保存了当前的状态,则终止此类进程不会对用户体验产生可见的影响。因为在用户返回时,activity会恢复所有可见的状态.
(5)空进程
不含任何活动应用程序组件的进程。保留这种进程的唯一目的就是用作缓存,以改善下次在此进程中运行组件的启动时间。为了在进程缓存和内核缓存间平衡系统整体资源,系统经常会终止这种进程。
3 进程的级别规则
依据进程中目前活跃组件的重要程度,Android会给进程评估一个尽可能高的级别。例如:如果一个进程中运行着一个服务和一个用户可见的activity,则此进程会被评定为可见进程,而不是服务进程。
此外,一个进程的级别可能会由于其它进程的依赖而被提高——为其它进程提供服务的进程级别永远不会低于使用此服务的进程。比如:如果A进程中的content provider为进程B中的客户端提供服务,或进程A中的服务被进程B中的组件所调用,则A进程至少被视为与进程B同样重要。
因为运行服务的进程级别是高于后台activity进程的,所以,如果activity需要启动一个长时间运行的操作,则为其启动一个服务service会比简单地创建一个工作线程更好些——尤其是在此操作时间比activity本身存在时间还要长久的情况下。比如,一个activity要把图片上传至Web网站,就应该创建一个服务来执行之,即使用户离开了此activity,上传还是会在后台继续运行。不论activity发生什么情况,使用服务可以保证操作至少拥有“服务进程”的优先级。同理,上一篇中的广播接收器broadcast receiver也是使用服务而非线程来处理耗时任务的。
二 线程
1 android中的UI线程
应用程序启动时,系统会为它创建一个名为“main”的主线程。主线程非常重要,因为它主要负责处理与UI相关的事件,例如用户的按键事件、用户的触摸事件,屏幕绘图事件,并且把相关的事件分发给相应的组件进行处理。它也是应用程序与Android UI组件包(来自android.widget和android.view包)进行交互的线程。因此,主线程有时也被叫做UI线程。
系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在UI线程中实例化的,对每个组件的系统调用也都是由UI线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的onKeyDown()或生命周期回调方法)总是运行在UI线程中。举个例子,当用户触摸屏幕上的按钮时,应用程序的UI线程把触摸事件分发给widget,widget先把自己置为按下状态,再发送一个显示区域已失效(invalidate)的请求到事件队列中。UI线程从队列中取出此请求,并通知widget重绘自己。如果应用程序在与用户交互的同时需要执行繁重的任务,单线程模式可能会导致运行性能很低下,除非应用程序的执行时机刚好很合适。如果UI线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的ANR异常。如果引起用户不满,他可能就会决定退出并删除这个应用程序。
此外,Andoid的UI组件包并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能会出现线程安全问题。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:
a:不要阻塞UI线程(也就是说不能在UI线程中不能进行耗时操作)。
b:不要在UI线程之外访问Andoid的UI组件包(也就是说Android平台只允许在UI线程中修改UI组件)。
2 线程分类
3 线程优先级
二 线程通信
1 runOnUiThread
new Thread(new Runnable() { @Override public void run() { //从网络下载图片资源 final Bitmap bitmap = loadImage(); ThreadTestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { if (bitmap != null){ imgThread.setImageBitmap(bitmap); } } }); } }).start();
//声明消息机制并初始化它 final Handler mHandler = new Handler(); public final void runOnUiThread(Runnable action) { //如果当前线程不是UI线程的话,就通过mHandler把action发送到UI线程进程处理 if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long)
2 Handle机制
new Thread(new Runnable() { @Override public void run() { final Bitmap bitmap = loadImage(IMG_URL); //处理方式二 //发送请求结果 Message message = handler.obtainMessage(); message.what = 21; message.obj = bitmap; handler.sendMessage(message); } }).start();
private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 21://处理请求结果 img_handle.setImageBitmap((Bitmap) msg.obj); break; } } };
3 异步任务
异步任务AsyncTask允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在UI线程中呈现结果,在此过程中不需要对线程和handler进行人工干预。在doInBackground()方法中处理耗时操作,在onPostExecute方法中对请求结果进行处理。
实现源码(这是部分代码,完整代码点击参考):
//处理方法三 private class DownloadImageTask extends AsyncTask<String, Void, Bitmap>{ //在这个方法中处理耗时任务 @Override protected Bitmap doInBackground(String... params) { String imgUrl = params[0]; if (TextUtils.isEmpty(imgUrl)){//如果imgUrl 为null 则解析默认图片 return BitmapFactory.decodeResource(ThreadTestActivity.this.getResources(), R.mipmap.ic_launcher); } return loadImage(imgUrl); } //在这个方法中对请求结果进行处理 @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if (bitmap != null){ img_async.setImageBitmap(bitmap); } } }
//处理方式三 不要和上面的同时使用 new DownloadImageTask().execute(IMG_URL);
三 进程间通讯
四 多线程
1多线程理论
2 多线程利弊
3 进程与线程的关系
这个问题很经典,无数面试官想从这个问题得到你对线程和进程的理解程度,想完全讲清楚有点儿难度,同时也需要篇幅。从程序开发角度来讲,进程是资源分配的基本单位,是一个程序或者服务的基本单位。我们可以说进程就是程序的执行过程,这个过程包括很多东西,如CPU执行时间、运行内存、数据等,而且是一个动态的过程。线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。总结一下二者关系:
a.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
b.资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
c.处理机分给线程,即真正在处理机上运行的是线程。
d.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4 Java多线程实现基本方式
(1)实现Runnable
public class MyThread implements Runnable{ @Override public void run() { for(int i=0; i<20; i++){ System.out.println(Thread.currentThread().getName()+"-"+i); } } public static void main(String[] args) { Thread t1 = new Thread(new MyThread()); Thread t2 = new Thread(new MyThread()); t1.start(); t2.start(); } }
(2)继承Thread
public class MyThread extends Thread{ @Override public void run() { for(int i=0; i<20; i++){ System.out.println(Thread.currentThread().getName()+"-"+i); } } public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } }