【读书笔记】Android开发艺术探索

来源:互联网 发布:淘宝螺旋刷法 编辑:程序博客网 时间:2024/05/16 08:22

作者:张明云
链接:http://zhuanlan.zhihu.com/zmywly8866/20308214
来源:知乎

第一章:Activity的生命周期和启动模式

1、当前Activity的onPause方法执行结束后才会执行下一个Activity的onCreate方法,所以在onPause方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率;

2、如果新的Activity采用了透明主题,那么当前Activity的onStop方法不会被调用;

3、onSavedInstanceState和onRestoreInstanceState只会在Activity被异常终止的情况下被调用,正常情况下系统不会回调这两个方法,并且onRestoreInstanceState一旦被调用,其参数bundle必定为非空,不需要在方法内做空值判断;

4、View和Activity一样,每个View都有onSavedInstanceState和onRestoreInstanceState这两个方法,用于保存和恢复view的状态;

5、如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程就很容易被系统杀死,比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死;

6、避免屏幕旋转时Activity重启,可以在AndroidManifest.xml中对应Activity标签声明时加上“android:configChanges="orientation|screenSize"”即可;

7、Android有四种启动模式:standard、singleTop、singleTask和singleInstance。

  • standard:标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在;
  • singleTop:栈顶复用模式。在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前的请求信息。需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新的Activity的实例已经存在但不是位于栈顶,那么新的Activity仍然会重新创建;
  • singleTask:栈内复用模式。这是一种单实例模式,在这种情况下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent;
  • singleInstance:单实例模式,这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中。

8、Activity的Flags:

  • FLAG_ACTIVITY_NEW_TASK:这个标记位的作用是为Activity指定"singleTask"启动模式,其效果和在XML中指定该启动模式相同;
  • FLAG_ACTIVITY_SINGLE_TOP:这个标记位的作用是为Activity指定"singleTop"启动模式,其效果和在XML中指定该启动模式相同;
  • FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈;
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性"android:excludeFromRecents="true""。

第二章:IPC机制

1、给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest.xml中指定"android:process"属性可以在应用内实现多进程,如果进程名以":"开头,说明该进程属于私有进程,其他应用的组件不可以和它跑在同一个进程中,如果经常名不以":"开头,则属于全局进程,其它应用通过ShareUID方式可以和它跑在同一个进程中。

2、所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。使用多进程会造成如下几个方面的影响:

  • 静态成员和单例模式完全失效;
  • 线程同步机制完全失效;
  • SharedPreferences的可靠性下降;
  • Application会多次创建。

3、两个序列化类Parcelable和Serializable的区别:Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量的I/O操作。而Parcelable是Android中的序列化方式,因此更适合于用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高。Parceable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,此种情况建议使用Serializable。

4、在AIDL文件中,并不是所有的数据类型都是可以使用的,AIDL只支持如下六种数据类型:

  • 基本数据类型(int、long、char、boolean、double等);
  • String和CharSequence;
  • List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
  • Map:只支持HashMap,里面的每个元素都必需被AIDL支持,包括key和value;
  • Parcelable:所有事项了Parcelable接口的对象;
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。

其中自定义的Parcelable对象和AIDL对象必须要显式地import进来,不管它们时候和当前的AIDL文件位于同一个包内。

5、如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

6、Android中有Intent、文件共享、SharedPreferences、Binder通信、ContentProvider、网络通信等多种IPC通信机制,各种通信机制的特点如下:

  • Intent:Activity、Receiver、Service这三个组件都支持Intent通信,Intent适合于传递可序列化的数据;
  • 文件共享:文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
  • SharedPreferences:SharedPreferences也属于文件的一种(以键值对方式存储数据的一个xml文件),但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,SharedPreferences有很大几率会丢失数据,因此,不建议在进程间通信中使用SharedPreferences(MODE_MULTI_PROCESS)。
  • Binder通信:适用于进程间传递数据;
  • ContentProvider:使用于应用间共享数据,ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,这点和数据库很类似;虽然ContentProvider的底层数据看起来很像一个SQLite数据库,但是ContentProvider对底层的数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储。
  • 网络通信:网络数据交换。

7、不能在主线程中访问网络,因为这会导致我们的程序无法在Android 4.0及以上的设别中运行,会抛出如下异常:android.os.NetworkOnMainThreadException。

8、AIDL的创建流程:首先创建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端的Service,建立连接后就可以访问远程服务端的方法了。

第三章:View的事件体系

1、View的坐标都是相对于View的父容器来说的,因此它是一种相对坐标;

2、在Android中,x轴和y轴的正方向分别为右和下,不仅仅是Android,大部分显示系统都是按照这个标准来定义坐标系的;

3、MotionEvent提供了两组方法:getX/getY和getRawX/getRawY,getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标;

4、TouchSlop是系统所能识别出的被认为是滑动的最小距离,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。这是一个常量,和设备有关,在不同设备上这个值有可能是不同的,可以通过如下方式获取这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop()。但我们在处理滑动时,尅利用这个常量来对触摸操作做去抖处理;

5、VelocityTracker用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度;

6、GesturceDetector用于辅助检测用户的淡季、滑动、长按、双击等行为;

7、Scroller用于实现View的弹性滑动,Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能;

8、可以通过如下三种方式来实现View的滑动:

  • 第一种:通过View本身提供的scrollTo/scrollBy方法来实现滑动;特点是操作简单,适合对View内容的滑动;
  • 第二种:通过动画给View施加平移效果来实现滑动;特点是操作简单,适用于没有交互的View和实现复杂的动画效果;
  • 第三种:通过改变View的LayoutParams使得View重新布局从而实现滑动;操作稍微复杂,适用于有交互的View。

9、点击事件的分发过程由三个很重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent:

  • dispatchTouchEvent:用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和View的dispatchTouchEvent方法的影响,表示是否当消耗当前事件;
  • onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件;
  • onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

第四章:View的工作原理

1、View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。

2、DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(具体情况和Android版本及主体有关),上面的是标题栏,下面的是内容栏。在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,在代码中可以通过ViewGroup content = findViewById(R.android.id.content)来得到content对应的layout;

3、MeasureSpec将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程,SpecMode有三类:

  • UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态;
  • EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式;
  • AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

4、对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以快速地确定出子元素的MeasureSpec了,有了MeasureSpec就可以进一步确定出子元素测量后的大小。针对不同的父容器和View本身不同的LayoutParams,View就可以有多重MeasureSpec。当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayoutParams中的大小。当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精确还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。

5、直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content时就相当于使用match_parent。

6、在Activity的onCreate、onStart、onResume方法中均无法正确得到某个View的宽/高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View就已经测量完毕了,如果View还没有测量完毕,那么获得的宽/高就是0。

可以通过如下四个方法来解决获取View宽/高为0的问题:

  • 在Activity/View的onWindowFocusChanged方法(View已经初始化完毕了,宽/高已经准备好了)中获取View的宽高;
  • 在view.post(runnable)方法(将runnable投递到消息队列的尾部,等待Looper调用此runnable的时候,View也已经初始化好了)中获取View的宽高;
  • 使用ViewTreeObserver;
  • 手动调用View的measure方法;

7、在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的measure过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。多数情况下可以认为View的测量宽/高就等于最终的宽/高,但对于在View的layout中改变了View的left、top、right、bottom四个属性时,得出的测量宽/高有可能和最终的宽/高不一致;还有就是View需要多次measure才确定自己测量宽/高的情况时,在前几次的测量过程中,其得出的测量宽/高有可能和最终的宽/高不一致,但最终测量宽/高还是和最终宽/高相同。

8、View的draw流程如下:

  • 绘制背景(background.draw);
  • 绘制自己(onDraw);
  • 绘制children(dispatchDraw);
  • 绘制装饰(onDrawScrollBars)。

9、View有一个特殊的方法setWillNotDraw,如果一个View不需要绘制任何内容,设置这个标记位true后,系统会进行优化。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:如果自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当明确知道一个ViewGroup需要通过onDraw来绘制内容时,需要显示地关闭WILL_NOT_DRAW这个标记位。


持续阅读中,会不断更新,未完待续!

0 0
原创粉丝点击