Android开发艺术

  1. Activity从stop恢复至resume时,先restart,后start,再resume
  2. 两个activity切换时,前一个activity一定是先onpause,第二个activity才开始启动,也即onCreate,onstart,onresume,之后,后一个activity执行onstop;因此为了更快地切换两个activity,应该保证onpause执行更少的操作,因为,每一次切换activity,总是先onpause当前的activity
  3. 如果新的activity是半透明的状态,那么前一个activity不会进入stop,其实,在失去焦点后,activity就进入了暂停状态;进入暂停状态有两种,一种是半可见,一种是可见但失去焦点
  4. 当activity在意外情况下被销毁时:
    1. 当屏幕发生翻转时,activity会经过onpause,onstop,ondestroy方法,最终销毁,Android系统为了避免丢失数据,会调用onsaveInstanceState方法保存数据,该方法的调用时机是在onstop之前,与onpause的前后关系为未知,类似地,新建activity时,会在onstart完成后的某个时机,调用onrestoreIntancestate,恢复数据;需要注意的是,instancestate方法仅会在activity被重建时或者异常状态销毁时,被调用;被保存的bundle对象会被送至新建activity时的oncreate和onrestoreintance方法,因此我们可以通过检测这两个方法,从而判定activity是否被重建
    2. 保存数据时,activity采用上层委托下层,父类委托子类的方式,从上往下,经window,viewgroup,view保存view中的数据
    3. 当内存不足时,按照优先级顺序杀死activity,比如前台的running状态优先级最高,其次是处于暂停状态,最后是停止状态的activity;系统会优先杀死没有四大组件运行的进程,注意一个进程大多数情况下是一个application;因此,如果有重要的进程不想被杀死,应该制作一个服务保证高优先级
    4. 如果不想activity因为旋转屏幕被重建,可以在manifest中为activity增加configchanges属性增加orientation,代表方向的意思,此外也有一些其他参数;此时,activity不会重新创建,转而调用onConfigurationChange方法即可
  5. 任务栈的概念:长按home键后,会出现多个选项卡,我们当前只能处于一个任务栈中,也即前台任务栈,其他的后台处于停止状态的选项卡代表一个个后台任务栈
  6. Activity的四种启动模式:
    1. 标准模式:每次以当前上下文为参数,在本activity所在的任务栈中新建一个新的activity,如果传入applic`ation的context会报错
    2. Singletop:栈顶复用模式,如果已经位于栈顶,则不会重建,同时,onnewInrent方法被调用,然而oncreate,onstart,onpause不会会被调用;如果是标准模式则一定会创建
    3. Singletask模式:栈内复用模式,如果当前需要任务栈A,则会在任务栈A中搜索有没有需要启动的activity,如果有,则默认弹出其上面的所有栈,把该activity调至前台,并调用onNewIntent生命周期方法;如果需要任务栈B,如果B也不存在,则创建B并push一个新的activity
    4. SingleInstance模式:新的activity会被放入一个新的任务栈中,且activity只能单独处于一个任务栈中,因此如果继续新建,则不会重新创建这个activity,因为有栈内复用特性
    5. 注意:在singletask中,如果有两个任务栈,前台和后台任务栈,如果启动了后台任务栈中的activity,则为了复用该activity,后台的任务栈中的全部activity都会被push至前台任务栈,因此back时,一般会把原先后台的activity返回完之后,才会回到原先的activity;此外,所需的任务栈这个概念,所需的任务栈可以手动指定,以包名为唯一标识,该标识为一个taskAffinity参数,如果需要别的任务栈,需要手动指定包名
    6. *有一个有趣的现象:taskAffinity参数和allowtaskReparenting为true的时候,那么如果应用A调用了应用B,B的activity会出现在A任务栈中,此时切换回启动器页面,启动B应用会发现,显示的activity并不是B的mainActivity,而是A中调用的activity,此时B的任务栈把A的任务栈中属于B的activity直接搬回到了B任务栈
    7. Activity启动模式的指定:
      1. 有两种方法可以指定启动模式,一种是在manifest中,一种是在代码中addFlag方法增加标志位,优先级中,代码声明的方式优于manifest中的声明方式,但各自有限定范围,manifest无法指定activity clear top效果,而后者无法声明singleinstance启动模式
      2. Android的flag标记位:new_task,singletask模式;singletop即singletop模式;cleartop清栈操作,这个一般与new-task配合使用,但该标记位默认被启用;exclude_from_recent,从历史列表中不被发现
  1. Intentfilter筛选规则:
    1. 显式ntent需要提供包名和类名,包名的信息在contenx中;如果一个intent既是显式又是隐式,则应该以显式为准
    2. 隐式intent需要同时匹配action,category,data三种,intent filter可能会有多个,同时一个filter中也可以有多个action,category和data
    3. Action必须至少有一个匹配,没有action会匹配失败
    4. 每个intent默认会带有一个category,该category是一个intent filter中的DEFAULT的category,因此,过滤规则中必须包含一个Default的category,然后才是自定义的category;category的匹配和action不同,category可选,category参数是在默认情况下被添加,而不是覆盖自带的category;intent中增加的category必须是intentfilter中声明的category的子集,否则匹配失败
    5. Data元素由两部分构成,一部分是url,另一部分是mimeType,后者由两部分构成,一部分是媒体类别,另一部分是媒体格式,例如image/jpeg,表示图片类别中的jpeg格式;至于url,则分为下面几个部分:scheme,uri的模式,例如http,file等;hostname,如www.baidu.com;port端口;路径信息,一般以一个文件夹开始,如/folder/..;uri模式和hostname不可或缺,端口一般也是必须的,但有时候会有省略端口的情况,比如系统自动添加了默认端口
    6. Data的匹配规则:如果filter不指定uri模式,则默认为file或者file都可以适配,需要注意,一般需要调用setDataAndType;data的匹配规则和action类似
    7. 使用隐式intent时,首先应该检测是否有可用的activity,用packageManager的resolve或者queryactivity方法,后者返回所有的activity,只要返回不为null,则表示可以匹配成功,如果不检测,可能会导致匹配失败
    8. 匹配规则对于service和broadcast receiver也是使用的
  2. 开发中,对于java开发来说,一般只需要考虑多线程机制;而对于android来说,也是主要考虑一个application中的多线程机制,一个application一般只有一个进程
    1. 对于windows软件来说也一样,一个应用代表一个进程,如果应用支持多开,那么会出现多个相同的进程;但也有少数软件,比如chrome是多进程架构,它以消耗更多的资源换取速度,多进程就相当于一个软件打开多次,这样会加剧软件开发复杂度,因为不仅要维护多进程间的通信,也要维护多线程间的通信,我们一般开发中,是在一个进程内部做开发的,因此thread之类的代表线程,而非进程,java甚至很难实现多进程,因为没有相关的api;在操作系统级的编程中,可用调用系统函数,fork一个新的进程,这里简单提及一下,开发中并不常见
    2. 因此,IPC是指进程间通信,而非线程间的通信,此外,进程间的同步也不同于线程间的同步;一定要分清楚进程和线程的区别,进程工作在操作系统层次,调用操作系统的资源,而线程则只是调用进程内部的资源
    3. 然而,作为辅助知识,了解进程间通信,也是有必要的,尽管开发中创建进程的情况并不多,除非是操作系统级的开发,而非应用层的开发
    4. Android中的IPC多进程通信,实质上是多application之间的通信
    5. 简单来说,进程是十分有限的系统资源,而多线程共享这一份资源,是包含与被包含的关系,一个进程至少包含一个主线程,而在Android中,主线程就是UI线程,如果UI线程执行耗时的操作,那么可能会导致ANR机制
    6. 关于ANR机制:首先必须是主线程,其次是超时时间,最后是特定事件或操作,特定事件是指触屏或者按键,特定操作是指receiver和service的回调函数,事件超时时间是5s,而receiver是10s,而后台服务是20s
    7. 多终端之间的通信本质上也可以看作一种多进程间的通信,如果是跨设备间的多进程通信,socket协议是一种解决方案,当然,在一个设备中,使用socket通信也没有问题;在android中,contentprovider也是一种多进程通信,但其底层实现被掩盖了
  3. 在Android中,是可以开启单个应用的多进程模式,但其带来的负担有时候会大于其带来的益处,因此需要慎重,我们一般都采用单进程单app的模式
    1. 序列化与反序列化,实现serializable接口的对象可以完成序列化与反序列化,不论是进程间通信还是网络传输都需要该技术,serializable接口内部有一个serialversionUID用来判定类的版本,如果类的版本一致,才可以被反序列化;序列化操作,先新建输出流,然后调用输出流的writeObject方法,就可以把对象写入文件中;在反序列化这边,来一个输入流,调用其readObject方法,可以将对象反序列化
    2. 此外,parcelable接口也可以实现序列化和反序列化,虽然其相对于serializable接口使用相对复杂一些,但其在Android系统中工作性能要优于serializble,因此推荐使用该接口
    3. AIDL是指android接口定义语言,类似于corba中的IDL语言,定义接口后,就可以实现服务端和客户端的分布式交互信息,binder底层通过AIDL实现进程间通信
    4. Android进程间通信由几种方式:
      1. Bundle作为最简单的一种,bundle内部可以放入实现了序列化接口的类对象,如serializable和parcelable和一些android内置的一些特殊对象,而不被支持的对象无法放入bundle对象中,如果一个计算结果不支持序列化,则可以在计算前,启动另一个进程B也即app B的服务组件,代之在里面完成运算,这样巧妙地回避这个问题,后台服务可以启动真正的activity,并在进程内部共享运算结果
      2. 文件共享:可以把对象序列化后,再反序列化,这样可以完成进程间的文件共享,以达到进程间通信的目的;然而sharepreference等,由于其缓存机制,但会多进程下会发生数据丢失,因此不推荐
      3. ContentProvider方法,可以让别的进程或者叫应用临时访问自己的数据
      4. Socket套接字方法
      5. Messenger对象,messenger对象可以携带Message对象在进程间传递
  4. 布局的选择:
    1. 线性布局适合简单的布局,线性布局有一个orientation的方向参数,该参数默认为vertical垂直方向,他的意思是,内部的组件类似于堆栈的方式;而调整为水平horizontal的话,则相当于把堆栈横放
    2. 如果布局深度大,嵌套多,则应该采用相对布局以减少深度嵌套,提高性能
    3. 如果是帧嵌套结构,例如fragment的切换之类的,类似于幻灯片的效果,应该采用frameLayout,即帧布局
    4. 绝对布局已经被废弃
    5. ConstraintLayout是google推荐的布局,其相当于相对布局的改版,适合非常复杂的布局结构,且有良好的性能
    6. Gravity表示引力,与放置位置相关;而weight表示权重,与等比例划分有关
  5. RecyclerView和listview中,recyclerview可以把淡出的view进行复用,只需要更新数据即可,而listview则淡出的不能回收,新出现的必须重新生成,这很浪费内存
  6. Android五层架构:
    1. 系统应用层,如联系人,拨号软件等
    2. Java api framework层,也即java框架api层;分为两部分,左边是content providers内容提供程序和view system视觉系统;右边是一些管理器,例如activity Manager, Package Manager,notification Manager等
    3. Native jni库文件library层,包含一些webkit,opengles,media framework库文件等,该层为c/c++语言编写
    4. 与jni库文件层平级的为android runtime层,里面包含core library核心库,和art即android runtime虚拟机
    5. 再下一层为HAL,也即hardware abstract layer,硬件虚拟层,为每一个硬件设备创建一个库模块
    6. Linux内核层,保存所有的驱动程序和电源管理
    7. Ps :AMS即activity Manager Service
  7. 什么是view?
    1. view是所有的可视组件的共同父类,这包括具体的如单个view的button和布局容器的viewGroup,如线性布局容器等,因此如线性布局不仅是一个viewgroup,同时更是一个view
    2. view的位置坐标方式:Android中的坐标为相对坐标,即相对于父view的左上角为坐标原点,向右为x,向下为y,view以左上角和右下角确定该view的大小和位置,左上角为(left, top),右下角为(right, bottom)
    3. x, y是绝对坐标,而translationx,translationy为相对于坐标原点的组件的偏移量,x = left + translation
  8. motion event和touchslot
    1. 移动事件有:action_down,手指刚接触屏幕;action_move,手指在屏幕上移动;action_up,手指从屏幕上松开的瞬间;而一般我们操作有两种情况:down-up;down-move-up;
    2. Touchslot:最小滑动距离,小于此滑动距离,会被认为滑动失效,即不会被视作滑动,相关定义在framework源码中,可以getscaledtouchslot方法
  9. Velocitytracker:利用该对象可以获得手指的平均移动速度,分别为水平速度和垂直速度,获取速度前需要设定时间跨度,该对象根据时间跨度来计算平均速度并返回,使用完要记得手动回收该对象
  10. Gesterdetector:新建该对象并设定监听器,实现ongesterlistener接口,可以手动捕获当前的手势操作;由于view自身也有ontouchevent之类的回调函数,如果view自带的方法,则可以不借助手势检测器,除此之外,可以借助手势检测器来得到手势,如快速滑动flip,拖动scroll,长按,单击双击等
  11. 弹性滑动scroller,比如开关时的过渡效果,scroller类似于一个可以过渡动画的开关,类似于惯性滑动或自动过渡返回,类似于viewpager的切换时的自动滑动动画,实际上viewpager使用了scroller来实现该效果
  12. View的滑动效果:手机的动画效果基本上都由滑动+特效组成,因此滑动对于android开发来说十分重要,实现view滑动有三种方法:
    1. 使用scroll by和scrollto方法;前者scrollby的两个参数是移动距离,而后者是移动后的绝对坐标位置,而scrollby实际上调用了scrollto方法;view的滑动并非移动了该view在布局中的位置,而只是移动了其内容的位置,也即我们看到的view移动是假象,实际上,view的位置不懂,其可见的视觉元素发生了位移,控件往左移动为正,往右移动为负,正好和坐标系正负相反,mscrollx和mscrolly就是这样定义的,代表空间位置和内容位置的距离差值,换句话说,两个变量代表当前滑动的距离

注意:该方法不影响单击事件的发生

    1. 使用动画:有两种方式可以实现动画效果:一种是view动画,是在xml文件中,创建一个set标签,必须把fillafter设置为true,否则会导致view动画完成后瞬间回到原来的位置,再创建一个自定义translation标签,在里面设定动画参数,起始坐标,终点坐标,时间间隔等;另一种是属性动画,是在代码中实现的,但对于旧版本兼容性不好,objectAnimator对象的方法,最终start即可,为了兼容旧版本的属性动画,需要借助开源库,在3.0系统以下,本质上也是借助view动画实现的

注意:上述view动画的方式,由于只是移动影像,而非view本身,因此会导致触摸监听器失效,为了防止此类情况发生,有两种解决方法:1. 改用属性动画的方法;2. 在目标位置创建一个一模一样的button,移动完成后,隐藏原button,显示目标位置的button,两个button的监听器一模一样

    1. 创建一个宽度为0的view,获得该view的layoutparam对象,然后设置其宽度和高度等,然后重新设置参数即可,然后其会挤压别的view实现动画效果
    2. 在所有的动画方案中,view动画是最强大的,但缺点也很明显,会使触摸监听失效
  1. View的事件拦截机制:
    1. 类似于activity重建时保存数据时,从上级委托下级,先父类,后子类的方式,当前viewgroup首先接受到该事件,则onDispatchTouchEvent会被调用,该方法负责是否将触摸事件向下传递,该方法会调用view自身的以下两个方法
    2. 如果OnInterceptTouchEvent方法,该方法决定是否将事件向下传递,如果为true,则将事件拦截,在当前viewgroup中处理该事件,调用本viewgroup的onTouchEvent方法处理事件
    3. 如果OnInterceptTouchEvent方法返回false,则当前view的ontouchEvent方法不会调用,转而将事件传递给子view,传递方法当然是child.onDispatchTouchEvent
    4. 关于事件处理的优先级顺序:onTouchListener > onTouchEvent > onClickListener
    5. 关于事件处理函数onTouchEvent的处理事件流程:这里与拦截机制不同,拦截后必然要处理,而处理则是从当前view开始,如果返回false,则会交给其父组件处理,也即相当于递归调用,如果下属都选择不处理该事件,那么只能回到领导这里,但事件的分发机制则是由领导分发给下属的
    6. Viewpager在解决事件冲突上为我们解决了一部分,viewpager只会拦截左右滑动,而不会拦截上下滑动
    7. 所谓滑动冲突并非两个view同时响应一个事件,而是该事件被错误的view相应并消耗,而我们真正的view并没有收到该事件;事件处理有一个原则,从上往下委托,如果拦截,则必定要处理该事件,如果不拦截,那么该事件会继续分发,当前view没有机会再次处理该事件
    8. 外部拦截法:从父类中作拦截,父组件不拦截action-down
    9. 内部拦截法:父组件同样不拦截action-down,由于其不受标记位控制影响,标记为可以由子类调用ondisallowInterceptTouchEvent(false),即可让父类继续拦截action-down以外的事件
  2. Handler机制:handler本身不会创建子线程,创建子线程需要继承HandlerThread类,而且handler是可以自定义的
    1. 主进程默认会启动一个UI线程,而线程默认是不会启动handler的,主线程也即ActivityThread,我们的程序启动时,系统默认为我们启动了主线程的looper,因此,我们默认可以在主线程中使用handler,但子线程则不然,需要手动启动looper才可以;handler创建时会采用当前的looper系统,而要获得当前的looper,需要ThreadLocal对象
    2. 如果要更新UI,必须在主线程中,这也是handler的常用用途之一;由于UI对象是线程不安全的,因此必须保证只能在一条线程中访问UI,如果要多线程,则需要加锁,而加锁会拖慢性能,因此Android只允许在主线程中更新UI,在子线程中更新UI会抛出异常
    3. Handler内部包含一个looper和MessageQuene,创建时三者会协同工作
    4. Handler的postdelay方法,会立即将该runnable放入消息队列中,当到达该Message时,因为有延时,因此looper阻塞,如果此时又来一个Message,则会把它放入头部,而不是尾部,然后唤醒线程,处理新来的Message,之后,又会进入处理前一个message的等待
    5. 注意:由于创建handler时,handler会寻找当前进程的looper,因此在创建handler之前,必须确保looper已经启动
    6. Handler.obtainMessage方法比较繁琐,而实现runnable对象比较方便,只要调用post方法即可
    7. ThreadLocal内部用table数组存储,有两个用途:存储本线程中的数据,而不同线程中无法共享,因此可以从中获取到当前线程的looper;此外,threadlooper相当于线程内部的全局变量,诸如监听器之类的就可以在整个线程中方便传递
    8. MessageQuene尽管是一个消息队列,但实际上是一个链表结构,链表在插入和删除中效率较高,因此不用队列结构
    9. Looper实质上是一个死循环,调用Looper.prepare会创建一个looper但没有启动,只要创建了looper,那么就可以创建handler,调用Looper.loop可以启动死循环开启消息循环,如果要结束死循环,可以调用quitSafety方法
    10. Handler可以实现线程间通信,handler.sendmessage方法,可用向handler中放入消息发送,而handleMessage方法可以处理该消息
  3. 线程池:asyntask使用了线程池,而intentservice内部创建并使用了HandlerThread,因此内部具有消息队列,可以排队处理信息,因此其可以串行执行任务,直到所有任务执行完毕后,自动停止
  4. Binder在客户端和服务器扮演的角色:Binder实现了IBinder接口,客户端调用bindservice后,会得到服务端返回的一个Binder对象,因此,如果需要前台activity和后台service通信的话,显然bindservice提供了很大的便利;如果不用,则只能调用startService传输消息,如果要停止消息,调用stopService即可
  5. Android中实现事件的监听方式:
    1. 一种是设置view的监听器
    2. 另一种是在监听器的相应方法中,委托给gestureDetector处理,后者需要实现实现具体的监听器方法,并在方法中实现操作,创建手势检测器时,作为参数传递
    3. 重写view内部的onTouchEvent方法,手动从event对象中提取信息,比如坐标和速度之类的,手动用逻辑判断是哪种操作
  6. Android的多线程通信方式:
    1. 一种是handler,两大体系,一个post一个runnable对象,另一个是sendMessage方法,obtainMessage方法可以复用之前的对象,节省资源
    2. 另一种是asyntask,asyntask可以直接操作UI线程,而handler则不行;asyntask有四大方法,onPreExecute在UI线程中执行,而doInBackground则在工作线程中执行,而updateprogress和postExecute则是在UI线程中执行的;创建时三大参数:参数,进度,最终结果
    3. Asyntask底层是线程池实现,但在新版本中是串行运行,在旧版本中是并行多个线程执行,新版本中也可以并行,调用executeOnExecutor方法即可
  7. 应用crash的向服务器上报应用崩溃信息的实现:实现一个UncaughtExceptionHandler,然后继承application类,在onCreate方法中,初始化该handler,传入自定义的application对象;最后,要修改application的入口类,在androidManifest中的application属性中增加android:name指定自定义的.类名即可
  8. 解决方法数越界:方法数最多为65536个,一个是多dex方法, 另一个是动态加载的方法
  9. Android主线程也即UI线程会检测我们的触摸事件,然后该事件被分发,原则是从上往下传递分发,从下往上处理事件,传递时可能被拦截,而处理时,事件只能被一个view消耗,被消耗的事件返回上级时不会再次被处理
  10. 关于IntentService的源码:服务内部包含了一个handlerThread和handler类,在onCreate中,服务先是创建了线程handlerThread,而后获得该线程的looper对象,之后由looper对象构造handler,注意这里,handler与looper的绑定就在这一步实现,也即handler创造时,需要传入looper对象
  11. HandlerThread和普通Thread对象的区别就是handlerThread内部实现了消息队列,也即开启了looper,拥有handler对象,handler对象只会在与其绑定的线程中执行任务,这就为在子线程中通知主线程更新UI提供了可能,子线程只要拥有主线程handler对象的引用,就可以借助该handler传递消息,主线程的looper便会立即处理该消息
  12. 至于contentResolver的解析:先定义URI,该URI包含一个重要的authority授权参数,该授权参数是区别不同的contentProvider的唯一ID,此外,还包含content://前缀等,之后得到contentRedsolver对象后,即可调用查询函数获得cursor对象,从cursor对象中即可获得相应的查询信息

猜你喜欢

转载自blog.csdn.net/jiayuqicz/article/details/88615663