zoukankan      html  css  js  c++  java
  • 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.
    这里写图片描述
    也就是我们之前总结的的第二点,由于主线程不是线程安全的,不能非主线程的其他外部线程进行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的将会在之后具体介绍!

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

  • 相关阅读:
    随机生成30到四则运算题目2 (修改)
    随机生成30到四则运算题目2
    随机生成30道四则运算题目
    第一周学习进度表
    构建之法阅读笔记01
    个人简介
    个人简介
    bat 延时删除指定文件夹中的文件经验分享
    centos 7 (操作应用)-关闭防火墙
    mysql数据库迁移
  • 原文地址:https://www.cnblogs.com/yangqiangyu/p/5143078.html
Copyright © 2011-2022 走看看