A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始



大家好,新的一期深圳校区问答网集锦又来啦。这一期的问题焦点是Activity。

Acitivity作为Android Framwork层提供的四大组件的老大,是最重要、最常用的组件。从字面意思看,是“活动”的意思。可是“活动”到底是shen me gui?对于初学者来说,有点难以理解。只知道往Activity里边写布局,写监听事件,就能向用户展示界面并进行UI交互了,好神奇。

其实要理解我们的Activity,还要从Java的main方法开始说起。大家都知道,Android的应用其实也是跑在虚拟机的,Dalvik、ART和JAM一样也是虚拟机的一种。而虚拟机的入口就是main方法,在main方法起了一条UI线程,然后在里边创建了Activity,最后给分配了的windows对象给这个Acitivty。这样Activity就显示出来了。然而,onCreate这些生命周期方法到底是谁调用的?既然对象是在main法里创建的,其实就是从main方法里面进来的虚拟机在调用,只是调用的方式是反射啦。这么一说,是不是好像明白了一些。不过,在真正掌握Acitivty之前,知道这些还远远不够,这期我们把深圳校区里关于Activity的各种琐碎问题集合起来,给大家参考关于Acitivty的问题,在深圳校区问答网,有么多:


下面就来看看关于深圳校区学员在Activity里遇到的各种奇葩问题吧:-------------------------------------------------------华丽的分割线------------------------------------------------------一、onKeyUp方法和onKeyDown方法有什么区别?答:Activity.onKeyDown(); 当某个键被按下时会触发,但不会被任何的该Activity内的任何view处理。
默认按下KEYCODE_BACK键后会回到上一个Activity。
Activity.onKeyUp():
当某个按键被按下,松开后触发,但不会被任何的该Activity内的任何view处理。
默认没有执行任何操作,只是简单的给一个false作为返回值。

-------------------------------------------------------------------------------------------------------

二、我在activity中执行startActivityForResult,并且重写了onActivityResult(),但是没等到被调用的 Activity 返回,onActivityResult() 就被执行了。也就是说,在startActivityForResult被调用后startActivityForResult立即被执行了,这是怎么回事?

答:针对这个问题,在调用startActivityForResult之后,onActivityResult无响应或立即响应

有两种现象:
## 现象一: ##
执行startActivityForResult,没等到被调用的 Activity 返回,onActivityResult() 就被执行了。
## 原因: ##
这与 Activity 的加载模式(launchMode)有关,该属性可以在 AndroidManifest.xml 中设置。
原先将其设为 singleInstance或singleTask,经测试,所有需要传递或接收的 Activity 不允许设置该属性,**或只能设为标准模式**,否则系统将在 startActivityForResult() 后直接调用 onActivityResult()。
## 现象一: ##
两个activity传递数据和返回数据时,请求方的onActivityResult始终无响应,通过debug调试模式也没见调用该方法。查看了各种配置和程序代码,均未发现有错误之处。
## 原因 ##
后来仔细阅读API说明,恍然大悟,原来是调用startActivityForResult的参数问题,即调用时这样:
startActivityForResult(intent, 0);
**是第二个参数的问题,该参数必须大于0才能在返回值,并激活onActivityResult方法。**
-------------------------------------------------------------------------------------------------------
三、最后来一个给力的,直接上代码,精彩问答:
学生问题:我居然可以在子线程中成功更新UI?

我在activity的oncreate方法中,创建了一个子线程,并且在子线程中去更改了button的显示内容,居然还成功了,没有报错,这是为什么?不是说子线程不能更新UI吗?

具体代码如下:

  1. public class MainActivity extends ActionBarActivity {
  2. Button btn = null;
  3. /** Called when the activity is first created. */
  4. public void onCreate(Bundle savedInstanceState) {
  5.   super.onCreate(savedInstanceState);
  6.   setContentView(R.layout.activity_main);
  7.   btn = (Button) findViewById(R.id.Button01);
  8.   TestThread2 t = new TestThread2(btn);
  9.   t.start();
  10. }
  11. class TestThread2 extends Thread {
  12.   Button btn = null;
  13.   public TestThread2(Button btn) {
  14.    this.btn = btn;
  15.   }
  16.   @Override
  17.   public void run() {
  18.    btn.setText("TestThread2.run");
  19.   }
  20. }
  21. }
复制代码

精彩答案:

Android官方呢也建议我们不要在非UI线程直接更新UI,为什么呢?借助Android官方的一句话来说就是:
“The Android UI toolkit is not thread-safe and the view must always be manipulated on the UI thread.”
因此,很多童鞋会有这么一个惯性思维:在非UI线程中不能更新UI!既然Android不建议我们这么做,那其必定会对我们在code时做一些限制
Android通过检查我们当前的线程是否为UI线程从而抛出一个自定义的AndroidRuntimeException来提醒我们“Only the original thread that created a view hierarchy can touch its views”并强制终止程序运行,具体的实现在ViewRootImpl类的checkThread方法中:
  1. @SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
  2. public final class ViewRootImpl implements ViewParent,
  3.         View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
  4.     // 省去海量代码…………………………
  5.     void checkThread() {
  6.         if (mThread != Thread.currentThread()) {
  7.             throw new CalledFromWrongThreadException(
  8.                     "Only the original thread that created a view hierarchy can touch its views.");
  9.         }
  10.     }
  11.     // 省去巨量代码……………………
  12. }
复制代码
这就是Android在4.0后对我们做出的一个限制
OK,这里我们再来看一下上面的一段代码,在线程中我调用了Thread.sleep(200);来让我们的匿名线程暂停了200ms。假如……我们去掉它的会发生什么?来试试:
  1. /**
  2. * 主界面
  3. *
  4. * @author Aige {@link http://blog.csdn.net/aigestudio}
  5. * @since 2014/11/17
  6. */
  7. public class MainActivity extends Activity {
  8. private TextView tvText;
  9. @Override
  10. public void onCreate(Bundle savedInstanceState) {
  11.   super.onCreate(savedInstanceState);
  12.   setContentView(R.layout.activity_main);
  13.   tvText = (TextView) findViewById(R.id.main_tv);
  14.   new Thread(new Runnable() {
  15.    @Override
  16.    public void run() {
  17.     tvText.setText("OtherThread");
  18.    }
  19.   }).start();
  20. }
  21. }
复制代码
这时你会发现我们的代码正确执行了!而且我们的TextView正确显示出了“OtherThread”文本!看到屏幕上的这11个英文字母我相信小伙伴们惊呆了,我们成功地在非UI线程中更新了UI。其实这里最最根本的原因是我们并没有checkThread我们的当前线程,而我在文章最开始的代码中通过Thread.sleep(200)暂停了一小段时间,这里为什么回暂停线程一段时间?在这段时间的背后Android背地里背着我们都干了什么?这一切的背后, 是人性的扭曲还是道德的沦丧?抱歉……磕个药,上面我们讲到,我们能正确以上述代码的方式在非UI线程中更新UI而不报错,那么原因也许只有一个,那就是没有执行checkThread方法去检查我们的当前线。但是,细看调用checkThread方法的调用方法们你就会发现,全是跟View创建生成相关:

也就是说一旦我们尝试去对我们的控件进行生成,这些方法其中一个必然会被调用,这时候很多学员就会蛋疼。但是,请不要被checkThread方法的思维所束缚,这时候你该扩大你的思维范畴,既然checkThread方法属于ViewRootImpl的成员方法,那么会不会是此时我们的ViewRootImpl根本就没被创建呢?怀着这个出发点,我们再度审视ActivtyThread调度Activity生命周期的各个环节,首先看看performLaunchActivity方法中的处理:
  1. public final class ActivityThread {
  2.     // 省去海量代码…………………………
  3.     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  4.         ActivityInfo aInfo = r.activityInfo;
  5.          
  6.         // 省去对packageInfo的逻辑处理
  7.         // 省去对ComponentName的逻辑处理
  8.         Activity activity = null;
  9.         try {
  10.             java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
  11.             // 通过Instrumentation对象生成Activity类的实例
  12.             activity = mInstrumentation.newActivity(
  13.                     cl, component.getClassName(), r.intent);
  14.             
  15.             // 省去三行代码…………
  16.         } catch (Exception e) {
  17.             // 省去对异常的捕获处理
  18.         }
  19.         try {
  20.             Application app = r.packageInfo.makeApplication(false, mInstrumentation);
  21.             // 省去多行无关代码
  22.             if (activity != null) {
  23.                 Context appContext = createBaseContextForActivity(r, activity);
  24.                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  25.                 Configuration config = new Configuration(mCompatConfiguration);
  26.                 // 省去多行无关代码
  27.                 if (customIntent != null) {
  28.                     activity.mIntent = customIntent;
  29.                 }
  30.                 r.lastNonConfigurationInstances = null;
  31.                 activity.mStartedActivity = false;
  32.                 int theme = r.activityInfo.getThemeResource();
  33.                 if (theme != 0) {
  34.                     activity.setTheme(theme);
  35.                 }
  36.                 /*
  37.                  * 调用callActivityOnCreate方法处理Create逻辑
  38.                  */
  39.                 activity.mCalled = false;
  40.                 mInstrumentation.callActivityOnCreate(activity, r.state);
  41.                 if (!activity.mCalled) {
  42.                     // 省去多行无关代码
  43.                 }
  44.                 r.activity = activity;
  45.                 r.stopped = true;
  46.                 /*
  47.                  * 调用performStart方法处理Start逻辑
  48.                  */
  49.                 if (!r.activity.mFinished) {
  50.                     activity.performStart();
  51.                     r.stopped = false;
  52.                 }
  53.                 // 省去多行无关代码
  54.             }
  55.             // 省去两行无关代码
  56.         } catch (SuperNotCalledException e) {
  57.             // 省去对异常的捕获处理
  58.         } catch (Exception e) {
  59.             // 省去对异常的捕获处理
  60.         }
  61.         return activity;
  62.     }
  63.     // 省去巨量代码……………………
  64. }
复制代码
performLaunchActivity方法中目测没有我们想要的信息,其创建了Activity并调度了Create和Start的逻辑处理,那我们看看callActivityOnCreate方法呢:
  1. public class Instrumentation {
  2. // 省去海量代码…………………………
  3.     public void callActivityOnCreate(Activity activity, Bundle icicle) {
  4.         // 省去某些逻辑……
  5.          
  6.         activity.performCreate(icicle);
  7.          
  8.         // 省去某些逻辑……
  9.     }
  10. // 省去巨量代码……………………
  11. }
复制代码
callActivityOnCreate中除了对MQ的一些调度外最重要的还是通过Activity的实例调用了performCreate方法:

  1. public class Activity extends ContextThemeWrapper
  2.         implements LayoutInflater.Factory2,
  3.         Window.Callback, KeyEvent.Callback,
  4.         OnCreateContextMenuListener, ComponentCallbacks2 {
  5.     // 省去海量代码…………………………
  6.     final void performCreate(Bundle icicle) {
  7.         onCreate(icicle);
  8.         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
  9.                 com.android.internal.R.styleable.Window_windowNoDisplay, false);
  10.         mFragments.dispatchActivityCreated();
  11.     }
  12.     // 省去巨量代码……………………
  13. }
复制代码
performCreate方法逻辑就更干脆了,最主要的还是调用了我们Activity的onCreate方法,我们没在这里找到我们想要的东西,那再来看performStart:

  1. public class Activity extends ContextThemeWrapper
  2.         implements LayoutInflater.Factory2,
  3.         Window.Callback, KeyEvent.Callback,
  4.         OnCreateContextMenuListener, ComponentCallbacks2 {
  5.     // 省去海量代码…………………………
  6.     final void performStart() {
  7.         mFragments.noteStateNotSaved();
  8.         mCalled = false;
  9.         mFragments.execPendingActions();
  10.         mInstrumentation.callActivityOnStart(this);
  11.         if (!mCalled) {
  12.             throw new SuperNotCalledException(
  13.                 "Activity " + mComponent.toShortString() +
  14.                 " did not call through to super.onStart()");
  15.         }
  16.         mFragments.dispatchStart();
  17.         if (mAllLoaderManagers != null) {
  18.             final int N = mAllLoaderManagers.size();
  19.             LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
  20.             for (int i=N-1; i>=0; i--) {
  21.                 loaders[i] = mAllLoaderManagers.valueAt(i);
  22.             }
  23.             for (int i=0; i<N; i++) {
  24.                 LoaderManagerImpl lm = loaders[i];
  25.                 lm.finishRetain();
  26.                 lm.doReportStart();
  27.             }
  28.         }
  29.     }
  30.     // 省去巨量代码……………………
  31. }
复制代码
performStart相对于performCreate有更多的逻辑处理,但依然木有我们想要的结果,其最终还是同过Instrumentation对象调用callActivityOnStart:

  1. public class Instrumentation {
  2. // 省去海量代码…………………………
  3.     public void callActivityOnStart(Activity activity) {
  4.         activity.onStart();
  5.     }
  6. // 省去巨量代码……………………
  7. }
复制代码
callActivityOnStart仅仅是调用了Activity的onStart方法,同杨,onStart方法中也没有我们想要的结果。我们抱着即将从埃菲尔铁塔顶端做自由落体的心态继续看onResume方法的调度,其在ActivityThread中通过handleResumeActivity调度:

  1. public final class ActivityThread {
  2.     // 省去海量代码…………………………
  3.     final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
  4.             boolean reallyResume) {
  5.         unscheduleGcIdler();
  6.         ActivityClientRecord r = performResumeActivity(token, clearHide);
  7.         if (r != null) {
  8.             final Activity a = r.activity;
  9.             // 省去无关代码…………
  10.             final int forwardBit = isForward ?
  11.                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
  12.             boolean willBeVisible = !a.mStartedActivity;
  13.             if (!willBeVisible) {
  14.                 try {
  15.                     willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
  16.                             a.getActivityToken());
  17.                 } catch (RemoteException e) {
  18.                 }
  19.             }
  20.             if (r.window == null && !a.mFinished && willBeVisible) {
  21.                 r.window = r.activity.getWindow();
  22.                 View decor = r.window.getDecorView();
  23.                 decor.setVisibility(View.INVISIBLE);
  24.                 ViewManager wm = a.getWindowManager();
  25.                 WindowManager.LayoutParams l = r.window.getAttributes();
  26.                 a.mDecor = decor;
  27.                 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  28.                 l.softInputMode |= forwardBit;
  29.                 if (a.mVisibleFromClient) {
  30.                     a.mWindowAdded = true;
  31.                     wm.addView(decor, l);
  32.                 }
  33.             } else if (!willBeVisible) {
  34.                 // 省去无关代码…………
  35.                 r.hideForNow = true;
  36.             }
  37.             cleanUpPendingRemoveWindows(r);
  38.             if (!r.activity.mFinished && willBeVisible
  39.                     && r.activity.mDecor != null && !r.hideForNow) {
  40.                 if (r.newConfig != null) {
  41.                     // 省去无关代码…………
  42.                     performConfigurationChanged(r.activity, r.newConfig);
  43.                     freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
  44.                     r.newConfig = null;
  45.                 }
  46.                 // 省去无关代码…………
  47.                 WindowManager.LayoutParams l = r.window.getAttributes();
  48.                 if ((l.softInputMode
  49.                         & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
  50.                         != forwardBit) {
  51.                     l.softInputMode = (l.softInputMode
  52.                             & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
  53.                             | forwardBit;
  54.                     if (r.activity.mVisibleFromClient) {
  55.                         ViewManager wm = a.getWindowManager();
  56.                         View decor = r.window.getDecorView();
  57.                         wm.updateViewLayout(decor, l);
  58.                     }
  59.                 }
  60.                 r.activity.mVisibleFromServer = true;
  61.                 mNumVisibleActivities++;
  62.                 if (r.activity.mVisibleFromClient) {
  63.                     r.activity.makeVisible();
  64.                 }
  65.             }
  66.             if (!r.onlyLocalRequest) {
  67.                 r.nextIdle = mNewActivities;
  68.                 mNewActivities = r;
  69.                 // 省去无关代码…………
  70.                 Looper.myQueue().addIdleHandler(new Idler());
  71.             }
  72.             r.onlyLocalRequest = false;
  73.             // 省去与ActivityManager的通信处理
  74.         } else {
  75.             // 省略异常发生时对Activity的处理逻辑
  76.         }
  77.     }
  78.     // 省去巨量代码……………………
  79. }
复制代码
handleResumeActivity方法逻辑相对要复杂一些,对当前显示Window的逻辑判断以及没创建的初始化等等工作外其在最终会调用Activity的makeVisible方法:
  1. public class Activity extends ContextThemeWrapper
  2.         implements LayoutInflater.Factory2,
  3.         Window.Callback, KeyEvent.Callback,
  4.         OnCreateContextMenuListener, ComponentCallbacks2 {
  5.     // 省去海量代码…………………………
  6.     void makeVisible() {
  7.         if (!mWindowAdded) {
  8.             ViewManager wm = getWindowManager();
  9.             wm.addView(mDecor, getWindow().getAttributes());
  10.             mWindowAdded = true;
  11.         }
  12.         mDecor.setVisibility(View.VISIBLE);
  13.     }
  14.     // 省去巨量代码……………………
  15. }
复制代码
在makeVisible方法中逻辑相当简单,获取一个窗口管理器对象并将我们曾在自定义控件其实很简单7/12中提到过的根视图DecorView添加到其中,addView的具体实现在WindowManagerGlobal中:

  1. public final class WindowManagerGlobal {
  2.     public void addView(View view, ViewGroup.LayoutParams params,
  3.             Display display, Window parentWindow) {
  4.         // 省去很多代码
  5.         ViewRootImpl root;
  6.         // 省去一行代码
  7.         synchronized (mLock) {
  8.             // 省去无关代码
  9.             root = new ViewRootImpl(view.getContext(), display);
  10.             // 省去一行代码
  11.             // 省去一行代码
  12.             mRoots.add(root);
  13.             // 省去一行代码
  14.         }
  15.         // 省去部分代码
  16.     }
  17. }
复制代码

在addView生成了一个ViewRootImpl对象并将其保存在了mRoots数组中,每当我们addView一次,就会生成一个ViewRootImpl对象,其实看到这里我们还可以扩展一下问题一个APP是否可以拥有多个根视图呢?答案是肯定的,因为只要我调用了addView方法,我们传入的View参数就可以被认为是一个根视图,但是!在framework的默认实现中有且仅有一个根视图,那就是我们上面makeVisible方法中addView进去的DecorView,所以为什么我们可以说一个APP虽然可以有多个Activity,但是每个Activity只会有一个Window一个DecorView一个ViewRootImpl,看到这里很多童鞋依然会问,也就是说在onResume方法被执行后我们的ViewRootImpl才会被生成对吧,但是为什么下面的代码依然可以正确运行呢:
  1. public class MainActivity extends ActionBarActivity {
  2. Button btn = null;
  3. /** Called when the activity is first created. */
  4. public void onCreate(Bundle savedInstanceState) {
  5.   super.onCreate(savedInstanceState);
  6.   setContentView(R.layout.activity_main);
  7.   btn = (Button) findViewById(R.id.Button01);
  8.   TestThread2 t = new TestThread2(btn);
  9.   t.start();
  10. }
  11. class TestThread2 extends Thread {
  12.   Button btn = null;
  13.   public TestThread2(Button btn) {
  14.    this.btn = btn;
  15.   }
  16.   @Override
  17.   public void run() {
  18.    btn.setText("TestThread2.run");
  19.   }
  20. }
  21. }
复制代码
没错,可以执行!首先我们这里的是一条线程,其次这里要涉及framework对UI事件处理的方式,Android对UI事件的处理需要依赖于Message Queue,当一个Msg被压入MQ到处理这个过程并非立即的,它需要一段事件,我们在线程中通过Thread.sleep(200)在等,在等什么呢?在等ViewRootImpl的实例对象被创建.
那么最后总结一下:
到底子线程中能不能更新UI呢?答案是可以的,前提条件是它要拥有自己的ViewRoot
那ViewRoot什么时候建立的呢,
答案就是在Activity.onResume前,它是在ActivityThread.java的final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward)里创建的。ViewRoot实例没有建立,所以没有ViewRoot.checkThread检查。而btn.setText时设定的文本却保留了下来,所以当ViewRoot真正去刷新界面时,就把"TestThread2.run"刷了出来!



-------------------------------------------------------华丽的分割线------------------------------------------------------



看完这些解答后,才知道原来非UI线程在特定情况也能更新UI,反正我是信了。真心感谢就业指导老师的认真回答,认真查阅资料。






1 个回复

倒序浏览
赞赞赞赞赞赞赞赞赞赞赞赞
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马