Android中进程与线程

常说的主线程(UI线程)是什么?

当一个Android程序刚启动的时候,我们的android系统就会启动一个带有一个单一线程的linux进程。默认情况下,所有的组件比如Activity都运行在同样的一个进程和线程当中,这个线程就叫做主线程或者UI线程。也就是说,默认情况下,app启动的时候会创建一个线程,这个线程就叫做主线程。因为大部分功能是进行UI上的操作,所有也叫做UI线程。

关于为什么叫主线程请参考:Android 主线程之旅——PSVM(public static void main

让你的组件运行在一个新的进程

一般情况下,同一个Android程序里的所有应用都运行在一个进程当中。但是如果你有需要,你可以在manifest文件当中组件入口配置处进行设置,activity, service, receiver, provider都支持用android:process来设置运行的进程。你也在application里设置全局的android:process属性。

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:process="com.example.yangqiangyu.processandthread1"
android:theme="@style/AppTheme.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> <activity
android:name=".Main2Activity"
android:label="@string/title_activity_main2"
android:theme="@style/AppTheme.NoActionBar" android:process="com.example.yangqiangyu.processandthread2">
</activity>
public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this,Main2Activity.class));
}
}).show();
}
});
}
public class Main2Activity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActivityManager mActivityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> list = mActivityManager.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo info:list){
Log.d("process", info.processName);
}
}

指定了两个Activity的process属性,Main进入Main2,在Main2中输出了运行的所有进程信息:

11-15 23:35:23.454 22628-22628/com.example.yangqiangyu.processandthread2 D/process: com.example.yangqiangyu.processandthread2
11-15 23:35:23.454 22628-22628/com.example.yangqiangyu.processandthread2 D/process: com.example.yangqiangyu.processandthread1
11-15 23:35:23.454 22628-22628/com.example.yangqiangyu.processandthread2 D/process: com.android.launcher

可以看到的确存在了我们设置的进程。

进程的优先级

我们知道,Android系统会根据需要移除一些进程。比如当系统内存不足的时候,会把运行的某些Android程序干掉。那它又是按什么规则去干掉合适的程序呢?这就是进程的优先级。根据进程中的应用组件的重要性要判断干掉哪些程序,让哪些程序继续运行。

下面是官网列出的5个级别,越往后优先级越低,越容易被干掉:

1、前台进程

一般来说,在同一时刻只有一个前台进程存在,前台进程拥有最高的优先级,所以除非在特殊的情况下,比如内存不足完全不能运行程序的时候才会被干掉。(如果你没有遇到过,说明你不是和我一样用的百元Android机,哈哈)下面的情况都被认为是一个前台进程:

  1. 进程中有一个正在和用户交互的Activity时,也就是该Activity调用了onResume方法。

  2. 进程中含有一个与正在和用户交互的Activity绑定了的Service

  3. 进程中含有一个运行”在前台的” Service —–即该Service调用了startForeground()方法。

  4. 进程中含有一个调用了 onCreate(), onStart(), 或者onDestroy()方法中的任意一个方法的Service。

  5. 进程中含有一个调用了onReceive()方法的BroadcastReceiver。

2、可见的进程

即使一个进程没有前台组件,但是如果它能够影响到用户所看到的界面,它就是可见的进程。可见的进程依然很重要,它只有在必需干掉它才能维持前台进程存在的情况下才会被干掉。老大需要资源,做小弟的能不让吗?下面的情况都被认为是一个可见进程:

  1. 当我们学习生命周期的时候打开一个dialog的情况。此时之前的Activity所在的进程就是可见的进程。

  2. 进程中含有一个绑定到一个可见的、或者前台Activity的Service。

3、Service进程

和名字一样,就是一个进程当中有Service在运行的情况,也就是调用了startService() 方法。进程Service没有关联任何东西,用户看不见摸不着。但是它们所做的事情仍然重要(比如在后台放音乐,下数据)。所有系统会在只有内存不足以维持以上两个进程的时候干掉它。那就是老三。

4、后台进程

进程中含有当前不可见的Activity(即调用了onStop()方法),这些进程对用户没太大影响,所以系统可以在任何系统资源不足、前三种需要资源的时候干掉它。但是一般情况下,后台进程都会存在,并且维持着不可见的Activity信息。

5、空进程

一个没有任何活跃的组件的进程就是空进程,它存在的目的是为了缓存。比如让下次启动组件的需要的时间更快一点。最低优先级,没*,不说了。

当上面的列举的情况存在多个的时候,以情况优先级最高的为准。也就是说,当一个进程中存在前台Activity,又有一个运行在后台的Service时,就是前台进程。

线程

当我们的Android应用启动的时候,系统就会创建一个默认的线程,就叫做主线程。关于为什么叫主线程请参考:Android 主线程之旅——PSVM(public static void main

它管理着我们用户界面怎么‘画‘出来,当我们点击屏幕的时候,事件如何分发。所以主线程也叫做UI线程(以前虽然这么叫,但是我不知道为啥)。

由于我们的主线程要做那么多事情,如果此时我们又在它当中做耗时任务,比如网络请求或者数据库查询。就可能会导致主线程阻塞。当主线程阻塞之后,就不能进行事件分发等它本来应该做的事情了。如果阻塞超过5秒,就会出现弹ANR(“application not responding” ) dialog的情况了。

Android Ui线程并不是线程安全的,所以不能在其他线程(工作线程)里操作Ui,你只能在主线程是操作你的UI。

总结:

  • 不能阻塞UI线程

  • 不能非主线程的其他外部线程进行UI操作。

工作线程

由于上面描述的原因,我们在Android中耗时任务必需放在工作线程当中,下面参照一个官网的写个类似的例子,关键代码如下

public void loadImage(View view) {
switch (view.getId()){
case R.id.loadImage:
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadImageFromNetwork("http://e.hiphotos.baidu.com/image/pic/item/b2de9c82d158ccbf0881c1d01dd8bc3eb135411e.jpg");
imageView.setBitmap(bitmap);
break;
}
} private Bitmap loadImageFromNetwork(String imageUrl) {
URL imgUrl = null;
Bitmap bitmap = null;
try {
imgUrl = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection)imgUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
return bitmap;
}

布局文件很简单,一个按钮一个ImageView,在点击按钮的时候在一个新的线程中去执行网络加载,这符合了上面总结的第一点不能阻塞UI线程,我们运行项目后点击按钮发现闪退了,报错信息如下:

Only the original thread that created a view hierarchy can touch its views.

Android中进程与线程

也就是我们之前总结的的第二点,由于主线程不是线程安全的,不能非主线程的其他外部线程进行UI操作。

为了解决上面的问题,Android给我们提供了几种在其他线程中获取主线程的方式:

  • Activity.runOnUiThread(Runnable)

  • View.post(Runnable)

  • View.postDelayed(Runnable, long)

我们可以将上面的imageView.setImageBitmap(bitmap)设置图片改成

image.post(Runnable)

image.postDelayed(Runnable, long)

runOnUiThread(Runnable)的任何一种方式。

修改之后运行,你发现可以正在的获取图片并且显示在ui 界面上了。比如:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap =
loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}

这样就实现了网络在其他线程,而UI操作在主线程了。

Handler和AsyncTask的作用

在上面的代码中,用一个ImageView的 View.post(Runnable)方法,虽然实现了功能,但是如果每个View的操作都要这么写的话,那我们的代码不就太多太难维护了。然而Handler却可以让复杂的UI线程与主线程的交互变得简单。你只需要简单的在其他线程当中用handler发送消息。而AsyncTask也让你能够在合适的地方进行耗时操作,在合适的地方进行UI操作。比如我们可以将上面的代码用handler来处理,整个类的代码如下:

public class MainActivity extends AppCompatActivity {

    private ImageView imageView;
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
switch (message.what){
case 1000:
imageView.setImageBitmap((Bitmap) message.obj);
break;
}
return true;
}
}); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); imageView = (ImageView) findViewById(R.id.imageView);
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId(); //noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
} return super.onOptionsItemSelected(item);
} private Bitmap loadImageFromNetwork(String imageUrl) {
URL imgUrl = null;
Bitmap bitmap = null;
try {
imgUrl = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection)imgUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
return bitmap;
} public void loadImage(View view) {
switch (view.getId()){
case R.id.loadImage:
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadImageFromNetwork("http://e.hiphotos.baidu.com/image/pic/item/b2de9c82d158ccbf0881c1d01dd8bc3eb135411e.jpg");
Message message = new Message();
message.what = 1000;
message.obj = bitmap;
handler.sendMessage(message);
}
}).start();
break;
}
}
}

在执行耗时任务的线程中用handler发送了一条消息,然后在handler的handleMessage方法里面进行了UI操作。

相信你到这里对进程与线程、主线程是什么,为什么不能在其他线程进行UI操作,为什么不能在主线程进行耗时任务等等,关于Handler和AsyncTask的将会在之后具体介绍!

如果觉得对你有用,点个赞或者留个言支持一下,如果有错误请提出,因为我也是一个正在学习的菜鸟。

上一篇:JavaScript形而上的单例模式


下一篇:Android菜鸟的成长笔记(11)——Android中的事件处理