除了最基本的Android应用程序之外,您构建的所有内容都将至少需要使用一些后台线程来执行操作。 这是因为,Android有一些被称为一个ANR(A pplicationÑOTřesponsive)超时,其中当操作需要五个秒内造成或更长的UI线程上,防止用户输入并导致显示的内容对用户是一个悬应用程式。
为了避免这种情况,您必须将运行时间较长的操作(例如网络请求或缓慢的数据库查询)移至其他线程,以防止用户继续使用您的应用程序。 尽管对线程的全面介绍是计算机科学中的一个大而复杂的主题,但是本教程将向您介绍Android中线程的核心概念,以及一些可用的工具来帮助您通过使用后台进程来构建性能更好的应用程序。
了解线程
启动应用程序时,将启动具有单个主执行线程的新Linux进程。 这是可以访问Android UI工具包,侦听用户输入并处理绘制到Android设备屏幕的线程。 因此,它通常也称为UI线程 。
默认情况下,应用程序的所有组件都在同一线程和进程中运行,尽管可以创建其他线程来将任务移出UI线程并阻止ANR。 对于Android中的线程处理,要记住两个简单的规则,以确保应用程序按预期运行:
- 不要阻塞UI线程。
- 不要尝试从UI线程外部访问Android UI组件。
尽管您可以通过简单地创建新的Thread
和Runnable
来遵守第一条规则,但是处理第二条规则会有些棘手。 考虑以下代码片段:
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
mTextView.setText("test");
}
}).start();
虽然这段代码不会使UI线程在ANR超时之后进入Hibernate状态,但尝试设置TextView
文本将导致应用抛出以下错误:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
幸运的是,有一些简单的方法可以解决此问题。 您可以使用Android的runOnUiThread(Runnable)
方法在应用程序的主线程上执行代码。
mTextView = (TextView) findViewById(R.id.text);
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText("test");
}
});
}
}).start();
或者你也可以采取一个标准的View
对象,并post
一个Runnable
它。
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText("test");
}
});
}
}).start();
虽然这两个技巧都将有助于使操作线程安全,但是随着应用程序变得越来越复杂,这将变得难以维护。
异步任务
Android提供的可帮助管理后台线程复杂性的工具之一是AsyncTask
。 AsyncTask
提供了一个用于阻止操作的工作线程,然后使用预先创建的回调方法将结果发布回UI线程,使您可以轻松完成任务,而不必费解线程和处理程序。
AsyncTask生命周期
在开始使用AsyncTask
类之前,您需要了解与在主线程上运行操作相比的生命周期。
AsyncTask
调用的第一个方法是onPreExecute()
。 此方法在UI线程上运行,用于设置需要让用户知道发生了什么的任何接口组件。
在onPreExecute()
完成之后,将doInBackground(T)
。 此处的通用参数是您需要传递给方法以执行其任务的任何信息。 例如,如果要编写一个从URL检索JSON的任务,则可以将该URL作为String
传递给此方法。 当操作在doInBackground()
取得进展时,您可以调用onProgressUpdate(T)
来更新您的UI(例如屏幕上的进度条)。 在这里,泛型是代表进度的值,例如Integer
。
一旦doInBackground()
方法完成,它就可以返回传递到onPostExecute(T)
的对象,例如从初始URL下载的JSONObject
。 onPostExecute(T)
在UI线程上运行。
创建AsyncTask
类时,必须在类声明和上述方法中都覆盖这些泛型。 可以在下面看到一个示例AsyncTask
,它每秒更新一个ProgressBar
:
protected class DemoAsyncTask extends AsyncTask<Integer, Void, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgress.setProgress(0);
mProgress.setVisibility(View.Visible);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
mProgress.setProgress(values[0]);
}
@Override
protected Void doInBackground(Void... params) {
for( int i = 0; i < 100; i++ ) {
try {
Thread.sleep(1000);
} catch( InterruptedException e ) {}
publishProgress(i);
}
return "All done!";
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(aVoid);
if( isCancelled() ) {
return;
}
mProgress.setVisibility(View.GONE);
Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
}
}
您可能已经注意到onPostExecute(T)
检查isCancelled()
。 这是因为AsyncTasks
有一个大问题:即使在Context
被销毁之后,它们仍维护对Context
的引用。
启动AsyncTask
然后旋转屏幕时,最容易看到这种情况。 如果您在原始Context
被销毁后尝试引用Context
项目(例如View
或Activity
),则将引发Exception
。 解决此问题的最简单方法是在Activity
或Fragment
的onDestroy()
方法中的AsyncTask
上调用cancel(true)
,然后验证onPostExecute(T)
尚未取消任务。
与编程中的任何内容一样,何时应该使用AsyncTask
的答案是:它取决于。 尽管AsyncTasks
易于使用,但它们并不是线程解决方案的最终解决方案,并且最适合用于最多持续几秒钟的简短操作。 如果您有可能会持续较长时间的操作,我建议你使用调查ThreadPoolExecutor
, Service
,或GcmNetworkManager
(中的向后兼容版本JobScheduler
)。
服务
当您需要在后台执行长时间运行的操作(例如播放音乐,执行网络交易或与内容提供商进行交互)时,您可能需要考虑使用Service
。 基本Service
可以以两种状态存在:启动状态和有界状态。
启动的Service
由应用程序中的某个组件启动,即使原始组件被销毁,该服务仍在设备的后台处于活动状态。 当启动的Service
正在执行的任务完成时, Service
将自行停止。 标准启动的Service
通常用于长时间运行的后台任务,不需要与应用程序的其余部分进行通信。
绑定的Service
类似于启动的Service
,它还为可以绑定到它的各种应用程序组件提供了回调。 当所有绑定的组件都与Service
解除绑定后,它将停止运行。 这两种方式来运行,应该注意的重要Service
并不是相互排斥的,您可以启动一个Service
,将无限期地运行,并且可以有组件绑定到它。
意图服务
标准Service
的最大问题之一是它不能一次处理多个请求,因为这将是多线程的噩梦。 解决此问题的一种方法是扩展IntentService
,它扩展了标准Service
。 IntentService
创建一个默认工作线程来执行onStartCommand()
中接收的所有意图,因此所有操作都可以在主线程之外进行。 然后,它创建一个工作队列,一次将每个意图发送到onHandleIntent()
,这样您就不必担心多线程问题。
除了处理线程之外,一旦处理了所有启动请求, IntentService
也会自动停止自身。 由于所有实现细节都在IntentService
中处理, IntentService
作为开发人员的工作非常简单。
public class ExampleIntentService extends IntentService {
//required constructor with a name for the service
public ExampleIntentService() {
super("ExampleIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
//Perform your tasks here
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
}
}
结论
在本教程中,您已经学到了很多有关Android中的线程和多线程解决方案的知识。 整本书都是关于Android中的线程编写的,但是您现在应该有足够的基础来编写常规任务,并深入了解针对更复杂的Android应用程序的更深入的文档。
翻译自: https://code.tutsplus.com/tutorials/android-from-scratch-background-operations--cms-26810