Android进程与线程详解

Posted ayanwan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进程与线程详解相关的知识,希望对你有一定的参考价值。

        相信很多人读过Google android 推广工程师的《Who lives and who dies? Process priorities on Android》,该篇仅仅详细介绍了进程级别,但是没有对android的进程进行详细的介绍,而且其他很多文章也都将重点放在了四大组件、UI等方面。因此,本文将先从进程的角度,将进程相关的知识点进行一个串烧,再从主线程入手,对线程进行详解。


1、进程

       每个App在启动前必须先创建一个进程,该进程是由Zygote fork出来的,进程具有独立的资源空间,用于承载App上运行的各种Activity/Service等组件。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。


1.1 进程的创建

        不论是startActivity(),还是startService()等接口启动的进程,最终都是调用了ActivityManagerService 的 startProcessLocked() 方法,进而调用 Process.start() 方法。在该方法中,主要做了两件事:

public final class ActivityManagerService extends ActivityManagerNative  
                            implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback   
  
    private final void startProcessLocked(ProcessRecord app,  
                String hostingType, String hostingNameStr)   
  
        try   
            int pid = Process.start("android.app.ActivityThread",  
                            mSimpleProcessManagement ? app.processName : null, uid, uid,  
                            gids, debugFlags, null);  
  
            if (pid == 0 || pid == MY_PID)   
        
  
             else if (pid > 0)   
                app.pid = pid;  
                app.removed = false;  
                synchronized (mPidsSelfLocked)   
                    this.mPidsSelfLocked.put(pid, app);  
                    ......  
                  
             else   
                ......  
              
         catch (RuntimeException e)   
            ......  
          
      
    ......  
  
  

(1)新建一个进程;

startViaZygote

      zygoteSendArgsAndGetResult

       这个方法的主要功能是通过socket通道向Zygote进程发送一个参数列表,然后进入阻塞等待状态,直到远端的socket服务端发送回来新创建的进程pid才返回。既然system_server进程通过socket向Zygote进程发送消息,这是便会唤醒Zygote进程,来响应socket客户端的请求(即system_server端),接下来的操作便是在Zygote进程中执行。

       先执行 runSelectLoop。

public static void main(String argv[]) 
    try 
        runSelectLoop(abiList);
        ....
     catch (MethodAndArgsCaller caller) 
        caller.run(); 
     catch (RuntimeException ex) 
        closeServerSocket();
        throw ex;
    

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller 
    ...

    ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
    while (true) 
        for (int i = pollFds.length - 1; i >= 0; --i) 
            //采用I/O多路复用机制,当客户端发出连接请求或者数据处理请求时,跳过continue,执行后面的代码
            if ((pollFds[i].revents & POLLIN) == 0) 
                continue;
            
            if (i == 0) 
                //创建客户端连接
                ZygoteConnection newPeer = acceptCommandPeer(abiList);
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
             else 
                //处理客户端数据事务 
                boolean done = peers.get(i).runOnce();
                if (done) 
                    peers.remove(i);
                    fds.remove(i);
                
            
        
    
       没有连接请求时会进入休眠状态,当有创建新进程的连接请求时,唤醒Zygote进程,创建Socket通道ZygoteConnection,然后执行ZygoteConnection的runOnce()方法。

runOnce

      forkAndSpecialize

      handleChildProc

其中,forkAndSpecialize调用关系如下,主要完成进程的创建工作。

Zygote.forkAndSpecialize
	ZygoteHooks.preFork
		Daemons.stop
		ZygoteHooks.nativePreFork
			dalvik_system_ZygoteHooks.ZygoteHooks_nativePreFork
				Runtime::PreZygoteFork
					heap_->PreZygoteFork()
	Zygote.nativeForkAndSpecialize
		com_android_internal_os_Zygote.ForkAndSpecializeCommon
			fork()
			Zygote.callPostForkChildHooks
				ZygoteHooks.postForkChild
					dalvik_system_ZygoteHooks.nativePostForkChild
						Runtime::DidForkFromZygote
	ZygoteHooks.postForkCommon

接下来,新创建的App进程便进入handleChildProc()方法

private void handleChildProc(Arguments parsedArgs,
        FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
        throws ZygoteInit.MethodAndArgsCaller 

    //关闭Zygote的socket两端的连接
    closeSocket();
    ZygoteInit.closeServerSocket();

    if (descriptors != null) 
        try 
            Os.dup2(descriptors[0], STDIN_FILENO);
            Os.dup2(descriptors[1], STDOUT_FILENO);
            Os.dup2(descriptors[2], STDERR_FILENO);
            for (FileDescriptor fd: descriptors) 
                IoUtils.closeQuietly(fd);
            
            newStderr = System.err;
         catch (ErrnoException ex) 
            Log.e(TAG, "Error reopening stdio", ex);
        
    

    if (parsedArgs.niceName != null) 
        //设置进程名
        Process.setArgV0(parsedArgs.niceName);
    

    if (parsedArgs.invokeWith != null) 
        //据说这是用于检测进程内存泄露或溢出时场景而设计,后续还需要进一步分析。
        WrapperInit.execApplication(parsedArgs.invokeWith,
                parsedArgs.niceName, parsedArgs.targetSdkVersion,
                VMRuntime.getCurrentInstructionSet(),
                pipeFd, parsedArgs.remainingArgs);
     else 
        //执行目标类的main()方法 
        RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                parsedArgs.remainingArgs, null);
    

public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
        throws ZygoteInit.MethodAndArgsCaller 

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
    redirectLogStreams(); //重定向log输出
    commonInit(); // 通用的一些初始化
    nativeZygoteInit(); // zygote初始化 
    applicationInit(targetSdkVersion, argv, classLoader); // 应用初始化

总结一下,调用关系如下:

handleChildProc
       zygoteInit
       nativeZygoteInit
       applicationInit
                invokeStaticMain //调用startClass的static方法 main()

invokeStaticMain(args.startClass, args.startArgs, classLoader);此处args.startClass为”android.app.ActivityThread”。

invokeStaticMain()方法中抛出的异常MethodAndArgsCaller,根据前面的(1)中可知,下一步进入caller.run()方法。

public static class MethodAndArgsCaller extends Exception
        implements Runnable 

    public void run() 
        try 
            //根据传递过来的参数,可知此处通过反射机制调用的是ActivityThread.main()方法
            mMethod.invoke(null, new Object[]  mArgs ); 
         catch (IllegalAccessException ex) 
            throw new RuntimeException(ex);
         catch (InvocationTargetException ex) 
            Throwable cause = ex.getCause();
            if (cause instanceof RuntimeException) 
                throw (RuntimeException) cause;
             else if (cause instanceof Error) 
                throw (Error) cause;
            
            throw new RuntimeException(ex);
        
    

(2)导入android.app.ActivityThread这个类,然后执行它的main函数。

public final class ActivityThread   
      
    ......  
  
    public static final void main(String[] args)   
  
        ......  
  
        Looper.prepareMainLooper();  
      
        ......  
  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
  
        ......  
  
        Looper.loop();  
  
        ......  
  
        thread.detach();  
      
        ......  
      
  

        在Android应用程序中,每一个进程对应一个ActivityThread实例,所以,这个函数会创建一个thread实例,然后调用ActivityThread.attach函数进一步处理。最后,调用Looper.prepareMainLooper(),这是为主线程创建了Looper,然后调用thread.getHandler(),这是保存了主线程的Handler,最后调用Looper.loop()进入消息循环。可以参考Activity与AMS


1.2 进程资源

           由以上分析可知,andriod进程是由zygote进程fork出的,然后调用了ActivityThread的main方法,创建了主线程,最后进入Loop回环。简单的说,一个原始的android进程拥有:一个主线程,一个Lpooer,一个zygote,一个VM。


1.3 进程的等级

(1)前台进程 

     用户当前正在做的事情需要这个进程。如果满足下面的条件之一,一个进程就被认为是前台进程:

这个进程拥有一个正在与用户交互的Activity(这个Activity的onResume()方法被调用)。

这个进程拥有一个绑定到正在与用户交互的activity上的Service。

这个进程拥有一个前台运行的Service(service调用了方法startForeground()).

这个进程拥有一个正在执行其任何一个生命周期回调方法(onCreate(),onStart(),或onDestroy())的Service。

这个进程拥有正在执行其onReceive()方法的BroadcastReceiver。

(2)可见进程

一个进程不拥有运行于前台的组件,但是依然能影响用户所见。满足下列条件时,进程即为可见:

这个进程拥有一个不在前台但仍可见的Activity(它的onPause()方法被调用)。当一个前台activity启动一个对话框时,就出了这种情况。

(3)服务进程

一个可见进程被认为是极其重要的。并且,除非只有杀掉它才可以保证所有前台进程的运行,否则是不能动它的。

这个进程拥有一个绑定到可见activity的Service。

一个进程不在上述两种之内,但它运行着一个被startService()所启动的service。

尽管一个服务进程不直接影响用户所见,但是它们通常做一些用户关心的事情(比如播放音乐或下载数据),所以系统不到前台进程和可见进程活不下去时不会杀它。

(4)后台进程

一个进程拥有一个当前不可见的activity(activity的onStop()方法被调用)。

这样的进程们不会直接影响到用户体验,所以系统可以在任意时刻杀了它们从而为前台、可见、以及服务进程们提供存储空间。通常有很多后台进程在运行。它们被保存在一个LRU(最近最少使用)列表中来确保拥有最近刚被看到的activity的进程最后被杀。如果一个activity正确的实现了它的生命周期方法,并保存了它的当前状态,那么杀死它的进程将不会对用户的可视化体验造成影响。因为当用户返回到这个activity时,这个activity会恢复它所有的可见状态。

(5)空进程

一个进程不拥有入何active组件。保留这类进程的唯一理由是高速缓存,这样可以提高下一次一个组件要运行它时的启动速度。系统经常为了平衡在进程高速缓存和底层的内核高速缓存之间的整体系统资源而杀死它们。


1.4 进程间通信

        在Android中,Binder用于完成进程间通信(IPC),即把多个进程关联在一起。比如,普通应用程序可以调用音乐播放服务提供的播放、暂停、停止等功能。
        Binder工作在Linux层面,属于一个驱动,只是这个驱动不需要硬件,或者说其操作的硬件是基于一小段内存。从线程的角度来讲,Binder驱动代码运行在内核态,客户端程序调用Binder是通过系统调用完成的。

        除了binder之外,还有socket等进程通信方式。下图是binder通信的简单调用实例。Activity通过bindService()启动binder,Service接到通知后进行相应,并将处理结果返回给Activity。详细内容参考binder框架解析


2 线程 

       一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。

      在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

2.1 子线程更新UI

        Android的UI是单线程(Single-threaded)的。为了避免拖住GUI,一些较费时的对象应该交给独立的线程去执行。如果幕后的线程来执行UI对象,Android就会发出错误讯息CalledFromWrongThreadException。

2.2 Message Queue

       在单线程模型下,为了解决类似的问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:
1. Message
       Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。
2. Handler
      Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。
3. Message Queue
      Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
      每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
4. Looper
     Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper()得到当前线程的Looper就有可能为NULL。

2.3 AsyncTask

       Android另外提供了一个工具类:AsyncTask。它使得UI thread的使用变得异常简单。它使创建需要与用户界面交互的长时间运行的任务变得更简单,不需要借助线程和Handler即可实现。

1) 子类化AsyncTask
2) 实现AsyncTask中定义的下面一个或几个方法
onPreExecute() 开始执行前的准备工作;
doInBackground(Params...) 开始执行后台处理,可以调用publishProgress方法来更新实时的任务进度;
onProgressUpdate(Progress...) 在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result) 执行完成后的操作,传送结果给UI 线程。

这4个方法都不能手动调用。而且除了doInBackground(Params...)方法,其余3个方法都是被UI线程所调用的,所以要求:
1) AsyncTask的实例必须在UI thread中创建;
2) AsyncTask.execute方法必须在UI thread中调用;

同时要注意:该task只能被执行一次,否则多次调用时将会出现异常。而且是不能手动停止的,这一点要注意。



以上是关于Android进程与线程详解的主要内容,如果未能解决你的问题,请参考以下文章

Android面试收集录7 AsyncTask详解

Android中的线程状态 - AsyncTask详解

Android 进程和线程详解转

Python进程线程协程详解

Java并发编程Thread详解

进程线程与GIL全局解释器锁详解