Android Framework-Android进程/线程和程序内存优化

文章详细阐述了Android系统中进程和线程的概念,特别是主线程ActivityThread、Handler、Looper和MessageQueue的角色。每个应用程序至少有三个线程:主线程和两个Binder线程。Handler和Looper协同工作,处理MessageQueue中的消息。此外,文章还介绍了线程的状态、生命周期以及Android应用程序的启动流程,涉及到AMS(ActivityManagerService)和进程的创建过程。
摘要由CSDN通过智能技术生成

Android进程和线程

进程(Process)是程序的一个运行实例,以区别于“程序”这一静态的概念;而线程(Thread)则是CPU调度的基本单位。
Android中的程序和进程具体是什么概念呢?
一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。而main()函数的工作也是千篇一律的。总结如下:
初始化
比如Windows环境下通常要创建窗口、向系统申请资源等。
进入死循环
并在循环中处理各种事件,直到进程退出。
这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。
以AndroidManifest.xml为例:

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com. 
android.launchperf">
<application android:label="Launch Performance">
 <activity android:name="SimpleActivity" android:label="Simple
Activity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
 </activity>

可以看到,Activity的外围有一个名为< application>的标签。换句话说,四大组件只是“application”的“零件”。
自动创建一个activity,然后在onCreate方法打个断点,
在这里插入图片描述
那么当这个Activity启动后,将会生成几个Thread呢(是不是只会有一个主线程)?如图所示。
在这里插入图片描述
一个由向导诞生的、没有任何实际功能的Activity程序,有三个线程,除了有我们最熟悉的main thread(即图中的Thread[<1> main])外,还有两个Binder Thread。主线程由ZygoteInit启动,经由一系列调用后最终才执行Activity本身的onCreate()函数。Zygote为Activity创建的主线程是ActivityThread:

 SamplingProfilerIntegration.start();
 CloseGuard.setEnabled(false);
 Process.setArgV0("<pre-initialized>");
 Looper.prepareMainLooper(); /*只有主线程才能调用这个函数,普通线程应该使用prepare(),
 */
 if (sMainThreadHandler == null) {
 sMainThreadHandler = new Handler(); /*主线程对应的Handler*/
 }
 ActivityThread thread = new ActivityThread(); /*这个main()是static的,因此在这里需要创建一个实例*/
 thread.attach(false); /*Activity是有界面显示的,这个函数将与WindowManagerService
 建立联系。*/Looper.loop(); /*主循环开始*/
 throw new RuntimeException("Main thread loop
unexpectedly exited");/*如果程序运 行到这里,说明退出了上面的Looper循环*/
 }

Service也是寄存于ActivityThread之中的,并且启动流程和Activity基本一致。
启动Service时,也同样需要两个Binder线程的支持。

对于同一个AndroidManifest.xml中定义的四大组件,除非有特别声明(,否则它们都运行于同一个进程中(并且均由主线程来处理事件)。
结论:
1.四大组件并不是程序(进程)的全部,而只是它的“零件”。
2.应用程序启动后,将创建ActivityThread主线程。
3.同一个包中的组件将运行在相同的进程空间中。
4.不同包中的组件可以通过一定的方式运行在一个进程空间中。
5.一个Activity应用启动后至少会有3个线程:即一个主线程和两个Binder线程。

Handler, MessageQueue, Runnable与Looper

概念初探
在这里插入图片描述
Runnable,Message,MessageQueue,Looper和Handler的关系简图
Runnable和Message可以被压入某个MessageQueue中,形成一个集合
在这里插入图片描述
注意,一般情况下某种类型的MessageQueue只允许保存相同类型的Object。图中我们只是为了叙述方便才将它们混放在同一个MessageQueue中,实际源码中需要先对Runnalbe进行相应转换。
Looper循环地去做某件事
比如在这个例子中,它不断地从MessageQueue中取出一个item,然后传给Handler进行处理,如此循环往复。假如队列为空,那么它会进入休眠。
Handler是真正“处理事情”的地方
它利用自身的处理机制,对传入的各种Object进行相应的处理并产生最终结果。
用一句话来概括它们,就是:
Looper不断获取MessageQueue中的一个Message,然后由Handler来处理。
它们各司其职,很像一台计算机中CPU的工作方式::中央处理器(Looper)不断地从内存
(MessageQueue)中读取指令(Message),执行指令(Handler),最终产生结果。
Thread和Handler的关系
在这里插入图片描述
① 每个Thread只对应一个Looper;
② 每个Looper只对应一个MessageQueue;
③ 每个MessageQueue中有N个Message;
④ 每个Message中最多指定一个Handler来处理事件。
由此可以推断出,Thread和Handler是一对多的关系。
Handler的作用:
1.处理Message,这是它作为“处理者”的本职所在。
2.将某个Message压入MessageQueue中。
实现第一个功能的相应函数声明如下:

public void dispatchMessage(Message msg);//对Message进行分发
public void handleMessage(Message msg);//对Message进行处理

Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略来将Message分发给相应的责任人
Handler的第二个功能,相应的功能函数声明如下:

1Post系列:
final boolean post(Runnable r);
final boolean postAtTime(Runnable r, long uptimeMillis);2Send系列:
final boolean sendEmptyMessage(int what);
final boolean sendMessageAtFrontOfQueue(Message msg);
boolean sendMessageAtTime(Message msg, long uptimeMillis);
final boolean sendMessageDelayed(Message msg, long
delayMillis);等等

Post和Send两个系列的共同点是它们都负责将某个消息压入MessageQueue中;区别在于后者处理的函数参数直接是Message,而Post则需要先把其他类型的“零散”信息转换成Message,再调用Send系列函数来执行下一步。
更贴近Android实现的“印象图”
在这里插入图片描述
Looper中包含了一个MessageQueue。下面是一个使用Looper的普通线程范例,名为LooperThread:

class LooperThread extends Thread {
 public Handler mHandler;
 public void run() {
 Looper.prepare();/*一句简单的prepare,究竟做了些什么工作?
*/
 mHandler = new Handler() {
 public void handleMessage(Message msg) {/*处理消息的地方。继承Handler的子类通常需要修改这个函数
*/
 }
 };
 Looper.loop();/*进入主循环*/
 }
 }

这段代码在Android线程中很有典型意义。概括起来只有3个步骤
(1)Looper的准备工作(prepare);
(2)创建处理消息的handler;
(3)Looper开始运作(loop)。
分析一下上边的代码:
首先是:

Looper.prepare();

既然要使用Looper类的函数,那么LooperThread中肯定就得执行如下操作:

import android.os.Looper;

仔细观察,Looper里有个非常重要的成员变量:

static final ThreadLocal<Looper> sThreadLocal = new
ThreadLocal<Looper>();

这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThreadLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即便是同一进程)一概无法访问到它。这从侧面告诉我们,每个线程的Looper都是独立的。

prepare:

private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {/*sThreadLocal.get返回的
是模板类,这个场景中是Looper。
 这个判断保证一个Thread只会有
一个Looper实例存在*/
 throw new RuntimeException("Only one Looper may be
created per thread");
 }
 sThreadLocal.set(new
Looper(quitAllowed));
 }

sThreadLocal的确保存了一个新创建的Looper对象。

接下来创建一个Handler对象,我们单独将它提取出来以方便阅读。

public Handler mHandler;
…
mHandler = new Handler() {
 public void handleMessage(Message msg) {}
};

可见,mHandler是LooperThread的成员变量,并通过new操作创建了一个Handler实例。
Handler有多个构造函数,比如:

public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);

之所以有这么多构造函数,是因为Handler有如下内部变量需要初始化:

final MessageQueue mQueue;
 final Looper mLooper;
 final Callback mCallback;

我们就以LooperThread例子中采用的第一个函数来讲解下它的构造函数:

public Handler() {
 /*…省略部分代码*/
 mLooper =Looper.myLooper();/*还是通过sThreadLocal.get来获取当
前线程中的Looper实例*/
 …
 mQueue= mLooper.mQueue; /*mQueue是Looper与
Handler之间沟通的桥梁*/
 mCallback = null;
}

这样Handler和Looper,MessageQueue就联系起来了

UI主线程——ActivityThread

public static void main(String[] args) {Looper.prepareMainLooper();//和前面的LooperThread不同
 ActivityThread thread = new ActivityThread();//新建一个
ActivityThread对象
 thread.attach(false);
 if (sMainThreadHandler == null) {
 sMainThreadHandler = thread.getHandler();//主Handler
 }
 AsyncTask.init();
 Looper.loop();
 throw new RuntimeException("Main thread loop
unexpectedly exited");
 }

prepareMainLooper和prepare
普通线程只要prepare就可以了,而主线程使用的是prepareMainLooper。
普通线程生成一个与Looper绑定的Handler对象就行,而主线程是从当前线程中获取的Handler(thread.getHandler)
(1)prepareMainLooper

public static void prepareMainLooper() {
 prepare(false);//先调用prepare
 synchronized (Looper.class) {
 if (sMainLooper != null) {
 throw new IllegalStateException("The main Looper
has already been prepared.");
 }
 sMainLooper = myLooper();
 }
 }

可以看到,prepareMainLooper也需要用到prepare。参数false表示该线程不允许退出,这和前面的LooperThread不一样。经过prepare后,myLooper就可以得到一个本地线程的Looper对象,然后将其赋给sMainLooper。从这个角度来讲,主线程的sMainLooper其实和其他线程的Looper对象并没有本质的区别
在这里插入图片描述
这个图描述的是一个进程和它内部两个线程的Looper情况,其中线程1是主线程,线程2是普通线程。方框表示它们能访问的范围,如线程1就不能直接访问到线程2中的Looper对象,但二者都可以接触到进程中的各元素。
线程1:因为是Main Thread,它使用的是prepareMainLooper(),这个函数将通过prepare()为线程1生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样做的目的就是其他线程如果要获取主线程的Looper,只需调用getMainLooper()即可。
线程2:作为普通线程,它调用的是prepare();同时也生成了一个ThreadLocal的Looper对象,只不过这个对象只能在线程内通过myLooper()访问。当然,主线程内部也可以通过这个函数访问它的Looper对象。
(2)sMainThreadHandler。
当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象:

final H mH = new H();

ActivityThread.main中调用的thread.getHandler()返回的就是mH。
也就是说,ActivityThread提供了一个“事件管家”,以处理主线程中的各种消息。
Looper.loop

public static void loop() {
 final Looper me = myLooper();/*loop函数也是静态的,所以它只能访问静态数据。函数myLooper 则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实 就是取出之前prepare中创建的那个Looper对象)*/final MessageQueue queue = me.mQueue;/*正如我们之前所说,Looper中自带一个MessageQueue*/for (;;) {//消息循环开始
 Message msg = queue.next();/*从MessageQueue中取出一个消息,可能会阻塞*/
 if (msg == null) {/*如果当前消息队列中没有msg,说明线程要退出了。类比于上面Windows 伪代码例子中的while判断条件为0,这样就会结束循环*/
 return; /*直接返回,结束程序*/
 }
 …
 msg.target.dispatchMessage(msg); /*终于开始分派消息了,重心就在这里。变量target其实是一个Handler,所以dispatchMessage最终调用的是Handler中的处理函数。*/
 …
 msg.recycle();/*消息处理完毕,进行回收*/
 }
 }

loop()函数的主要工作就是不断地从消息队列中取出需要处理的事件,然后分发给相应的责任人。如果消息队列为空,它很可能会进入睡眠以让出CPU资源。而在具体事件的处理过程中,程序会post新的事件到队列中。另外,其他进程也可能投递新的事件到这个队列中。APK应用程序就是不停地执行“处理队列事件”的工作,直到它退出运行,如图所示。
在这里插入图片描述
在这个模型图中,ActivityThread这个主线程从消息队列中取出Message后,调用它对应的Runnable.run进行具体的事件处理。在处理的过程中,很可能还会涉及一系列其他类的调用(在图中用Object1,
Object2表示)。而且它们可能还会向主循环投递新的事件来安排后续操作。另外,其他进程也同样可以通过IPC机制向这一进程的主循环发送新事件,如触摸事件、按键事件等。这就是APK应用程序能“动起
来”的根本原因。
MessageQueue

/*以下代码还是Looper.java中的,不过只提取出MessageQueue相关的部分
*/
 final MessageQueue mQueue; /*注意它不是static的*/
 private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed); /*new了一个MessageQueue,就是它了。也就是说,当Looper创建时,消息队列也同时会被创建出来*/
 mRun = true;
 mThread = Thread.currentThread();//Looper与当前线程建立对应关系
 }

事实证明Looper内部的确管理了一个MessageQueue,它将作为线程的消息存储仓库,配合Handler、Looper一起完成一系列操作。

Thread类

Thread类的内部原理
 public class Thread implements Runnable {

可以看到,Thread实现了Runnable,也就是说线程是“可执行的代码”:

public interface Runnable {
 public void run();
}

Runnable是一个抽象接口类,唯一提供的方法就是run()。一般情况下,我们是这样使用Thread的:
方法1,继承自Thread
定义一个MyThread继承自Thread,重写它的run方法,然后调用:

MyThread thr = new MyThread();
thr.start();

方法2,直接实现Runnable
Thread的关键就是Runnable,因而下面是另一个常见用法:

new Thread(Runnable target).start();

这两种方法最终都通过start启动,它会间接调用上面的run实现。

 checkNotStarted();
 hasBeenStarted = true;
 VMThread.create(this, stackSize);/*这里是真正创
建一个CPU线程的地方*/
}

在此之前,我们一直都运行在“老线程”中,直到VMThread.create——而实际上真正在新线程中运行的只有Run方法,这解释了上面第二种方法通过传入一个Runnable也可以奏效的原因。从这个角度来理解,Thread类只能算是一个中介,任务就是启动一个线程来运行用户指定的Runnable,而不管这个Runnable是否属于自身,如图5-13所示。
在这里插入图片描述
线程有如下几种状态:
public enum State {
NEW, //线程已经创建,但还没有start
RUNNABLE, //处于可运行状态,一切就绪
BLOCKED, //处于阻塞状态,比如等待某个锁的释放
WAITING, //处于等待状态
TIMED_WAITING, //等待特定的时间
TERMINATED //终止运行
}

Thread休眠和唤醒

1.wait和notify/notifyAll
这3个函数是由Object类定义的——也就意味着它们是任何类的共有“属性”
在这里插入图片描述
当某个线程(比如SystemServer所在线程)调用一个Object(比如BlockingRunnable)的wait方法时,系统就要在这个Object中记录这个请求。因为调用者很可能不止一个,所以可使用列表(见图5-15的
waiting list)的形式来逐一添加它们。当后期唤醒条件(也就是BlockingRunnable执行了run后)满足时,Object既可以使用notify来唤醒列表中的一个等待线程,也可以通过notifyAll来唤醒列表中的所
有线程,如图5-15所示
在这里插入图片描述
2.interrupt
调用一个线程的interrupt的目的和这个单词的字面意思一样,就是“中断”它的执行过程。此时有以下3种可能性。
1.如果Thread正被blocked在某个object的wait上,或者join(),sleep()方法中,那么会被唤醒,中断状态会被清除并接收到InterruptedException。
2.如果Thread被blocked在InterruptibleChannel的I/O操作中,那么中断状态会被置位,并接收到ClosedByInterruptException,此时channel会被关闭。
3.如果Thread被blocked在Selector中,那么中断状态会被置位并且马上返回,不会收到任何exception。
3.join
join方法有如下几个原型:

public final void join ();
public final void join (long millis, int nanos);
public final void join (long millis);

比如:

Thread t1 = new Thread(new ThreadA()); 
Thread t2 = new Thread(new ThreadB()); 
t1.start(); 
t1.join(); 
t2.start();

它希望达到的目的就是只有当t1线程执行完成时,我们才接着执行后面的t2.start()。这样就保证了两个线程的顺序执行。而带有时间参数的join()则多了一个限制,即假如在规定时间内t1没有执行完成,那么我们也会继续执行后面的语句,以防止“无限等待”拖垮整个程序。
4.sleep
wait是等待某个object,而sleep则是等待时间,一旦设置的时间到了就会被唤醒。

Thread状态迁移
在这里插入图片描述
Thread+Handler+Looper的组合实例
BusinessThread

private Thread mBusinessThread = null;
private boolean mBusinessThreadStarted = false;
private BusinessThreadHandler mBusinessThreadHandler = null; 
private void startBusinessThread()
{
 if (true == mBusinessThreadStarted)
 return;
 else
 mBusinessThreadStarted = true;
 mBusinessThread = new Thread(new Runnable()
 {
 @Override
 public void run()
 {
 Looper.prepare();
 mBusinessThreadHandler = new
BusinessThreadHandler();
 Looper.loop();
 }
 });
 mBusinessThread.start();
}

BusinessThread重写了run方法,并使用Looper.prepare和Looper.loop来不断处理调整请求。这些请求是通过mBusinessThreadHandler发送到BusinessThread的消息队列中的。

public class BusinessThreadHandler extendsHandler
{
 public boolean sendMessage(int what, int arg1, int arg2)//重写sendMessage
 {
 removeMessages(what); //清理消息队列中未处理的请求
 return super.sendMessage(obtainMessage(what, arg1,arg2));//发送消息到队列
 }
 public void handleMessage(Message msg)
 {
 switch(msg.what)
 {
 case MSG_CODE:
 //在这里执行耗时操作
 break;
 default:
 break;
 }
 }
};

Android应用程序如何利用CPU的多核处理能力

开发人员如何主动去利用CPU的多核能力,从而有效提高自身应用程序的性能呢?
答案就是针对Java-Based的并行编程技术。
第一种方式就是Java线程,它在Android系统中同样适用。使用上也和Java没有太多区别,我们只要继承Thread类或者实现Runnable接口就可以了。不过采用这类方法有一点比较麻烦的地方,就是和主线程的通信需要通过Message Queue——因为只有主线程才能处理UI相关的事务,包括UI界面更新
另一种可选的并行编程方法是AsyncTask,它是Android开发的专门用于简化多线程实现的Helper类型的类。优点很明显,就是可以不需要通过繁琐的Looper、Handler等机制来与UI线程通信。
AsyncTask在设计时的目标是针对比较短时间的后台操作,换句话说,如果你需要在后台长时间执行某些事务的话,我们建议你还是使用java.util.concurrent包所提供的Executor、ThreadPoolExecutor和FutureTask等其它API接口。
第三种比较常用的“工作线程”实现模型是IntentService。

Android应用程序的典型启动流程

APK类型的应用程,它们通常由两种方式在系统中被启动
1.在Launcher中点击相应的应用程序图标启动
这种启动方式大多是由用户发起的。默认情况下APK应用程序在Launcher主界面上会有一个图标,通过点击它可以启动这个应用程序指定的一个Activity。
2.通过startActivity启动
这种启动方式通常存在于源码内部。比如在Activity1中通过startActivity来启动Activity2。
这两种启动方式的流程基本上是一致的,最终都会调用ActivityManagerService的startActivity来完成。

在这里插入图片描述
无论以什么方式发起一个Activity的启动流程,最终都会调用到AMS的startActivity函数。

如果一切顺利,AMS才会最终尝试启动指定的Activity。如果读者写过APK应用程序,应该清楚Activity的生命周期中除了onCreate,onResume外,还有onPause,onStop等。其中的onPause就是在此时被
调用的——因为Android系统规定,在新的Activity启动前,原先处于resumed状态的Activity会被pause。这种管理方式相比于Windows的多窗口系统简单很多,同时也完全可以满足移动设备的一般需求。将一
个Activity置为pause主要是通过此Activity所属进程的ApplicationThread.schedulePauseActivity方法来完成的。ApplicationThread是应用程序进程提供给AMS的一个Binder通道。

当收到pause请求后,此进程的ActivityThread主线程将会做进一步处理。除了我们熟悉的调用Activity.onPause()等方法外,它还需要通知WindowManagerService这一变化——因为用户界面也需要发生改变。做完这些以后,进程通知AMS它的pause请求已经执行完成,从而使得AMS可以接着完成之前的startActivity操作。

假如即将启动的Activity所属的进程并不存在,那么AMS还需要先把它启动起来。这一步由Process.start实现,它的第一个入参是“android.app.ActivityThread”,也就是应用程序的主线程,然后调用它的main函数。

ActivityThread启动并做好相应初始化工作后,需要调用attachApplication来通知AMS,后者才能继续执行未完成的startActivity流程。具体而言,AMS通过ApplicationThread.scheduleLaunchActivity请求应用程序来启动一个指定的Activity。之后的一系列工作就要依靠应用进程自己来完成,如Activity创建
Window,ViewRootImpl,遍历View Tree等。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值