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的将会在之后具体介绍!

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

  • 相关阅读:
    mysql修改数据表名
    HDU 5742 It's All In The Mind (贪心)
    HDU 5752 Sqrt Bo (数论)
    HDU 5753 Permutation Bo (推导 or 打表找规律)
    HDU 5762 Teacher Bo (暴力)
    HDU 5754 Life Winner Bo (博弈)
    CodeForces 455C Civilization (并查集+树的直径)
    CodeForces 455B A Lot of Games (博弈论)
    CodeForces 455A Boredom (DP)
    HDU 4861 Couple doubi (数论 or 打表找规律)
  • 原文地址:https://www.cnblogs.com/yangqiangyu/p/5143078.html
Copyright © 2011-2022 走看看