android多线程中更新ui,Android能否在子线程中更新UI呢?

如题,Android能否在子线程中更新UI呢?这是一道面试题。那么这道题应该怎么去回答呢?在此我给出个人答案:“Android是不允许在子线程中更新UI的,但是在某种特殊情况下子线程是可以更新UI的”。为什么这么说呢?下面我们来看一个例子:

MainActivity

package example.lc.com.uicheckdemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView text;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

text= (TextView) findViewById(R.id.text);

new Thread(new Runnable() {

@Override

public void run() {

text.setText("在子线程中更新的UI");

}

}).start();

}

}

activity_main.xml

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_main"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="example.lc.com.uicheckdemo.MainActivity">

android:layout_centerInParent="true"

android:id="@+id/text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Hello World!" />

例子很简单就是一个Activity和一个布局,在onCreate中写了一个Thread并且在子线程中更新了UI,但是程序不会报错,不信大家可以去试一下。但是,将代码做如下修改:

MainActivity

package example.lc.com.uicheckdemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView text;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

text= (TextView) findViewById(R.id.text);

new Thread(new Runnable() {

@Override

public void run() {

try {

Thread.sleep(200);

} catch (InterruptedException e)

{

e.printStackTrace();

}

text.setText("在子线程中更新的UI");

}

}).start();

}

}

再次运行程序就会崩溃,logcat报错如下:

01-07 18:21:11.207 1476-2957/example.lc.com.uicheckdemo E/AndroidRuntime: FATAL EXCEPTION: Thread-96 Process: example.lc.com.uicheckdemo, PID: 1476 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6118)

at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:848)

at android.view.View.requestLayout(View.java:16431)

at android.view.View.requestLayout(View.java:16431)

at android.view.View.requestLayout(View.java:16431)

at android.view.View.requestLayout(View.java:16431)

at android.view.View.requestLayout(View.java:16431)

at android.view.View.requestLayout(View.java:16431)

at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)

at android.view.View.requestLayout(View.java:16431)

at android.widget.TextView.checkForRelayout(TextView.java:6600)

at android.widget.TextView.setText(TextView.java:3813)

at android.widget.TextView.setText(TextView.java:3671)

at android.widget.TextView.setText(TextView.java:3646)

at example.lc.com.uicheckdemo.MainActivity$1$override.run(MainActivity.java:25)

at example.lc.com.uicheckdemo.MainActivity$1$override.access$dispatch(MainActivity.java)

at example.lc.com.uicheckdemo.MainActivity$1.run(MainActivity.java:0)

at java.lang.Thread.run(Thread.java:841)

那么这到底是为什么呢?这里我们提出问题,本文下面所有的内容都是要去解答这个问题的,接下来就正式开始我们今天的探索。

**

从错误信息入手寻找问题的根源

**

从logcat显示的信息中我们发现了很有用的几行:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6118)

at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:848)

这几行信息直接告诉了我们错误是从ViewRootImpl的checkThread方法中报出来的,那么我们就去证实一下,打开checkThread方法:

void checkThread() {

if (mThread != Thread.currentThread()) {

throw new CalledFromWrongThreadException(

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

}

}

是的错误就是这个方法报出的,mThread指的是UI线程而Thread.currentThread()则是当前线程。由此,我们知道了Android中更新UI的时候检查线程的操作是在ViewRootImpl中进行的。到这里我们还是没有办法去解答我们提出的问题,我们接着分析。

ViewRootImpl是何时创建的?

想要知道ViewRootImpl是在哪里创建的,我们要先要找到handleResumeActivity()方法,这个方法在ActivityThread中,代码如下:

final void handleResumeActivity(IBinder token,

boolean clearHide, boolean isForward, boolean reallyResume) {

//代码省略

if (r.window == null && !a.mFinished && willBeVisible) {

r.window = r.activity.getWindow();

View decor = r.window.getDecorView();

decor.setVisibility(View.INVISIBLE);

ViewManager wm = a.getWindowManager();

WindowManager.LayoutParams l = r.window.getAttributes();

a.mDecor = decor;

l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

l.softInputMode |= forwardBit;

if (a.mVisibleFromClient) {

a.mWindowAdded = true;

wm.addView(decor, l);

}

//代码省略

}

在上面代码中,有两个需要我们注意的地方。第一个就是 View decor = r.window.getDecorView();,这个decor 就是我们熟知的DecorView,但是这不是我们今天研究的重点。第二个是 ViewManager wm = a.getWindowManager();,这个才是今天真正的重点。ViewManager是一个接口其定义了addView、updateViewLayout、removeView三个方法,a.getWindowManager是一个WindowManager,WindowManager也是一个接口并且继承了ViewManager,也就是说wm 是一个WindowManager。WindowManager是一个借口它的实现类是WindowManagerImpl, 也就是说wm.addView(decor, l);这句话调用的是WindowManagerImpl中的addView方法,我们看下WindowManagerImpl代码:

package android.view;

import android.annotation.NonNull;

import android.os.IBinder;

public final class WindowManagerImpl implements WindowManager {

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

private final Display mDisplay;

private final Window mParentWindow;

private IBinder mDefaultToken;

public WindowManagerImpl(Display display) {

this(display, null);

}

//代码省略

@Override

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

applyDefaultToken(params);

mGlobal.addView(view, params, mDisplay, mParentWindow);

}

//代码省略

}

看到在WindowManagerImpl 的addView方法中,又调用了WindowManagerGlobal的addView方法,接着打开WindowManagerGlobal类,代码如下:

package android.view;

//代码省略

public final class WindowManagerGlobal {

private static final String TAG = "WindowManager";

//代码省略

private Runnable mSystemPropertyUpdater;

private WindowManagerGlobal() {

}

public static void initialize() {

getWindowManagerService();

}

public static WindowManagerGlobal getInstance() {

synchronized (WindowManagerGlobal.class) {

if (sDefaultWindowManager == null) {

sDefaultWindowManager = new WindowManagerGlobal();

}

return sDefaultWindowManager;

}

}

//代码省略

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

if (view == null) {

throw new IllegalArgumentException("view must not be null");

}

if (display == null) {

throw new IllegalArgumentException("display must not be null");

}

if (!(params instanceof WindowManager.LayoutParams)) {

throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");

}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

if (parentWindow != null) {

parentWindow.adjustLayoutParamsForSubWindow(wparams);

} else {

// If there's no parent, then hardware acceleration for this view is

// set from the application's hardware acceleration setting.

final Context context = view.getContext();

if (context != null

&& (context.getApplicationInfo().flags

& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {

wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;

}

}

ViewRootImpl root;

View panelParentView = null;

synchronized (mLock) {

// Start watching for system property changes.

if (mSystemPropertyUpdater == null) {

mSystemPropertyUpdater = new Runnable() {

@Override public void run() {

synchronized (mLock) {

for (int i = mRoots.size() - 1; i >= 0; --i) {

mRoots.get(i).loadSystemProperties();

}

}

}

};

SystemProperties.addChangeCallback(mSystemPropertyUpdater);

}

int index = findViewLocked(view, false);

if (index >= 0) {

if (mDyingViews.contains(view)) {

// Don't wait for MSG_DIE to make it's way through root's queue.

mRoots.get(index).doDie();

} else {

throw new IllegalStateException("View " + view

+ " has already been added to the window manager.");

}

// The previous removeView() had not completed executing. Now it has.

}

// If this is a panel window, then find the window it is being

// attached to for future reference.

if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

final int count = mViews.size();

for (int i = 0; i < count; i++) {

if (mRoots.get(i).mWindow.asBinder() == wparams.token) {

panelParentView = mViews.get(i);

}

}

}

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

}

// do this last because it fires off messages to start doing things

try {

root.setView(view, wparams, panelParentView);

} catch (RuntimeException e) {

// BadTokenException or InvalidDisplayException, clean up.

synchronized (mLock) {

final int index = findViewLocked(view, false);

if (index >= 0) {

removeViewLocked(index, true);

}

}

throw e;

}

}

//代码省略

}

以上是WindowManagerGlobal类,只给出了addView方法其他代码省略了。我们仔细的来看下addView方法,再把无用的代码省去,结果如下:

public void addView(View view, ViewGroup.LayoutParams params,

Display display, Window parentWindow) {

//代码省略

ViewRootImpl root;

//代码省略

int index = findViewLocked(view, false);

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

//代码省略

}

好了代码省去的比较多,不过我们终于找到了我们想要的代码了。 root = new ViewRootImpl(view.getContext(), display); 到这ViewRootImpl就创建完了!也就是说ViewRootImpl是在WindowManagerGlobal中的addView方法中创建的!

那么现在我们来尝试回答一下我们提出的问题。合理的说法是让线程睡眠200ms以后再次醒来此时onResume方法已经被调用ViewRootImpl已经创建完成,此时可以检查线程了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值