我爱编程

网络请求中代码执行顺序

2018-06-22  本文已影响0人  野岗狼沟兜

1 问题的提出,最常见在网络请求等耗时操作

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

   <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="点我执行"
       android:id="@+id/bt_click_execute"
       />
</LinearLayout>
public class MainActivity extends AppCompatActivity {
    private String toastStr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.bt_click_execute)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        execute();
                    }
                });
    }
    private void execute() {
        toastStr = "execute开始";
        toastStr = getWebData();/*模拟获取网络数据等耗时操作*/
        Toast.makeText(this, toastStr, Toast.LENGTH_LONG).show();
    }
    private String getWebData() {
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   Thread.sleep(1000*2);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               toastStr="模拟网络返回的数据";
           }
       }).start();
       return toastStr;
    }
}
  1. 找到布局文件中的Button,并给它设置一个监听,点击Button,执行execute()
  2. execute中有三个主要部分
    2.1 将字符串toastStr初始化为“execute开始”;
    2.2 将方法getWebData返回的字符串赋值给toastStr;
    2.3 将toastStr字符串显示出来。
  3. getWebData方法中:
    3.1 开启一个新线程
    3.2 让线程睡眠两秒(模拟耗时操作)
    3.2 将"模拟网络返回的数据" 赋值给toastStr
    3.3 返回toastStr
public class MainActivity extends AppCompatActivity {
    private String toastStr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.bt_click_execute)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        execute();
                    }
                });
    }

    private void execute() {
        toastStr = "execute开始";
        Log.d("20180621", "在execute方法中第一步时候,toastStr: " + toastStr);
        toastStr = getWebData();/*模拟获取网络数据等耗时操作*/
        Log.d("20180621", "在execute方法中第二步时候,toastStr: " + toastStr);
        Toast.makeText(this, toastStr, Toast.LENGTH_LONG).show();
    }

    private String getWebData() {
        Log.d("20180621", "在getWebData方法中开始时,toastStr: " + toastStr);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                toastStr = "模拟网络返回的数据";
                Log.d("20180621", "在getWebData方法中修改数据后,toastStr: " + toastStr);
            }
        }).start();
        Log.d("20180621", "在getWebData方法中返回前,toastStr: " + toastStr);
        return toastStr;
    }
}
  1. getWebData方法返回时,并没有修改字符串
  2. 也就是说在方法getWebData中,代码块
new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                toastStr = "模拟网络返回的数据";
                Log.d("20180621", "在getWebData方法中修改数据后,toastStr: " + toastStr);
            }
        }).start();

在方法返回后才执行的(通过最后字符串变为“模拟网络返回的数据”推断,其实只能推断赋值语句在方法返回后才执行。我这么写是因为认识了解了线程嘛,还是那句话,想看看执行顺序,请Debug),那么代码执行顺序“有问题”,是因为新线程的原因咯,正是如此!

2 多线程、异步任务给我们造成了代码执行顺序不对的假象

private String getWebData() {
        Log.d("20180621", "在getWebData方法中开始时,toastStr: " + toastStr);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                toastStr = "模拟网络返回的数据";
                Log.d("20180621", "在getWebData方法中修改数据后,toastStr: " + toastStr);
            }
        }).start();
        Log.d("20180621", "在getWebData方法中返回前,toastStr: " + toastStr);
        return toastStr;
    }

new ....start():之外的代码都是在主线程中执行的(在两者之间的就是子线程),所以当我们在主线程调用getWebData方法,想以此获取网络数据时,因为主线程不可能等待子线程,所以其实执行的是在子线程之外的、属于主线程的代码,所以其实执行的操作就是:return toastStr,而toasStr在之前赋值为"execute开始",那么

 toastStr = getWebData();

其实就相当于把他它自身赋值给自己,所以值还是"execute开始",这样,Toast显示出这个字符串也就理所当然了。

3 将一个子线程写在一个带返回值方法里,想获取子线程处理的数据在逻辑上就是错的

4 如何取回子线程中处理的数据

public class MainActivity extends AppCompatActivity {
    private String toastStr;

    private MyHandler handler = new MyHandler(this);

    /**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivity;

        private MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            if (activity != null) {
                if (msg.what == 1) {
                    Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.bt_click_execute)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        execute();
                    }
                });
    }

    private void execute() {
        toastStr = "execute开始";
        Log.d("20180621", "在execute方法中第一步时候,toastStr: " + toastStr);
        requestWebData();/*模拟获取网络数据等耗时操作*/
        Log.d("20180621", "在execute方法中第二步时候,toastStr: " + toastStr);

    }

    private void requestWebData() {
        Log.d("20180621", "在getWebData方法中开始时,toastStr: " + toastStr);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                toastStr = "模拟网络返回的数据";
                Message message = Message.obtain();
                message.what = 1;
                message.obj = toastStr;
                handler.sendMessage(message);

                Log.d("20180621", "在getWebData方法中修改数据后,toastStr: " + toastStr);
            }
        }).start();
        Log.d("20180621", "在getWebData方法中返回前,toastStr: " + toastStr);
    }
}
  1. 与之前相比,定义了一个Handler来处理子线程传过来的数据
  2. 在子线程处理好数据后,将消息发给主线程,并将数据发送给主线程
  3. 既然getWedData没有实际用处,那么将返回值改成void类型,并改方法名为:requestWebData,意在请求网络数据(至于怎么拿到,我不关心,我只是告诉子线程,去给我拿数据(子线程处理好数据,发送消息给主线程,那么也就拿到数据了))

5 异步任务请求数据的正确姿势

  1. 告诉子线程,“小子去干活了”,相当于上述代码的requestWebData,其核心就是开启子线程,执行耗时操作
  2. 当子线程活干完了,要报告:“老大,活干好了,要看看成果不?”,上述代码中的,即向主线程发送消息,传递数据
                Message message = Message.obtain();
                message.what = 1;
                message.obj = toastStr;
                handler.sendMessage(message);
  1. 老大检查工作成果,并做进一步加工,也就是最后数据的使用
 public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            if (activity != null) {
                if (msg.what == 1) {
                    Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_LONG).show();
                }
            }
        }
上一篇 下一篇

猜你喜欢

热点阅读