Android-小知识

来源:互联网 发布:数据库系统概论王珊ppt 编辑:程序博客网 时间:2024/06/13 06:18

Android-小知识


Android-小知识


Activity界面加载过程解析

  1. Activity-->setContentView -> mWindow -> setContentView(window的setContentView方法)

  2. mWindow初始化:
    attach -> mWindow -> PolicyManager.makeNewWindow(获得window对象) --> policy.makeNewWindows{return PhoneWindow(context)}

  3. PhoneWindow构造函数如下:

PhoneWindow(Context context){    super(context);    mLayoutInflater = LayoutInflater.from(context);}

最终调用的方法是PhoneWindow中的setContentView方法

public void setContentView(int layoutResID) {    if (mContentParent == null) {        installDecor();    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {        mContentParent.removeAllViews();    }    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,                getContext());        transitionTo(newScene);    } else {        mLayoutInflater.inflate(layoutResID, mContentParent);    }    final Callback cb = getCallback();    if (cb != null && !isDestroyed()) {        cb.onContentChanged();    }}

另外需要注意的是,invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用 requestLayout()了。这个方法中的流程比invalidate()方法要简单一些,但中心思想是差不多的。

findViewById的流程

-> activity.findViewById -> getWindow().findViewById(id) -> window.findViewById -> getDecorView().findViewById(id) -> installDecor() -> generateDecor() -> generateDecor() -> DecorView(context,-1){PhoneWindow的内部类 extends FrameLayout}-> view.findViewById()-> view.findViewTraversal()
/** * findViewById的起点匹配View中的mID变量 * @param id the id of the view to be found * @return the view of the specified id, null if cannot be found */protected View findViewTraversal(int id) {    if (id == mID) {        return this;    }    return null;}

View和ViewGroup

invalidate() 触发view重绘

在布局文件中设置大小属性失败原因

为什么我们在布局文件中直接改变一个按钮的宽度,如按钮的宽度改成300dp,高度改成80dp,但重新运行程序来观察效果,却发现并没有什么变化。

其实不管你将Buttonlayout_widthlayout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_widthlayout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_widthlayout_height,而不是width和height。

布局加载

inflate()的作用就是将一个用xml定义的布局文件加载出来,注意与findViewById()的区别,inflate是加载一个布局文件,而findViewById则是从布局文件中查找一个控件。

  1. 获取LayoutInflater对象有三种方法
LayoutInflater inflater=LayoutInflater.from(this);LayoutInflater inflater=getLayoutInflater();LayoutInflater inflater=(LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
  1. 关于LayoutInflater类inflate(int resource, ViewGroup root, boolean attachToRoot)方法三个参数的含义:

    • resource:需要加载布局文件的id,意思是需要将这个布局文件中加载到Activity中来操作。
    • root:需要附加到resource资源文件的根控件,什么意思呢,就是inflate()会返回一个View对象,如果第三个参数attachToRoot为true,就将这个root作为根对象返回,否则仅仅将这个root对象的LayoutParams属性附加到resource对象的根布局对象上,就是返回resource对象实例化的组件。
    • attachToRoot:是否将root附加到布局文件的根视图上(如果传true,系统会多创建一个多余的root来装载需要加载的组件,即传true返回root,传false返回resource加载的组件。)

UI小知识

如何监听EditText的文本变化

关键函数public void addTextChangedListener(TextWatcher watcher)

在Activity启动的时候去获取某个View的dimension信息

因为View的measure过程和Activity的生命周期方法不是同步执行的,所以无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕。这个时候,三个周期方法拿到的dimension可能都是0。下面有四个方法可以解决这个问题:

  1. Activity/View#onWindowFocusChanged
    onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽/高已经准备好。但有个问题,当前Activity得到或失去焦点时 都会导致onWindowFocusChanged被调用,所以当Activity频繁切换的时候,这个方法会被频繁调用。
  2. view.post(runnable)
    通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用,但被调用到的时候,View已经初始化好了。
  3. ViewTreeObserver
    使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当View
  4. 在重写view.measure方法,获取View的宽度(PS:虽然测量的宽度并不一定是View最后的宽度,但一般都是最终的宽度)

Activity基础

activity是什么

Activity是用于与用户交互的组件,所以Activity会提供窗口来和用户进行交互。
Activity全都是用task来管理的,一个task里有多个activity,这些activity按照启动的顺序存入task中(后进先出)。Android默认会为每个应用创建一个task来管理该应用的activity,task的名字一般都是应用的包名packagename

我们可用通过AndroidManifest.xml中activity的taskAffinity属性来单独定义管理该activity的task,但如果其他应用申明了相同的task,就有可能启动到该activity,就会带来安全问题(显而易见,这里Intent被其他应用获取到)。

Activity的生命周期
Activity的生命周期方法

  • 打开一个Activity实例的时候,系统会依次调用:
onCreate ==> onStart ==> onResume ==> 取得焦点进入交互状态
  • 当其他activity启动或者锁屏的时候
    activity依旧在前台运行,onPause被调用。

该方法执行activity暂停,通常用于提交未保存的更改到持久化数据,停止动画和其他的东西。但这个activity还是完全活着(它保持所有的状态和成员信息,并保持连接到窗口管理器)

  • 接下来 这个activity有三种可能
      用户返回到该activity就调用onResume()方法重新取得焦点    //    //======用户回到桌面或是打开其他activity,调用onStop()进入停止状态(对用户不可见)   \\    \\      系统内存不足,拥有更高限权的应用需要内存,该activity的进程就可能会被系统回收。(回收onPause()和onStop()状态的activity进程)要想重新打开就必须重新创建一遍。
  • 如果用户重新回到onStop()状态的activity(又显示在前台了),如下生命周期方法会被调用

onRestart ==> onStart ==> onResume

  • activity结束(主动调用finish ())或是被系统杀死之前会调用onDestroy()方法释放所有占用的资源。

  • activity生命周期中三个嵌套的循环

    • activity的完整生存期会在 onCreate() 调用和 onDestroy() 调用之间发生。
    • activity的可见生存期会在 onStart() 调用和 onStop() 调用之间发生。系统会在activity的整个生存期内多次调用 onStart() 和onStop(),因为activity可能会在显示和隐藏之间不断地来回切换。
    • activity的前后台切换会在 onResume() 调用和 onPause() 之间发生。 因为这个状态可能会经常发生转换,为了避免切换迟缓引起的用户等待,不要添加耗时操作。

activity被回收的状态和信息保存和恢复过程

onSaveInstanceState方法

在activity可能会被回收前调用,用于保存相关状态和信息,以便于回收后现场重建时的数据恢复(在onRestoreInstanceStateonCreate中恢复)。但处于onPauseonStop状态的activity,被回收的时候不一定会被调用。

系统灵活的来决定调不调用该方法,但是如果要调用就一定发生在onStop方法之前,但并不保证发生在onPause的前面还是后面。

onRestoreInstanceState方法

这个方法在onStartonPostCreate之间调用,在onCreate中也可以状态恢复,但有时候需要所有布局初始化完成后再恢复状态。

onPostCreate:一般不实现这个方法,当程序的代码开始运行时,它调用系统做最后的初始化工作。

启动模式

启动模式的作用

决定activity和task之间的关系。具体一些作用:

  • 让某个 activity 启动一个新的 task (而不是被放入当前 task )
  • 让 activity 启动时只是调出已有的某个实例(而不是在 back stack 顶创建一个新的实例) 
  • 你想在用户离开 task 时只保留根 activity,而 back stack 中的其它 activity 都要清空

四种启动模式对应的作用

  • standard:
    这种模式下,Android总会为目标 Activity创建一个新的实例,并将该Activity添加到当前Task栈中。(不会启动新的栈)
  • singleTop:
    该模式和standard模式基本一致,有一点不同:当将要被启动的Activity已经位于Task栈顶时,系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。
  • singleTask:
    保持task中只有一个实例,如果要启动的activity不存在,那么系统将会创建该实例,并将其加入Task栈顶;如果已存在,直接复用Task内的Activity;如果没有位于栈顶,那么系统会把位于该Activity上面的所有其他Activity全部移出Task,从而使得该目标Activity位于栈顶。
  • singleInstance
    用该模式启动的Activity,系统将会创建一个全新的Task栈来装载该实例(全局单例)。如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个Task中;系统都会把该Activity所在的Task转到前台,从而使该Activity显示出来。

如何使用

通过 manifest 文件
通过manifest文件在activity声明时,添加launchMode 属性来设定 activity 与 task 的关系。这种方式只是设定启动模式,但任然能被启动activity Intent修改。

 <activity    ...    android:launchMode="standard">    ...    </activity>

使用 Intent
在要启动 activity 时,你可以在传给 startActivity() 的 intent 中包含相应标志,以修改 activity 与 task 的默认关系。

Intent i = new Intent(this,NewActivity.class);i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(i);

与启动模式对应:

  • FLAG_ACTIVITY_NEW_TASK — singleTask
  • FLAG_ACTIVITY_SINGLE_TOP — singleTop
  • FLAG_ACTIVITY_CLEAR_TOP — 与singleTask类型
    此种模式在launchMode中没有对应的属性值。如果要启动的 activity 已经在当前 task 中运行,则不再启动一个新的实例,且所有在其上面的 activity 将被销毁。