作过android开发基本都碰见过 ViewRootImpl$CalledFromWrongThreadException,上网一查,获得结果基本都是只能在主线程中更改 ui,子线程要修改 ui 只能 post 到主线程或者使用 handler 之类。可是仔细看看exception的描述并非这样的,“Only the original thread that created a view hierarchy can touch its views”,只有建立该 view 布局层次的原始线程才可以修改其所属 view 的布局属性,因此“只能在主线程中更改 ui ”这句话自己是有点不严谨的,接下来分析一下。javascript
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6498)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:954)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4643)
at android.view.View.invalidateInternal(View.java:11775)
at android.view.View.invalidate(View.java:11739)
at android.view.View.invalidate(View.java:11723)
at android.widget.TextView.checkForRelayout(TextView.java:7002)
at android.widget.TextView.setText(TextView.java:4073)
at android.widget.TextView.setText(TextView.java:3931)
at android.widget.TextView.setText(TextView.java:3906)
at com.android.sample.HomeTestActivity$1.run(HomeTestActivity.java:114)
at java.lang.Thread.run(Thread.java:818)复制代码
相关博客介绍:
android 不能在子线程中更新ui的讨论和分析:Activity 打开的过程分析;
java/android 设计模式学习笔记(9)---代理模式:AMS 的相关类图和介绍;
android WindowManager解析与骗取QQ密码案例分析:界面 window 的建立过程;
java/android 设计模式学习笔记(8)---桥接模式:WMS 的相关类图和介绍;
android IPC通讯(下)-AIDL:AIDL 以及 Binder 的相关介绍;
Android 动态代理以及利用动态代理实现 ServiceHook:ServiceHook 的相关介绍;
Android TransactionTooLargeException 解析,思考与监控方案:TransactionTooLargeException 的解析以及监控方案。java
咱们根据 exception 的 stackTrace 信息,了解一下源码,以 setText 为例,若是 textview 已经被绘制出来了,调用 setText 函数,会调用到 View 的 invalidate 函数,其中又会调用到 invalidateInternal 函数,接着调用到 parent.invalidateChildInParent 函数,其中 parent 对象就是父控件 ViewGroup,最后会调用到 ViewRootImpl 的 invalidateChildInParent 函数,为何最后会调用到 ViewRootImpl 类中呢,这里就须要说到布局的建立过程了:android
先分析一下 Activity 启动过程,startActivity 和 startActivityForResult 函数用来启动一个 activity,最后他们最终都会调用到一个函数windows
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options)复制代码
中,接着函数中会调用 Instrumentation 的 execStartActivity 方法,该函数中会调用 ActivityManagerNative.getDefault().startActivity 方法,ActivityManagerNative 类的定义设计模式
public abstract class ActivityManagerNative extends Binder implements IActivityManager复制代码
该类继承自 Binder 并实现了 IActivityManager 这个接口,IActivityManager 继承自 IInterface 接口,用过 AIDL 的应该知道,基本和这个结构类似,因此确定是用来跨进程通讯的,ActivityManagerService 类也是继承自 ActivityManagerNative 接口,所以 ActivityManagerService 也是一个 Binder 实现子类,他是 IActivityManager 接口的具体实现类,getDefault 函数是经过一个 Singleton 对象对外提供,他最后返回的是 ActivityManagerService 的 IBinder 对象,因此 startActivity 方法最终实现是在 ActivityManagerService 类中(这里讲的比较简单,若是你们对相关类层次结构和调用方式感兴趣的,能够看看个人博客: java/android 设计模式学习笔记(9)---代理模式,里面有详细介绍到):
安全
ActivityThread thread = new ActivityThread();
thread.attach(false);复制代码
而后 attach 方法:多线程
final ApplicationThread mAppThread = new ApplicationThread();
.....
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}复制代码
能够看到这里经过 AIDL 调用,将 ApplicationThread 对象设置进了 AMS 中来做为 AMS 和 应用进程的桥梁,为何须要这个 ApplicationThread 桥梁呢,由于 AMS 的职责是管理 Activity 的生命周期和栈,因此不少时候都是 AMS 主动调用到应用进程,不是简单的一个应用进程调用系统进程 Service 而且返回值的过程,因此必需要让 AMS 持有一个应用进程的相关对象来进行调用,这个对象就是 ApplicationThread 对象。ApplicationThreadNative 虚类则实现了 IApplicationThread 接口,在该虚类中的 onTransact 函数中,根据 code 不一样会进行不一样的操做,最后 ActivityThread 类的内部类 ApplicationThread 继承自 ApplicationThreadNative 类,最终的实现者就是 ApplicationThread 类,在 ApplicationThreadNative 中根据 code 进行不一样操做的实现代码都在 ApplicationThread 类中,这个过程执行到最后会回调到 ApplicationThread 类中的 scheduleLaunchActivity 方法:app
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
....
sendMessage(H.LAUNCH_ACTIVITY, r);
}复制代码
最终给 H 这个 Handler 类发送了一个 message(关于 H 类能够去看看博客 Android TransactionTooLargeException 解析,思考与监控方案),其中调用了的 handleLaunchActivity 方法:ide
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn't in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// We need to keep around the original state, in case we need to be created again.
// But we only do this for pre-Honeycomb apps, which always save their state when
// pausing, so we can not have them save their state when restarting from a paused
// state. For HC and later, we want to (and can) let the state be saved as the
// normal part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}复制代码
这个方法经过 performLaunchActivity 方法获取到一个 Activity 对象,在 performLaunchActivity 函数中会调用该 activity 的 attach 方法,这个方法把一个 ContextImpl 对象 attach 到了 Activity 中,很是典型的装饰者模式:函数
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
....
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}复制代码
window 是经过下面方法获取的
mWindow = new PhoneWindow(this)复制代码
建立完 Window 以后,activity 会为该 Window 设置回调,Window 接收到外界状态改变时就会回调到 activity 中。在 activity 中会调用 setContentView() 函数,它是调用 window.setContentView() 完成的,最终的具体操做是在 PhoneWindow 中,PhoneWindow 的 setContentView 方法第一步会检测 DecorView 是否存在,若是不存在,就会调用 generateDecor 函数直接建立一个 DecorView;第二步就是将 activity 的视图添加到 DecorView 的 mContentParent 中;第三步是回调 activity 中的 onContentChanged 方法通知 activity 视图已经发生改变。
public void setContentView(View view, ViewGroup.LayoutParams params) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Window.Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}复制代码
这些步骤完成以后,DecorView 尚未被 WindowManager 正式添加到 Window 中,接着会调用到 ActivityThread 类的 handleResumeActivity 方法将顶层视图 DecorView 添加到 PhoneWindow 窗口,activity 的视图才能被用户看到:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
.....
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);
}
.....
}复制代码
DecorView 和 Window 的关系代码中已经很清楚了,接下来分析一下 addView 方法,其中最关键的代码是:
ViewManager wm = a.getWindowManager();
....
wm.addView(decor, l);复制代码
而 a.getWindowManager 调用到的是 Activity.getWindowManager:
/** Retrieve the window manager for showing custom windows. */
public WindowManager getWindowManager() {
return mWindowManager;
}复制代码
这个值是在上面的 attach 方法里面设置的:
mWindow = new PhoneWindow(this);
.....
mWindowManager = mWindow.getWindowManager();复制代码
因此咱们跟踪 PhoneWindow 里面的 getWindowManager 方法:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
.....
/** * Set the window manager for use by this Window to, for example, * display panels. This is <em>not</em> used for displaying the * Window itself -- that must be done by the client. * * @param wm The window manager for adding new windows. */
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
.....
/** * Return the window manager allowing this Window to display its own * windows. * * @return WindowManager The ViewManager. */
public WindowManager getWindowManager() {
return mWindowManager;
}复制代码
setWindowManager 函数是在哪里调用到呢,仍是 Activity.attach 方法:
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0)复制代码
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 这个返回的是什么呢?咱们先看看 context 对象是什么,是 attach 函数的第一个参数,好,咱们回到 ActivityThread 类调用 activity.attach 函数的地方:
Context appContext = createBaseContextForActivity(r, activity);
......
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);复制代码
看看 createBaseContextForActivity 函数:
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
int displayId = Display.DEFAULT_DISPLAY;
try {
displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
baseContext = appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}复制代码
可见,这里返回的是一个 ContextImpl 对象,并且这个对象会被 Activity 调用 attachBaseContext(context);
方法给设置到 mBase 对象里面,典型的装饰者模式,因此最终确定是调用到了 ContextImpl 类的 getSystemService 函数:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}复制代码
而后调用到 SystemServiceRegistry.getSystemService 函数,咱们来看看 SystemServiceRegistry 类的相关几个函数:
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
......
static {
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
}
.....
/** * Gets a system service from a given context. */
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
.......
/** * Statically registers a system service with the context. * This method must be called during static initialization only. */
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}复制代码
咱们这里能够清楚的看到,SystemServiceRegistry 类中有一个静态块代码,用来注册因此基本的 Service ,例如 alarm,notification 等等等,其中的 WindowManager 就是经过这个注册进去的,注意到这里返回的是一个 WindowManagerImpl 对象,因此 PhoneWindow 的 setWindowManager 函数 的 wm 对象就是 WindowManagerImpl 对象,这就是一个典型的桥接模式,WindowManager 接口继承自 ViewManager 接口,最终实现类是 WindowManagerImpl 类(感兴趣的能够去看看个人博客: java/android 设计模式学习笔记(8)---桥接模式,其实这里是有用到桥接模式的):
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);复制代码
因此这里的
该类并无直接实现 Window 的三大操做,而是所有交给了 WindowManagerGlobal 来处理,WindowManagerGlobal 以单例模式 的形式向外提供本身的实例,在 WindowManagerImpl 中有以下一段代码:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getinstance();复制代码
因此 WindowManagerImpl 将 addView 操做交给 WindowManagerGlobal 来实现,WindowManagerGlobal 的 addView 函数中建立了一个 ViewRootImpl 对象 root,而后调用 ViewRootImpl 类中的 setView 成员方法:
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
.....
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) {
....
}复制代码
setView 方法完成了三件事情,将外部参数 DecorView 赋值给 mView 成员变量、标记 DecorView 已添加到 ViewRootImpl、调用 requestLayout 方法请求布局,那么继续跟踪代码到 requestLayout() 方法:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}复制代码
scheduleTraversals 函数实际是 View 绘制的入口,该方法会经过 WindowSession 使用 IPC 方式调用 WindowManagerService 中的相关方法去添加窗口(这里我就不作详细介绍了,感兴趣的去看看我上面提到的博客: java/android 设计模式学习笔记(8)---桥接模式 和博客 Android TransactionTooLargeException 解析,思考与监控方案),scheduleTraversals 函数最后会调用到 doTraversal 方法,doTraversal 方法又调用 performTraversals 函数,performTraversals 函数就很是熟悉了,他会去调用 performMeasure,performLayout 和 performDraw 函数去进行 view 的计算和绘制,咱们只是在一个比较高的层次上归纳性地梳理了它的整个脉络,它的简化结构:
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
....
parent = parent.invalidateChildInParent(location, dirty);
....
} while (parent != null);复制代码
这个问题就差很少清楚了,其余的能够再看看老罗的博客:blog.csdn.net/luoshengyan…
上面分析了 Activity 的启动和布局建立过程,其中知道 Activity 的建立须要新建一个 ViewRootImpl 对象,看看 ViewRootImpl 的构造函数:
public ViewRootImpl(Context context, Display display) {
.....
mThread = Thread.currentThread();
.....
}复制代码
在初始化一个 ViewRootImpl 函数的时候,会调用 native 方法,获取到该线程对象 mThread,接着 setText 函数会调用到 requestLayout 方法(TextView 绘制出来以后,调用 setText 才会去调用 requestLayout 方法,没有绘制出来以前,在子线程中调用 setText 是不会抛出 Exception):
public void requestLayout() {
.....
checkThread();
.....
}
....
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}复制代码
因此如今 “不能在子线程中更新 ui” 的问题已经很清楚了,无论 startActivity 函数调用在什么线程,ActivityThread 的内部函数执行是在主线程中的:
/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. */
public final class ActivityThread {
....
}复制代码
因此 ViewRootImpl 对象的建立也是在主线程中,这就是说一个 activity 的对应 ViewRootImpl 对象中的 mThread 必定是表明主线程,这就是“为何不能在子线程中操做 UI 的”答案的解释,问题解决!!!
可是不是说这个答案不严谨么?是的,可不能够在子线程中添加 Window,而且建立 ViewRootImpl 呢?固然能够,在子线程中建立一个 Window 就能够,思路是在子线程中调用 WindowManager 添加一个 view,相似于
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
params.format = PixelFormat.TRANSPARENT;
params.gravity = Gravity.CENTER;
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
....
windowManager.addView(v, params);复制代码
android WindowManager解析与骗取QQ密码案例分析博客中介绍到 activity 和 dialog 不是系统层级的 Window,咱们可使用 WindowManager 来添加自定义的系统 Window,那么问题又来了,系统级别 Window 是怎么添加的呢,老罗的另外一篇博客 blog.csdn.net/luoshengyan… 中介绍到: “对于非输入法窗口、非壁纸窗口以及非 Activity 窗口来讲,它们所对应的 WindowToken 对象是在它们增长到 WindowManagerService 服务的时候建立的......若是参数 attrs 所描述的一个 WindowManager.LayoutParams 对象的成员变量 token 所指向的一个 IBinder 接口在 WindowManagerService 类的成员变量 mTokenMap 所描述的一个 HashMap 中没有一个对应的 WindowToken 对象,而且该 WindowManager.LayoutParams 对象的成员变量 type 的值不等于 TYPE_INPUT_METHOD、TYPE_WALLPAPER,以及不在FIRST_APPLICATION_WINDOW 和LAST_APPLICATION_WINDOW,那么就意味着这时候要增长的窗口就既不是输入法窗口,也不是壁纸窗口和 Activity 窗口,所以,就须要以参数 attrs 所描述的一个 WindowManager.LayoutParams 对象的成员变量 token 所指向的一个 IBinder 接口为参数来建立一个 WindowToken 对象,而且将该 WindowToken对象保存在 WindowManagerService 类的成员变量 mTokenMap 和 mTokenList 中。”。
了解上面以后,换一种思路,就能够在子线程中建立 view 而且添加到 windowManager 中。
有了思路以后,既能够来实现相关代码了:
new Thread(new Runnable() {
@Override
public void run() {
showWindow();
}
}).start();
......
private void showWindow(){
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
params.format = PixelFormat.TRANSPARENT;
params.gravity = Gravity.CENTER;
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
LayoutInflater inflater = LayoutInflater.from(this);
v = (RelativeLayoutWithKeyDetect) inflater.inflate(R.layout.window, null);
.....
windowManager.addView(v, params);
}复制代码
运行一下,报错:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.view.ViewRootImpl$ViewRootHandler.<init>(ViewRootImpl.java:3185)
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:3483)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:261)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at com.android.grabqqpwd.BackgroundDetectService.showWindow(BackgroundDetectService.java:208)
at com.android.grabqqpwd.BackgroundDetectService.access$100(BackgroundDetectService.java:39)
at com.android.grabqqpwd.BackgroundDetectService$1.run(BackgroundDetectService.java:67)
at java.lang.Thread.run(Thread.java:818)复制代码
这是由于 ViewRootImpl 类内部会新建一个 ViewRootHandler 类型的 mHandler 用来处理相关消息,因此若是线程没有 Looper 是会报错的,添加 Looper,修改代码:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
showWindow();
handler = new Handler(){
@Override
public void dispatchMessage(Message msg) {
Looper.myLooper().quit();
L.e("quit");
}
};
Looper.loop();
}
}).start();复制代码
建立 Looper 以后,须要在必要时候调用 quit 函数将其退出。这样就成功显示了
为何 android 会设计成只有建立 ViewRootImpl 的原始线程才能更改 ui 呢?这就要说到 Android 的单线程模型了,由于若是支持多线程修改 View 的话,由此产生的线程同步和线程安全问题将是很是繁琐的,因此 Android 直接就定死了,View 的操做必须在建立它的 UI 线程,从而简化了系统设计。 有没有能够在其余非原始线程更新 ui 的状况呢?有,SurfaceView 就能够在其余线程更新,具体的你们能够去网上了解一下相关资料。