当前位置: 首页 > 科技观察

Android进程与线程总结

时间:2023-03-12 21:45:10 科技观察

本文翻译自Android官方文档Linux进程。默认情况下,同一应用程序下的所有组件都运行在同一进程和线程(通常称为程序的“主”线程)中。如果一个应用组件启动但是应用的进程已经存在(因为应用的其他组件之前已经启动),那么该组件将在进程中启动并在应用的主线程中执行。但是,您也可以让应用程序的组件在单独的进程中运行,并且可以向任何进程添加额外的线程。本文讨论进程和线程如何在Android应用程序中工作。进程默认情况下,同一程序的所有组件都在同一进程中运行,大多数应用程序都是这种情况。但是,如果您发现您的程序中需要某个组件在特定进程中运行,您可以在清单文件中进行设置。清单文件为每个组件元素——提供了一个android:process属性。通过设置此属性,您可以使组件在特定进程中运行。您可以让每个组件在其自己的进程中运行,或者让一些组件共享一个进程而其他组件不共享。您还可以将不同应用程序的组件设置为在同一进程中运行——这允许这些应用程序共享相同的Linux用户ID并通过相同的证书进行身份验证。元素还支持android:process属性。设置该属性允许应用程序中的所有组件默认继承该属性。Android可能会在系统剩余内存不足时关闭一个进程,其他直接为用户服务的进程需要申请内存。这时,这个进程中的组件也会依次被杀死。当这些组件的新任务到达时,它们相应的进程将再次启动。在决定哪些进程需要被杀死时,Android系统会权衡这些进程相对于用户的重要性。例如,与承载不再可见的活动的进程相比,系统将更容易终止承载不再可见的活动的进程。决定是否终止进程取决于进程中组件的运行状态。下面我们讨论一些杀死进程时使用的规则。进程生命周期作为一个多任务系统,Android当然可以尽可能长时间地保留一个应用进程。但是随着更新或更重要的进程需要更多内存,系统不得不逐渐杀死旧进程以获得内存。为了声明哪些进程需要保留,哪些需要杀死,系统会根据这些进程中的组件以及这些组件的状态,为每个进程生成一个“重要性级别”。重要性级别最高的进程会在第一时间被清除,然后重要性越高的进程,依此类推,根据系统的需要终止进程。在这个重要性等级中有5个级别。以下列表按重要性顺序显示了不同类型的进程(第一个进程最重要,因此它会最后被杀死):前台进程与用户交互的进程。如果一个进程处于以下状态之一,那么我们可以称这个进程为前台进程:该进程包含一个与用户交互的Activity(调用这个Activity的onResume()方法)。该流程包含绑定到与用户交互的活动的服务。该进程包含一个在“前台”状态下运行的服务——该服务调用startForeground()方法。一个进程包含一个正在运行的服务及其生命周期回调(onCreate()、onStart()、oronDestroy())。该进程包含一个正在运行onReceive()方法的BroadcastReceiver。一般来说,任何时候系统上只存在少量的前台进程。只有当系统内存紧张到无法继续运行时,系统才会杀掉这些进程来缓解内存压力。这个时候系统必须杀掉一些(一般在那个时候,设备已经达到内存分页状态,这句话怎么翻译比较好)前台进程来保证用户的交互是有响应的。可见进程一个进程没有任何前台组件,但它仍然可以影响屏幕上的显示。如果进程处于以下状态之一,则称为可见进程:该进程包含一个不处于前台状态的Activity,但它仍然对用户可见(它的onPause()方法已被调用)。例如,前台活动可能会启动一个对话框,这使得之前的活动在对话框后面可见。流程由绑定到可见(或前台)活动的服务组成。可见进程在系统中非常重要,只有考虑杀掉可见进程才能使所有前台进程正常运行。Serviceprocess包含一个已经用startService()方法启动的Service,还没有进入到上面两个更高级别类别的进程。服务进程虽然与用户看到的东西没有直接关联,但往往是用来做用户关心的事情(比如后台播放音乐或者下载网络数据),所以系统只会为了保证所有的服务进程而运行foregroundandvisible在进程正常运行时杀掉服务进程。后台进程包含不再可见的活动(已调用活动的onStop())的进程。此类进程不会直接影响用户体验,对于前台进程、可见进程或服务进程,系统可以随时将其杀死。一般来说,系统中有很多后台进程在运行,所以将它们放在一个LRU(最近最少使用)列表中可以保证属于用户最近看到的activity的进程最迟被杀死。如果一个activity正确实现了它的生命周期回调函数并保存了当前状态,那么kill掉这个activity所在的进程并不会影响用户的视觉体验,因为当用户回到这个activity时,它的所有可见状态都会恢复.有关如何保存和恢复状态的更多文档,请参阅活动。空进程不包含任何活动应用程序组件的进程。这种进程存在的唯一原因是缓存。为了提高组件的启动时间,需要让组件在这个进程中运行。为了平衡进程缓存和相关内核缓存的系统资源,系统需要杀死这些进程。Android根据流程中组件的最高可能重要性进行评级。例如,如果一个流程包含一个服务和一个可见的活动,则该流程将被评为可见流程,而不是服务流程。此外,一个进程的评级可能会被附加到它的其他进程提升——服务于其他进程的进程的评级永远不会低于它正在服务的进程。例如,如果进程A中的内容提供者正在为进程B中的客户端提供服务,或者如果进程A中的服务绑定到进程B中的组件,则系统将认为进程A的评级至少更高比进程B.要高。因为进程中运行的服务的等级高于包含后台活动的进程,所以当一个活动开始一个长期操作时,最好启动一个服务来做这个操作,而不是简单地创建一个工作线程——特别是当这种长时间的操作可能会拖累活动时。例如,需要将图像上传到网站的Activity应该启动一个Activity来执行上传。这样即使用户离开了activity,也能保证上传动作在后台继续进行。使用服务确保操作至少是“服务进程”的优先级,无论活动发生什么。这就是广播接收器应该使用服务而不是简单地将耗时操作放在线程中的原因。线程当应用程序启动时,系统会为其创建一个线程,称为“主线程”。该线程很重要,因为它负责处理向相关用户界面小部件发送的事件,包括绘图事件。此线程也是您的应用程序与来自AndroidUI工具包的组件(包括来自android.widget和android.view包的组件)交互的地方。因此,这个主线程有时也被称为UI线程。系统不会为每个组件创建单独的线程。同一个进程中的所有组件都在UI线程中被实例化,每个组件的系统调用都是通过这个线程来调度的。因此,响应系统调用的方法(例如捕获用户操作的onKeyDown()方法或生命周期回调函数)在进程的UI线程中运行。例如,当用户单击屏幕上的按钮时,您的应用程序的UI线程会将单击事件传递给小部件,然后小部件设置其按下状态并向事件队列发送失败请求。UI线程使请求出列并处理它(通知小部件重新绘制自身)。当您的应用程序对用户交互的响应性要求很高时,这种单线程模型可能会产生较差的结果(除非您的应用程序实现良好)。特别是,当应用程序中的所有事情都发生在UI线程中时,那些访问网络数据、数据库查询等长操作会阻塞整个UI线程。当整个线程被阻塞时,任何事件都无法传递,包括绘图事件。从用户的角度来看,应用程序已经死了。更糟糕的是,如果UI线程被阻塞超过几秒(目前为5秒),系统将弹出臭名昭著的“应用程序未响应”(ANR)对话框。此时用户可能会选择退出您的应用程序,甚至卸载它。另外,Android的UI线程并不是线程安全的。所以你不能在工作线程上操作你的UI——你必须在UI线程上操作你的UI。Android单线程模型有两个简单的规则:不要阻塞UI线程,不要在非UI线程中访问AndroidUItoolkitWorker线程。由于上面单线程模型的描述,保证应用界面响应及时,UI线程不阻塞。阻塞变得很重要。如果您不能让您的应用程序中的操作短时间运行,那么您应该确保将这些操作放在单独的线程(“后台”或“工作线程”)中。例如,以下代码在附加线程中下载图像并将其显示在ImageView中:newThread(newRunnable(){publicvoidrun(){Bitmapb=loadImageFromNetwork("http://example.com/image.png");mImageView.setImageBitmap(b);}}).start();}起初这段代码看起来不错,因为它创建了一个新线程来处理网络操作。然而,它违反了单线程模型的第二条规则:不要从非UI线程访问AndroidUI工具包——这个例子在工作线程上修改ImageView。这会导致不可预知的结果并且难以调试。为了解决这个问题,Android提供了几种方法来从非UI线程访问AndroidUI工具包。具体见下面的列表:Activity.runOnUiThread(Runnable)View.post(Runnable)View.postDelayed(Runnable,long)然后,可以使用View.post(Runnable)方法修改之前的代码:publicvoidonClick(Viewv){newThread(newRunnable(){publicvoidrun(){finalBitmapbitmap=loadImageFromNetwork("http://example.com/image.png");mImageView.post(newRunnable(){publicvoidrun(){mImageView.setImageBitmap(位图);}});}}).start();}现在这个方案是线程安全的:在一个单独的线程中完成网络操作后,UI线程就会对ImageView进行操作。然而,随着操作复杂性的增加,代码变得更加复杂和难以维护。为了使用工作线程来处理更复杂的交互,可以考虑在工作线程中使用一个Handler来处理UI线程中的消息。也许最好的解决方案是继承AsyncTask类,它简化了需要与UI交互的工作线程任务的执行。使用AsyncTaskAsyncTask允许您在UI上执行异步操作。它在工作线程中执行一些阻塞操作,然后将结果传递给UI主线程。在此过程中,您不需要处理线程或处理程序。要使用它,您必须扩展AsyncTask并实现doInBackground()回调方法,该方法在后台线程池中运行。如果你需要更新UI,那么你应该实现onPostExecute(),这个方法从doInBackground()中获取结果,并在UI线程中运行它,这样你就可以安全地更新你的UI。您可以通过在UI线程上调用execute()方法来运行此任务。例如,您可以使用AsyncTask实现前面的示例:/**系统调用thisstopperform在worker线程中工作并*传递参数giventoAsyncTask.execute()*/protectedBitmapdoInBackground(String...urls){returnloadImageFromNetwork(urls[0]);}/**系统调用thisstopperform在UI线程中工作并传递*doInBackground()的结果*doInBackground()的结果*/protectedvoidonmapressetView)(ImageresultfromdoInBackground()*/protectedvoidonmapresset)现在UI是安全的,代码也更简单,因为AsyncTask将工作线程中完成的工作与UI线程中完成的工作分开。您应该阅读AsyncTask的参考文档以更好地使用它。下面简要介绍一下AsyncTask的工作原理:您可以指定任务的参数类型、进度值和最终值,使用通用的doInBackground()方法自动执行onPreExecute()、onPostExecute()和onProgressUpdate()方法在UI线程上调用。doInBackground()的返回值将被发送到onPostExecute()方法。你可以随时调用doInBackground()方法中的publishProgress()方法,在UI线程中执行onProgressUpdate()方法。这个任务可以从任何线程取消注意:使用工作线程时你可能遇到的另一个问题是你的活动由于运行时配置更改(例如用户改变屏幕方向)而意外重启,这可能会导致你的工作线程被杀死。解决这个问题可以参考项目Shelves。线程安全的方法在某些情况下,你实现的方法可能会被多个线程调用,所以你必须把它写成线程安全的。正文地址:Android进程与线程