我们知道,Android 是基于 Linux 系统的,Linux 拥有“多用户、多任务、多线程”的特性,所以 Android 也有这个特性。
Android 系统中的每一个 App 都是一个不同的用户,App 安装之后,系统会为其分配一个用户 ID,并新创建一个进程,并创建一个虚拟机来运行该应用(Android 5.0 之前是 Dalvik 虚拟机,之后是 ART),也就是说,每个 App 的运行环境都是独立的。Android 会在需要执行任何应用组件时启动该进程,然后在不再需要该进程或系统必须为其他应用恢复内存时关闭该进程。
默认情况下,我们的应用只能访问自己的工作进程内的组件,不能访问其他进程内的组件,这样也就达到了安全的目的,有以下方法可以访问其他应用或者系统服务:
默认情况下,我们 App 的组件都是运行在同一进程内的,但是我们可以为组件指定新的进程,,我们可以在清单文件中的 <activity>
、<service>
、<receiver>
和 <provider>
中直接指定 android:process
属性为该组件设置单独的进程,也可以在 <application>
标签中设置该属性,用于为所有组件设置共同的特定进程:
当系统内存不足又需要为其他应用开启进程时,系统会选择性的关闭一些进程,那么进程中运行的组件也会随之销毁,销毁的顺序按照其重要程度决定,重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程):
通常情况下,前台进程都不会太多,只有在内存严重不足,不足到满足不了他们同时存在的情况下,才会终止他们来确保一些前台进程来确保用户界面的正常交互。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
应用在启动时,系统会创建一个名为“主线程”的执行线程,该线程主要主要负责界面的绘制、事件传递响应,有时候,该线程也称为“UI 线程”。
主线程之外的线程被称之为“工作线程”。
我们知道主线程用于绘制界面、事件传递响应,假如在主线程内执行耗时操作,譬如下载操作、大数据 I/O,则会阻塞 UI 线程,从而导致界面无法绘制、事件无法传递,Android 系统会弹出“ANR”提示(Application Not Responding,程序无响应)。
此外,Android UI 工具包并非线程安全工具包,所以,我们不可以通过工作线程去操作 UI,只能通过 UI 线程去操作。因此,Android 的单线程模式必须遵守两条规则:
那如果我们需要从网上下载图片,然后再设置到 UI 上呢?Android 提供了以下几种方法:
示例一:
public class MainActivity extends Activity {
private Button button;
private ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
image = findViewById(R.id.image);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
URL url = new URL("http://pic.yesky.com/uploadImages/2015/019/31/C6IQ4C17849F_200x0.png");
URLConnection connection = url.openConnection();
InputStream fis = connection.getInputStream();
FileOutputStream fos = openFileOutput("girl.png", Context.MODE_PRIVATE);
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
fis.close();
fos.close();
String path = getApplicationContext().getFilesDir().getAbsolutePath() + "/girl.png";
Bitmap bitmap = BitmapFactory.decodeFile(path);
image.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
代码很简单,点击按钮,从网上下载一张图片,然后设置给 ImageView,但是在上面例子中,点击按钮会程序崩溃抛出异常:
android.os.NetworkOnMainThreadException
顾名思义,主线程里执行了网络操作,修改一下代码:
public class MainActivity extends Activity {
private Button button;
private ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
image = findViewById(R.id.image);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
URL url = null;
try {
url = new URL("http://pic.yesky.com/uploadImages/2015/019/31/C6IQ4C17849F_200x0.png");
URLConnection connection = url.openConnection();
InputStream fis = connection.getInputStream();
FileOutputStream fos = openFileOutput("girl.png", Context.MODE_PRIVATE);
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
fis.close();
fos.close();
String path = getApplicationContext().getFilesDir().getAbsolutePath() + "/girl.png";
final Bitmap bitmap = BitmapFactory.decodeFile(path);
image.post(new Runnable() {
@Override
public void run() {
image.setImageBitmap(bitmap);
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
}
这下就可以执行成功了~新开了线程去下载图片,然后调用 View 的 post 方法,在 UI 线程中更新设置图片。
但是这样写是不是很麻烦?Android 提供了 Handler 机制来帮助我们完成类似这种异步任务,也可以使用 AsyncTask