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

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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">

    <TextView
        android:layout_centerInParent="true"
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</RelativeLayout>

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

package example.lc.com.uicheckdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import org.w3c.dom.Text;

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
    评论
### 回答1: "watchdog: Blocked in handler on UI thread (android.ui)" 是 Android 应用程序常见的错误消息。它表示在应用程序的主线程UI 线程)上执行的某个代码块被阻塞,导致用户界面无法响应。 这个错误通常由以下几个原因引起: 1. 长时间运行的任务: 如果应用程序的主线程执行了需要较长时间才能完成的操作(例如读取大量数据或下载文件),则 UI 线程可能会被阻塞。 2. 阻塞 I/O 操作: 如果应用程序的主线程执行了阻塞 I/O 操作(例如读取网络或文件系统的数据),则 UI 线程可能会被阻塞。 3. 耗时的计算: 如果应用程序的主线程执行了耗时的计算操作(例如计算大量数据或执行复杂算法),则 UI 线程可能会被阻塞。 为了解决这个问题,你可以尝试以下方法: 1. 在应用程序的主线程执行耗时的操作是不好的,因此应该使用后台线程或 AsyncTask 等技术来执行长时间运行的任务或阻塞 I/O 操作。 2. 将耗时的计算操作放在后台线程执行,以避免阻塞 UI 线程。 3. 使用 Android 的性能分析工具,例如 Traceview 或 Systrace,来识别和解决应用程序的性能问题。 4. 使用 Android 的异步机制,例如 Handler、AsyncTask、Loader 和 IntentService 等来处理后台任务,从而避免 UI 线程的阻塞。 5. 使用 RecyclerView 或 ListView 等来避免在 UI 线程上加载大量数据。在使用这些视图时,可以使用异步加载技术,例如使用 AsyncTask 加载数据,或使用 Glide 或 Picasso 等图片加载库加载图片。 ### 回答2: 首先,"watchdog: Blocked in handler on ui thread (android.ui)"错误是指在AndroidUI线程上的处理程序被阻塞了。这个错误通常是由于在UI线程执行了长时间运行的操作造成的,导致应用程序的响应性能下降甚至崩溃。 要解决这个问题,可以进行以下几个步骤的分析思路: 1. 查看错误日志:在开发过程,应该经常查看应用程序的日志,以便快速定位错误。搜索关键字"watchdog: Blocked in handler on ui thread (android.ui)",找到相关的错误日志信息。 2. 定位UI线程阻塞的位置:根据错误日志的信息,定位到代码导致UI线程阻塞的位置。可能是某个长时间运行的耗时操作,如网络请求、数据库访问或者复杂的计算。 3. 分析耗时操作:对定位到的代码段进行深入分析,确定耗时操作的原因。可以使用Log输出来查看代码执行的时间,进一步定位导致阻塞的具体原因。 4. 优化耗时操作:一旦找到了导致UI线程阻塞的耗时操作,应该考虑进行优化。可以通过使用异步处理、线程池等技术将耗时操作放在后台线程执行,而不阻塞UI线程。 5. 使用Handler和MessageQueue:在一些特殊情况下,无法将耗时操作完全放在后台线程执行,还可以考虑使用Handler和MessageQueue来处理一些耗时操作。通过将任务分解为小块并使用Handler发送消息,然后在UI线程上按序处理这些消息,可以降低UI线程的负载。 总的来说,解决"watchdog: Blocked in handler on ui thread (android.ui)"错误需要通过分析日志、定位问题代码、优化耗时操作等步骤来解决。重点是将耗时操作放到后台线程执行,避免阻塞UI线程,以确保应用程序的响应性能和稳定性。 ### 回答3: 当我们在Android开发遇到watchdog: Blocked in handler on ui thread (android.ui)的错误时,一般是由于在UI线程执行了耗时操作引起的。 为了保证App的流畅性和响应性,Android设备规定UI操作只能在UI线程执行,一旦在UI线程执行了耗时操作,就会导致界面卡顿、ANR(Application Not Responding)甚至崩溃。 分析这个问题的思路如下: 1. 找出具体的错误信息和报错位置。通过查看Logcat的详细日志信息,我们可以找到watchdog: Blocked in handler on ui thread (android.ui)错误的具体信息和堆栈跟踪。 2. 定位到错误的代码。通过查看错误信息的堆栈跟踪,可以找到触发错误的具体代码位置。 3. 检查代码是否执行了耗时操作。查看错误位置附近的代码,判断是否有可能执行了耗时操作,例如网络请求、数据库查询、大数据处理等。 4. 将耗时操作移到线程。如果确定错误是由于在UI线程执行了耗时操作引起的,需要将该操作移到线程执行。可以使用多线程技术,如AsyncTask、HandlerThread、ThreadPoolExecutor等,在子线程执行耗时操作,并在操作完成后通过Handler或runOnUiThread回到UI线程更新UI。 5. 优化耗时操作。除了将耗时操作移到线程外,还可以尝试优化耗时操作本身,减少执行时间,例如使用算法优化、数据缓存、减少IO操作等。 6. 避免在UI线程执行耗时操作。在编写代码时,需要时刻注意不要在UI线程执行耗时操作,特别是网络请求、数据处理、文件读写等,应该采用异步操作或者使用线程池进行处理。 总之,分析watchdog: Blocked in handler on ui thread (android.ui)错误的思路主要是定位错误位置,并确定是否有耗时操作在UI线程执行。如果确实有耗时操作,在将其移到线程的同时,还要考虑优化操作本身以提升App的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值