├── Android ├── Android 动画学习笔记 │ ├── Android动画之 Property Animation.md │ ├── Android动画之Frame Animation.md │ └── Android动画之Tween Animation.md ├── RxJava 学习笔记 │ ├── RxJava 学习笔记2.md │ ├── RxJava 学习笔记3.md │ └── 与RxJava 1.x的差异.md ├── 《Android 进阶之光》读书笔记 │ └── 函数响应式编程.md ├── 《Android开发艺术探索》笔记 │ ├── Activity的生命周期和启动模式.md │ ├── Android 动画深入分析.md │ ├── Android 的 Drawable.md │ ├── Android 的消息机制.md │ ├── Android 的线程和线程池.md │ ├── Android性能优化.md │ ├── Bitmap 的加载和 Cache.md │ ├── IPC机制.md │ ├── JNI 和 NDK 编程.md │ ├── View 的工作原理.md │ ├── View的事件体系.md │ ├── 四大组件的工作过程.md │ ├── 理解 RemoteViews.md │ ├── 理解 Window 和 WindowManager.md │ └── 综合技术.md └── 笔记 │ ├── Android 中常见的内存泄漏.md │ ├── Android 自定义 View 的知识点.md │ ├── Android中的Theme.md │ ├── Android面试题整理.md │ ├── GreenDao 的简单使用.md │ ├── Service小记.md │ └── 单例总结.md ├── ES6笔记 ├── ES6 声明变量的六种方法.md ├── const.md ├── let.md ├── 变量的解构赋值.md └── 字符串的扩展.md ├── Git └── Git.md ├── Java ├── Java基础知识.md └── 《Java编程思想》笔记 │ ├── 第10章 内部类.md │ ├── 第11章 持有对象.md │ ├── 第12章 通过异常处理错误.md │ ├── 第13章 字符串.md │ ├── 第14章 类型信息.md │ ├── 第15章 泛型.md │ ├── 第16章 数组.md │ ├── 第19章 枚举类型.md │ ├── 第20章 注解.md │ ├── 第4章 控制执行流程.md │ ├── 第5章 初始化与清理.md │ ├── 第6章 访问权限控制.md │ ├── 第7章 复用类.md │ ├── 第8章 多态.md │ └── 第9章 接口.md ├── README.md ├── kotlin └── kotlin 笔记.md └── 微信小程序笔记 └── JSON 配置.md /Android/Android 动画学习笔记/Android动画之 Property Animation.md: -------------------------------------------------------------------------------- 1 | # Android 动画之 Property Animation 2 | 3 | 属性动画是控制属性来实现动画。最为强大的动画,弥补了补间动画的缺点,实现位置+视觉的变化。并且可以自定义插值器,实现各种效果 4 | 5 | ## Property Animation相关类 6 | |类名|用途| 7 | |----|:-| 8 | |ValueAnimator|属性动画主要的计时器,也计算动画后的属性的值,动画的执行类| 9 | |ObjectAnimator|ValueAnimator的一个子类,允许你设置一个目标对象和对象的属性进行动画,动画的执行类| 10 | |AnimatorSet|提供组织动画的结构,使它们能相关联得运行,用于控制一组动画的执行| 11 | |AnimatorInflater|用户加载属性动画的xml文件| 12 | |Evaluators|属性动画计算器,告诉了属性动画系统如何计算给出属性的值| 13 | |Interpolators|动画插入器,定义动画的变化率| 14 | 15 | 关系如下: 16 | 17 | ![](http://www.lightskystreet.com/img/propertyviewanalysis/12.png) 18 | 19 | ## 被定义在XML文件中 20 | 如果是被定义在 XML 中,需要注意是的属性动画文件存放目录为 res/animator,文件名可以作为资源 ID 在代码中引用, 21 | 22 | xml 代码: 23 | 24 | ````xml 25 | 26 | 28 | 37 | 45 | 46 | 47 | ... 48 | 49 | 50 | ```` 51 | **注意:** XML 文件的根元素必须为 或者 , or 。你也可以在一个 set 中放置不同的动画,来嵌套其他元素。 52 | 53 | ### 元素介绍 54 | 动画集合节点,有个 ordering 属性,表示它的子动画启动方式是先后有序还是同时。 55 | 56 | |属性|说明| 57 | |-|-| 58 | |sequentially|动画按照先后顺序| 59 | |together(default)|动画同时启动| 60 | 61 | ### 62 | 属性: 63 | 64 | - android:propertyName:String类型,必须要设定的值,代表要执行动画的属性,通过名字引用,比如你可以指定了一个View的”alpha” 或者 backgroundColor”,这个objectAnimator元素没有暴露target属性,因此比不能够在XML中执行一个动画,必须通过调用loadAnimator() 填充你的XML动画资源,并且调用setTarget() 应用到拥有这个属性的目标对象上。 65 | - android:valueTo:Float、int或者color,也是必须值,表明了动画结束的点,颜色由6位十六进制的数字表示。 66 | - android:valueFrom:相对应valueTo,动画的起始点,如果没有指定,系统会通过属性身上的get方法获取,颜色也是6位十六进制的数字表示。 67 | - android:duration:动画的时长,int类型,以毫秒为单位,默认为300毫秒。 68 | - android:startOffset:动画延迟的时间,从调用start方法后开始计算,int型,毫秒为单位, 69 | - android:repeatCount:一个动画的重复次数,int型,”-1“表示无限循环,”1“表示动画在第一次执行完成后重复执行一次,也就是两次,默认为0,不重复执行。 70 | - android:repeatMode:重复模式:int型,当一个动画执行完的时候应该如何处理。该值必须是正数或者是-1, 71 | “reverse”会使得按照动画向相反的方向执行,可实现类似钟摆效果。“repeat”会使得动画每次都从头开始循环。 72 | - android:valueType:关键参数,如果该value是一个颜色,那么就不需要指定,因为动画框架会自动的处理颜色值。 73 | 有intType和floatType两种:分别说明动画值为int和float型。 74 | 75 | ### 76 | 在一个特定的时间里执行一个动画。相对应的是 ValueAnimator 所有的属性和一样 77 | 78 | - android:valueTo 79 | - android:valueFrom 80 | - android:duration 81 | - android:startOffset 82 | - android:repeatCount 83 | - android:repeatMode 84 | - android:valueType 85 | 86 | valueType 的值有两种: 87 | 88 | - intType 89 | - floatType(default) 90 | 91 | 举个例子 92 | ````xml 93 | 94 | 95 | 100 | 105 | 106 | 110 | 111 | ```` 112 | 为了执行该动画,必须在代码中将该动画资源文件填充为一个AnimationSet对象,然后在执行动画前,为目标对象设置所有的动画集合。 113 | 简便的方法就是通过setTarget方法为目标对象设置动画集合,代码如下: 114 | ````java 115 | AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,R.anim.property_animator); 116 | set.setTarget(myObject); 117 | set.start(); 118 | ```` 119 | ## 由编码实现 120 | ### ObjectAnimator 对象 121 | 对于java代码实现,ObjectAnimator 提供了以下几个方法:ofFloat(),ofInt(),ofObject(),ofArgb(),ofPropertyValuesHolder()这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。 122 | 举个例子: 123 | 124 | 以渐变效果为例: 125 | ````java 126 | 127 | ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f); 128 | objectAnimator.setDuration(500); 129 | objectAnimator.setRepeatCount(1); 130 | objectAnimator.setRepeatMode(ValueAnimator.REVERSE); 131 | objectAnimator.start(); 132 | ```` 133 | 134 | ### ValueAnimator(差值动画) 135 | ValueAnimator是ObjectAnimator的父类,它继承自抽象类Animator,它作用于一个值,将其由一个值变化为另外一个值,然后根据值的变化,按照一定的规则,动态修改View的属性,比如View的位置、透明度、旋转角度、大小等,即可完成了动画的效果。 136 | 137 | 示例: 138 | ````java 139 | ValueAnimator valueAnimator =ValueAnimator.ofFloat( 0f, 126512.36f); 140 | valueAnimator.setDuration(2000); 141 | valueAnimator.setInterpolator(new LinearInterpolator()); 142 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 143 | @Override 144 | public void onAnimationUpdate(ValueAnimator animation) { 145 | float money= (float) animation.getAnimatedValue(); 146 | mTextView.setText(String.format("%.2f", money)); 147 | } 148 | }); 149 | valueAnimator.start(); 150 | ```` 151 | 这里通过ofFloat()方法构造一个ValueAnimator实例,除此之外还提供了其他函数ofInt()、ofObject()、ofPropertyValuesHolder()函数,api 21之后又提供了ofArgb(),每个函数都是可以传入多个改变值。 152 | 153 | ## 实现一个组合动画 154 | 举例我们同时对一个控件进行宽高两个维度的缩放 155 | 156 | 方式一:使用 AnimatorSet 157 | 158 | ````xml 159 | 160 | 162 | 170 | 178 | 179 | 180 | ```` 181 | 加载xml动画 182 | ````java 183 | Animator anim = AnimatorInflater.loadAnimator(this, R.animator.animator_scale); 184 | anim.setTarget(imageView); 185 | anim.start(); 186 | ```` 187 | 纯Java代码实现: 188 | ````java 189 | AnimatorSet animatorSet = new AnimatorSet(); 190 | 191 | ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(imageView, "scaleX", 1f, 1.5f); 192 | scaleXAnimator.setDuration(500); 193 | scaleXAnimator.setRepeatCount(1); 194 | scaleXAnimator.setRepeatMode(ValueAnimator.REVERSE); 195 | scaleXAnimator.start(); 196 | 197 | ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(imageView, "scaleY", 1f, 1.5f); 198 | scaleYAnimator.setDuration(500); 199 | scaleYAnimator.setRepeatCount(1); 200 | scaleYAnimator.setRepeatMode(ValueAnimator.REVERSE); 201 | 202 | animatorSet.playTogether(scaleXAnimator, scaleYAnimator); 203 | animatorSet.start(); 204 | ```` 205 | 上述代码通过playTogether函数实现两个动画同时执行,如果不想同时执行,也可以调用play函数返回AnimatorSet.Builder实例,AnimatorSet.Builder提供了如下几个函数用于实现动画组合: 206 | 207 | - after(Animator anim) 将现有动画插入到传入的动画之后执行 208 | - after(long delay) 将现有动画延迟指定毫秒后执行 209 | - before(Animator anim) 将现有动画插入到传入的动画之前执行 210 | - with(Animator anim) 将现有动画和传入的动画同时执行 211 | 212 | 也可以调用playSequentially函数实现分布执行动画。 213 | 214 | 215 | 方式二:使用PropertyValuesHolder 216 | 217 | ````java 218 | PropertyValuesHolder scaleXValuesHolder = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.5f); 219 | PropertyValuesHolder scaleYValuesHolder = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 1.5f); 220 | ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(imageView, scaleXValuesHolder, scaleYValuesHolder); 221 | objectAnimator.setDuration(500); 222 | objectAnimator.setRepeatCount(1); 223 | objectAnimator.setRepeatMode(ValueAnimator.REVERSE); 224 | objectAnimator.start(); 225 | 226 | ```` 227 | 通过这种方式只能实现同时执行的动画组合相比AnimatorSet就没那么丰富了,PropertyValuesHolder 提供的函数方法有如下几种:ofInt()、ofFloat()、ofObject()、ofKeyframe()。 228 | 229 | 方式三:使用ViewPropertyAnimator 230 | ````java 231 | ViewPropertyAnimator viewPropertyAnimator=imageView.animate(); 232 | viewPropertyAnimator.scaleXBy(1.0f).scaleX(1.5f).scaleYBy(1.0f).scaleY(1.5f).setDuration(500).start(); 233 | ```` 234 | 多属性动画,作用于View,能够实现的动画相对单一,只能实现比如缩放,透明度改变,平移、旋转等,具体函数名字:平移 translationX,translationY, X,Y,缩放 scaleX,scaleY, 旋转 rotationX, rotationY,透明度 alpha 235 | 236 | 237 | ## 设置动画监听器 238 | 有时候我们可能要在某一个动画执行之前 或者动画结束之后进行一些其他的操作,这个时候就要借助动画监听器了。 239 | 240 | ````java 241 | objectAnimator.addListener(new Animator.AnimatorListener() { 242 | @Override 243 | public void onAnimationStart(Animator animation) { 244 | //TODO 动画开始前的操作 245 | } 246 | 247 | @Override 248 | public void onAnimationEnd(Animator animation) { 249 | //TODO 动画结束的操作 250 | } 251 | 252 | @Override 253 | public void onAnimationCancel(Animator animation) { 254 | //TODO 动画取消的操作 255 | } 256 | 257 | @Override 258 | public void onAnimationRepeat(Animator animation) { 259 | //TODO 动画重复的操作 260 | } 261 | }); 262 | ```` 263 | 如果我们需要简单动画执行过程中的变化可以使用AnimatorUpdateListener 264 | ````java 265 | objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 266 | @Override 267 | public void onAnimationUpdate(ValueAnimator animation) { 268 | float value = (float) animation.getAnimatedValue(); 269 | //可以根据自己的需要来获取动画更新值。 270 | Log.e("AnimatorUpdateListener", "the animation value is " + value); 271 | } 272 | }); 273 | ```` 274 | -------------------------------------------------------------------------------- /Android/Android 动画学习笔记/Android动画之Frame Animation.md: -------------------------------------------------------------------------------- 1 | # Android 动画之 Frame Animation 2 | Frame动画是一系列图片按照一定的顺序展示的过程,和放电影的机制很相似,我们称为逐帧动画。Frame动画可以被定义在XML文件中,也可以完全编码实现。 3 | 4 | ## 被定义在XML文件中 5 | 如果是被定义在 XML 中,可以放置在 /res 下的 anim 或 drawable 目录中 (/res/[anim|drawable]/filename.xml),文件名可以作为资源 ID 在代码中引用, 6 | 语法如下: 7 | ````xml 8 | 9 | 11 | 14 | 15 | ```` 16 | **注意:** 17 | 18 | 元素是必须的,并且必须作为根元素,可以包含一个或多个 元素;android:onshot 如果定义为 true 的话,此动画只会执行一次,如果为 false 则一直循环。 19 | 20 | 代表一帧动画,android:drawable 指定此帧动画所对应的图片资源,android:duration 代表此帧持续的时间,整数,单位为毫秒。 21 | 22 | **问题:** 23 | 24 | 为什么在 onCreate() 方法中执行动画会失败? 25 | > 这种现象是因为当我们在 onCreate 中调用 AnimationDrawable 的 start 方法时,窗口 Window 对象还没有完全初始化,AnimationDrawable 不能完全追加到窗口 Window 对象中,那么该怎么办呢?我们需要把这段代码放在 onWindowFocusChanged 方法中,当 Activity 展示给用户时,onWindowFocusChanged 方法就会被调用,我们正是在这个时候实现我们的动画效果。当然,onWindowFocusChanged 是在 onCreate 之后被调用的。 26 | 27 | 28 | 29 | 30 | 31 | 32 | ## 由编码实现 33 | 如果完全由编码实现,我们需要用到 AnimationDrawable 对象。 -------------------------------------------------------------------------------- /Android/Android 动画学习笔记/Android动画之Tween Animation.md: -------------------------------------------------------------------------------- 1 | # Android 动画之 Tween Animation 2 | Tween动画是操作某个控件让其展现出旋转、渐变、移动、缩放的这么一种转换过程,我们成为补间动画。我们可以以XML形式定义动画,也可以编码实现。 3 | ## 以XML形式定义动画 4 | 如果以XML形式定义一个动画,我们按照动画的定义语法完成XML,并放置于/res/anim目录下,文件名可以作为资源ID被引用; 5 | ````xml 6 | 7 | 10 | 13 | 20 | 25 | 30 | 31 | ... 32 | 33 | 34 | ```` 35 | xml 文件中必须有一个根元素,可以是中的任意一个,也可以来管理一个由前面几个组成的动画的集合。 36 | 37 | ### 38 | 是一个动画容器,管理多个动画的群组,与之相对性的 Java 对象是 AnimationSet。有两个属性: 39 | 40 | - android:interpolator ---- 代表一个插值器资源,可以引用系统自带查之前资源,也可以用自定义插值器资源,默认值是匀速插值器。 41 | - android:shareInterpolator ---- 代表多个动画是否要共享插值器,默认值为 true,即共享插值器;如果设置为 false,那么插值器就不再起作用,需要在每个动画中加入插值器。 42 | 43 | ### 44 | 是渐变动画,可以实现 fadeIn 和 fadeOut 的效果,与之对应的 Java 对象是 AlphaAnimation。 45 | 46 | - android:fromAlpha 属性代表起始 alpha 值,浮点值,范围在 0.0-1.0 之间,分别代表透明和完全不透明。 47 | - android:toAlpha 属性代表结尾 alpha 值,浮点值,范围在 0.0-1.0 之间。 48 | 49 | ### 50 | 是缩放动画,可以实现动态调整控件尺寸的效果,与之对应的 Java 对象是 ScaleAnimation。 51 | 52 | - android:fromXScale 属性代表起始的 X 方向上相对自身的缩放比例,浮点值。1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍。 53 | - android:toXScale 属性代表结尾的X方向上相对自身的缩放比例,浮点值。 54 | - android:fromYScale 属性代表起始的Y方向上相对自身的缩放比例,浮点值。 55 | - android:toYScale 属性代表结尾的Y方向上相对自身的缩放比例,浮点值。 56 | - android:pivotX 属性代表缩放的中轴点X坐标,浮点值。 57 | - android:pivotY 属性代表缩放的中轴点Y坐标,浮点值, 58 | 59 | 如果我们想表示中轴点为图像的中心,我们可以把 **android:pivotX** 和 **android:pivotY** 两个属性值定义成0.5或者50%。 60 | 61 | ### 62 | 是位移动画,代表一个水平、垂直的位移。 63 | 64 | - android:fromXDelta 属性代表起始X方向的位置; 65 | - android:toXDelta 代表结尾X方向上的位置; 66 | - android:fromYScale 属性代表起始Y方向上的位置; 67 | - android:toYDelta 属性代表结尾Y方向上的位置。 68 | 69 | 以上四个属性都支持三种表示方式:浮点数、num%、num%p; 70 | 71 | - 如果以浮点数字表示,代表相对自身原始位置的像素值; 72 | - 如果以 num% 表示,代表相对于自己的百分比,比如 toXDelta 定义为100%就表示在 X 方向上移动自己的1倍距离; 73 | - 如果以 num%p 表示,代表相对于父类组件的百分比。 74 | 75 | ### 76 | 是旋转动画,与之对应的 Java 对象是 RotateAnimation。 77 | 78 | - android:fromDegrees 属性代表起始角度,浮点值,单位:度; 79 | - android:toDegrees 属性代表结尾角度,浮点值,单位:度; 80 | - android:pivotX 属性代表旋转中心的X坐标值, 81 | - android:pivotY 属性代表旋转中心的Y坐标值, 82 | 83 | 这两个属性也有三种表示方式: 84 | 85 | - 数字方式代表相对于自身左边缘的像素值, 86 | - num% 方式代表相对于自身左边缘或顶边缘的百分比, 87 | - num%p 方式代表相对于父容器的左边缘或顶边缘的百分比。 88 | 89 | 另外,在动画中,如果我们添加了android:fillAfter="true"后,这个动画执行完之后保持最后的状态;android:duration="integer"代表动画持续的时间,单位为毫秒。 90 | 91 | 如果要把定义在XML中的动画应用在一个ImageView上,代码是这样的: 92 | ````xml 93 | ImageView image = (ImageView) findViewById(R.id.image); 94 | Animation testAnim = AnimationUtils.loadAnimation(this, R.anim.test); 95 | image.startAnimation(testAnim); 96 | ```` 97 | 98 | ### 插值器 99 | 在补间动画中,我们一般只定义关键帧(首帧或尾帧),然后由系统自动生成中间帧,生成中间帧的这个过程可以成为“插值”。插值器定义了动画变化的速率,提供不同的函数定义变化值相对于时间的变化规则,可以定义各种各样的非线性变化函数,比如加速、减速等。下面是几种常见的插值器: 100 | 101 | |Interpolator对象|资源ID|功能作用| 102 | |--|:-:|:-:| 103 | |AccelerateDecelerateInterpolator|@android:anim/accelerate_decelerate_interpolator| 先加速再减速| 104 | |AccelerateInterpolator|@android:anim/accelerate_interpolator|加速| 105 | AnticipateInterpolator|@android:anim/anticipate_interpolator|先回退一小步然后加速前进| 106 | AnticipateOvershootInterpolator|@android:anim/anticipate_overshoot_interpolator|在上一个基础上超出终点一小步再回到终点 107 | |BounceInterpolator|@android:anim/bounce_interpolator最后阶段弹球效果| 108 | |CycleInterpolator|@android:anim/cycle_interpolator|周期运动| 109 | |DecelerateInterpolator|@android:anim/decelerate_interpolator|减速| 110 | |LinearInterpolator|@android:anim/linear_interpolator匀速| 111 | |OvershootInterpolator|@android:anim/overshoot_interpolator|快速到达终点并超出一小步最后回到终点| 112 | 113 | #### 使用插值器: 114 | ````xml 115 | 116 | ... 117 | 118 | ```` 119 | 120 | ````xml 121 | 123 | ```` 124 | 125 | #### 修改插值器 126 | 有时候会不满足现有的插值器,就可以试试个性化插值器。我们可以创建 XML 资源文件,然后将其放于 /res/anim 下,然后在动画元素中引用。 127 | 几种常见的插值器可调整的属性: 128 | 129 | - 无; 130 | - android:factor 浮点值,加速速率,默认为1; 131 | - android:tension 浮点值,起始点后退的张力、拉力数,默认为2; 132 | - android:tension 同上 android:extraTension浮点值,拉力的倍数,默认为1.5(2 * 1.5); 133 | - 无; 134 | - android:cycles 整数值,循环的个数,默认为1; 135 | - android:factor 浮点值,减速的速率,默认为1; 136 | - 无; 137 | - 浮点值,超出终点后的张力、拉力,默认为2; 138 | 139 | 举个例子: 140 | ````xml 141 | 142 | 144 | ```` 145 | 然后引用; 146 | ````xml 147 | 150 | ```` 151 | 152 | 我们也可以实现 Interpolator 接口,因为所有的 Interpolator 都实现了 Interpolator 接口,这个接口定义了一个方法: 153 | > float getInterpolation(float input); 154 | 155 | 此方法由系统调用,input 代表动画的时间,在0和1之间,也就是开始和结束之间。 156 | 举个例子: 157 | 158 | 性(匀速)插值器定义如下: 159 | ````java 160 | public float getInterpolation(float input) { 161 | return input; 162 | } 163 | ```` 164 | 加速减速插值器定义如下: 165 | ````java 166 | public float getInterpolation(float input) { 167 | return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; 168 | } 169 | 170 | ```` 171 | 172 | 173 | ## 由编码实现动画 174 | 如果由编码实现,我们需要使用到 Animation 对象。与之对应的Java对象是TranslateAnimation。 175 | -------------------------------------------------------------------------------- /Android/RxJava 学习笔记/RxJava 学习笔记2.md: -------------------------------------------------------------------------------- 1 | # RxJava 学习笔记2 2 | Observable : 被观察者。 3 | Observer : 观察者;有时也叫做 Subscriber、Watcher、Reactor。 4 | 5 | ## 异步模型中的流程 6 | 1. 定义一个方法,它完成某些任务,然后从异步调用中返回一个值,这个方法是观察者的一部份 7 | 2. 将这个异步调用本身定义为一个 Observable 8 | 3. 观察者通过订阅(Subscribe)操作关联到那个 Observable 9 | 4. 继续你的业务逻辑,等方法返回时,Observable 会发射结果,观察者的方法会开始处理结果或结果集 10 | 11 | ```` 12 | def myOnNext = {it -> do something useful with it}; 13 | 14 | def myObservable = someObservable(itsParameters); 15 | 16 | myObservable.subscribe(myOnNext); 17 | ```` 18 | 19 | ## 回调方法(onNext,onCompleted,onError) 20 | Subscribe 方法用于将观察者连接到 Observable,你的观察者需要实现以下方法的一个子集: 21 | 22 | - onNext(T item) 23 | Observable 调用这个方法发射数据,方法的参数就是 Observable 发射的数据,这个方法可能会被调用多次。 24 | - onError(Exception ex) 25 | 当 Observable 遇到错误或者无法返回期望的数据时会调用这个方法,这个调用会终止 Observable,后续不会再调用 onNext 和 onCompleted,onError 方法的参数是抛出的异常。 26 | - onComplete 27 | 正常终止,如果没有遇到错误,Observable 在最后哦一次调用 onNext 之后调用此方法。 28 | 根据 Observable 协议的定义,onNext 可能会被调用零次或者很多次,最后会有一次 onCompleted 或者 onError 调用(不会同时),传递数据给 onNext 通常被称作发射,onCompleted 和 onError 被称作通知。 29 | ```` 30 | def myOnNext = {item -> /* do something useful with item */}; 31 | 32 | def myError = {throwable -> /*react sensibly to a failed call*/}; 33 | 34 | def myComplete = {/* clean up after the final response */}; 35 | 36 | def myObserable = someMethod(itsParameters); 37 | 38 | myObservable.subscribe(myOnNext,myError,myComplete); 39 | ```` 40 | 41 | ## 取消订阅(Unsubscribing) 42 | 取消订阅的结果会传递这个 Observable 的操作符链,而且会导致这个链条上的每个环节都停止发射数据项。这些并不保证会立即发生,然而,对一个 Observable 来说,及时没有观察者了,它也可以在一个 while 循环中继续生产并尝试发射数据项。 43 | 44 | ## 操作符 45 | 1. 创建操作:Create , Defer , Empty/Never/Throw , From , Interval , Just , Range , Repeat , Start , Timer 46 | 2. 变换操作: Buffer , FlatMap , GroupBy , Map , Scan , Window 47 | 3. 过滤操作: Debounce , Distinct , ElementAt , Filter , IgnoreElements , Last , Sample , Skip , SkipLast , Take , TakeLast 48 | 4. 组合操作: And/Then/When , CombineLatest , Join , Merge , StartWith , Switch , Zip 49 | 5. 错误处理: Catch , Retry 50 | 6. 辅助操作: Delay , Do , Materialize/Dematerialize , ObserbeOn , Serialize , Subscribe , SubscribeOn , TimeInterval , Timeout , Timestamp , Using 51 | 7. 条件和布尔操作: All , Amb , Contains , DefaultlfEmpty , SequeneceEqual , SkipUntil , SkipWhile , TakeUntil , TakeWhile 52 | 8. 算术和集合操作: Average , Concat , Count , Max , Min , Reduce , Sum 53 | 9. 转换操作: To 54 | 10. 连接操作: Connect , Publish , RefCount , Replay 55 | 11. 反压操作: 用于增加特殊的流程控制策略的操作符。 56 | 57 | ## RxJava 58 | 在 RxJava 中,一个实现了 Observable 接口的对象可以订阅(subscribe) 一个 Observable 类的实例,订阅者(subscriber)对 Observable 发射(emit)的任何数据或者序列作出响应,这种模式简化了并发操作,因为它不需要阻塞等待 Observable 发射数据,而是创建一个处于待命状态的观察者哨兵,哨兵在未来某个时刻响应 Observable 的通知。 -------------------------------------------------------------------------------- /Android/RxJava 学习笔记/RxJava 学习笔记3.md: -------------------------------------------------------------------------------- 1 | # RxJava 学习笔记3 2 | 3 | ## Scheduler(调度器) 4 | 如果想给 Observable 操作符链添加多线程功能,可以指定操作符(或者特定的 Observable) 在特定的调度器(Scheduler) 上执行。 5 | 6 | 使用 ObserveOn 和 SubscribeOn 操作符,你可以让 Observable 在一个特定的调度器上执行,ObserveOn 指示一个 Observable 在一个特定的调度器上调用观察者的 onNext,onError 和 onCompleted 方法,SubscribeOn 更进一步,它指示 Observable 将全部的处理过程(包括发射数据和通知)放在特定的调度器上执行。 7 | 8 | ### 调度器的种类 9 | |调度器类型|效果| 10 | |:-------- |:--| 11 | |Schedulers.computation()|用于计算任务,如事件循环或回调处理,不要用于 IO 操作(IO 操作请使用 Schedulers.io());默认线程数等于处理器的数量| 12 | |Schedulers.from(executor)|使用指定的 Executor 作为调度器| 13 | |Schedulers.immediate()|在当前线程立即开始执行任务| 14 | |Schedulers.io()|用于 IO 密集型任务,如异步阻塞 IO 操作,这个调度器的线程池会根据需要增长;对于普通的计算任务,请使用 Schedulers.computation();Schedulers.io() 默认是一个 CachedThreadScheduler,很像一个有线程缓存的新线程调度器| 15 | |Schedulers.newThread()|为每个任务创建一个新线程| 16 | |Schedulers.trampoline()|当其它排队的任务完成后,在当前线程排队开始执行| -------------------------------------------------------------------------------- /Android/RxJava 学习笔记/与RxJava 1.x的差异.md: -------------------------------------------------------------------------------- 1 | 2 | - Nulls 3 | 这是一个很大的变化,RxJava 1.x 是允许我们在发射事件的时候传入 null 值的,但现在我们的 2.x 不支持了,这意味着 Observable 不再发射任何值,而是正常结束或者抛出空指针。 4 | - Flowable 5 | 在 RxJava 1.x 中关于介绍 backpressure 部分有一个小小的遗憾,那就是没有用一个单独的类,而是使用 Observable 。而在 2.x 中 Observable 不支持背压了,将用一个全新的 Flowable 来支持背压。 6 | 或许对于背压,有些小伙伴们还不是特别理解,这里简单说一下。大概就是指在异步场景中,被观察者发送事件的速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略。感兴趣的小伙伴可以模拟这种情况,在差距太大的时候,我们的内存会猛增,直到OOM。而我们的 Flowable 一定意义上可以解决这样的问题,但其实并不能完全解决,这个后面可能会提到。 7 | - Single/Completable/Maybe 8 | 其实这三者都差不多,Single 顾名思义,只能发送一个事件,和 Observable接受可变参数完全不同。而 Completable 侧重于观察结果,而 Maybe 是上面两种的结合体。也就是说,当你只想要某个事件的结果(true or false)的时候,你可以使用这种观察者模式。 9 | - 线程调度相关 10 | 这一块基本没什么改动,但 RxJava 2.x 中已经没有了 Schedulers.immediate() 这个线程环境,还有 Schedulers.test()。 11 | - Function相关 12 | 熟悉 1.x 的小伙伴一定都知道,我们在1.x 中是有 Func1,Func2.....FuncN的,但 2.x 中将它们移除,而采用 Function 替换了 Func1,采用 BiFunction 替换了 Func 2.....FuncN。并且,它们都增加了 throws Exception,也就是说,妈妈再也不用担心我们做某些操作还需要 try-catch 了。 13 | - 其他操作符相关 14 | 如 Func1...N 的变化,现在同样用 Consumer 和 BiConsumer 对 Action1 和 Action2 进行了替换。后面的 Action 都被替换了,只保留了 ActionN。 15 | 16 | ![](https://upload-images.jianshu.io/upload_images/3994917-14f7e368b8e0596b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 17 | ![](https://upload-images.jianshu.io/upload_images/3994917-b447bbabccff5506.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 18 | ![](https://upload-images.jianshu.io/upload_images/3994917-9863b5f713ac86d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 19 | ![](https://upload-images.jianshu.io/upload_images/3994917-28ce3ee8a0ccdcf5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 20 | ![](https://upload-images.jianshu.io/upload_images/3994917-548c743caff7c3e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 21 | ![](https://upload-images.jianshu.io/upload_images/3994917-f20109ef808f04dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 22 | ![](https://upload-images.jianshu.io/upload_images/3994917-93f5aee82d8fc8fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 23 | ![](https://upload-images.jianshu.io/upload_images/3994917-4e8d8b566c245606.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/700) 24 | 25 | -------------------------------------------------------------------------------- /Android/《Android 进阶之光》读书笔记/函数响应式编程.md: -------------------------------------------------------------------------------- 1 | # 函数响应式编程 2 | >这本书里使用的是RxJava 1。 3 | 4 | 函数式编程是一种编程范式;响应式编程是一种面向数据流和变化传播的编程范式,数据更新是相关联的。 5 | ## RxJava 基本用法 6 | 1. RxJava 概述 7 | 基本用法 8 | - 创建 Observer(观察者) 9 | ````java 10 | Subscriber subscriber = new Subscriber{ 11 | 12 | @Override 13 | public void onCompleted(){ 14 | Log.d(TAG,"onCompleted"); 15 | } 16 | 17 | @Override 18 | public void onError(Throwable e){ 19 | Log.d(TAG,"onError"); 20 | } 21 | 22 | @Override 23 | public void onNext(String s){ 24 | Log.d(TAG,"onNext"); 25 | } 26 | 27 | @Override 28 | public void onStart(){ 29 | Log.d(TAG,"onStart"); 30 | } 31 | } 32 | ```` 33 | 其中,onCompleted、onError 和 onNext 是必须要实现的方法。 34 | - onCompleted: 35 | 事件队列完结。当不会有新的 onNext 发出时,需要触发 onCompleted() 方法作为完成标志。 36 | - onError: 37 | 事件队列异常。在事件处理过程中出现异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。 38 | - onNext: 39 | 普通的事件。将要处理的事件添加到事件队列中。 40 | - onStart: 41 | 会在事件还未发送之前被调用,可以用于一些准备工作,如数据的清零或重置。 42 | - 创建 Observable(被观察者) 43 | ````java 44 | Observable observable = Observable.create(new Observable.onSubscrible(){ 45 | @Override 46 | public void call(Subscriber subscriber){ 47 | subscriber.onNext("杨影枫"); 48 | subscriber.onNext("月眉儿"); 49 | subscriber.onCompleted(); 50 | } 51 | }); 52 | ```` 53 | 也可以用 from 来实现 54 | ````java 55 | String[] words = {"杨影枫","月眉儿"}; 56 | Observable observable = Observable.from(words); 57 | ```` 58 | - Subscribe(订阅) 59 | ````java 60 | observable.subscribe(subscriber); 61 | ```` 62 | 63 | 2. RxJava 的不完整定义回调 64 | Action 后的数字代表回调的参数类型数量 65 | 66 | ````java 67 | Action1 onNextAction = new Action1(){ 68 | @Override 69 | public void call(String s){ 70 | Log.d(TAG,"onCompleted"); 71 | } 72 | }; 73 | 74 | Action1 onErrorAction = new Action1(){ 75 | @Override 76 | public void call(Throwable throwable){ 77 | 78 | } 79 | }; 80 | 81 | Action0 onCompletedAction = new Action0(){ 82 | @Override 83 | public void call(){ 84 | Log.d(TAG,"onNext" + s); 85 | } 86 | }; 87 | 88 | observable.subscribe(onNextAction,onErrorAction,onCompleteAction); 89 | ```` 90 | 定义 onNextAction 来处理 onNext 回调,定义 onErrorAction 来处理 onError 的回调,定义 onCompletedAction 来处理 onCompleted 的回调,最后传给 subscribe 方法。 91 | 92 | ## RxJava 的Subject 93 | 1. Subject 既可以是一个 Observer,也可以是一个 Observable,是连接着 Observer 和 Observable 的桥梁。Subject = Observable + Observer。 94 | 2. RxJava 提供了4种 Subject: 95 | - PublishSubject 96 | PublishSubject 只会把在订阅发生时间点之后来自原始 Observable 的数据发射给观察者。PublishSubject 可能会一创建完成就立刻发射数据,因此有在 PublishSubject 被创建后到有观察者订阅它之前这个时间段内,一个或多个数据可能会丢失的风险。 97 | - BehaviorSubject 98 | 当 Observer 订阅 BehaviorSubject 时,它开始发射原始 Observable 最近发射的数据。还没有收到数据则会发射一个默认值,然后继续发射来自其他任何来自原始 Observable 的数据。如果原始的 Observable 发生一个错误而终止,BehaviorSubject 将不会发射任何数据,但会向 Observer 传递一个异常通知。 99 | - ReplaySubject 100 | 不管 Observer 何时订阅 ReplaySubject,ReplaySubject 均会发射所有来自原始 Observable 的数据给 Observer。 101 | - AsyncSubject 102 | 当 Observer 完成时,AsyncSubject 只会发射来自原始 Observable 的最后一个数据。如果原始的 Observable 发生一个错误而终止,AsyncSubject 将不会发射任何数据,但会向 Observer 传递一个异常通知。 103 | 104 | ## RxJava 操作符入门 105 | 1. 创建操作符 106 | - interval:创建一个固定时间间隔发射整数序列的 Observable,相当于定时器。 107 | ````java 108 | Observable.interval(3,TimeUnit.SECONDS) 109 | .subscribe(new Action1(){ 110 | @Override 111 | public void call(Long mLong){ 112 | Log.d(TAG,"INTERVAL:" + mLong.intValue()); 113 | } 114 | }); 115 | ```` 116 | - range:创建发射指定范围的整数序列的 Observable,可以代替 for 循环,发射一个范围内的有序整数序列。第一个参数是起始值,并且不小于0,第二个参数为终值,左闭右开。 117 | ````java 118 | Observable.range(0,5) 119 | .subscribe(new Action1(){ 120 | @Override 121 | public void call(Integer integer){ 122 | Log.d(TAG,"range:" + integer.intValue()); 123 | } 124 | }); 125 | ```` 126 | - repeat:创建一个 N 次重复发射特定数据的 Observable。 127 | ````java 128 | Observable.range(0,3) 129 | .repeat(2) 130 | .subscribe(new Action1(){ 131 | @Override 132 | public void call(Integer integer){ 133 | Log.d(TAG,"repeat:" + integer.intValue()); 134 | } 135 | }); 136 | ```` 137 | 138 | 2. 变换操作符 139 | - map:能通过指定一个 Func 对象,将 Observable 转换为一个新的 Observable 对象并发射,观察者将收到新的 Observable 处理。 140 | - flatMap、cast: 141 | + flatMap 操作符将 Observable 发射的数据集合变换为 Observable 集合,然后将这些 Observable 发射的数据平坦化地放进一个单独的 Observable。 142 | + cast 操作符的作用是强制将 Observable 发射的所有数据转换为指定类型。 143 | - concatMap:与 flatMap 的功能一致,解决了 flatMap 交叉问题,提供了一种能够把发射的值连续在一起的函数,而不是合并。 144 | - flatMapIterable:将数据包装成 Interable。 145 | - buffer:将源 Observable 变换为一个新的 Obserable,这个新的 Obserable 每次发射一组列表值而不是一个一个发射。 146 | - groupBy:用于分组元素,将源 Observable 变换成一个发射 Observable 的新 Observable(分组后)。它们中的每一组新 Observable 都发射一组指定的数据。 147 | 148 | 3. 过滤操作符 149 | - filter:是对源 Observable 产生的结果自定义规则进行过滤,只有满足条件的结果才会提交给订阅者。 150 | - elementAt:用来返回指定位置的数据。 151 | - distinct:用来去重,只允许还没有发射过的数据项通过。 152 | - skip、take: 153 | + skip 操作符将源 Observable 发射的数据过滤掉前 n 项; 154 | + take 操作符只取前 n 项。 155 | - ignoreElements:忽略所有源 Observable 产生的结果,只把 Observable 的 onCompleted 和 onError 事件通知给订阅者。 156 | - throttleFirst:会定期发射这个时间段里源 Observable 发射的第一个数据。默认在 computation 调度器上执行。 157 | - throttleWithTimeOut:通过时间来限流,源 Observable 每次发射出来一个数据后就会进行计时。 158 | 159 | 4. 组合操作符 160 | - startWith:会在源 Observable 发射的数据前面插上一些数据。 161 | - merge:将多个 Observable 合并到一个 Observable 中进行发射,可能会让合并的 Observable 发射的数据交错。 162 | - concat:将多个 Observable 发射的数据进行合并发射,concat 严格按照顺序发射数据。 163 | - zip:合并两个或者多个 Observable 发射出的数据项,根据指定的函数变换它们,并发射一个新值。 164 | - combineLastest:当两个 Observable 中的任何一个发射了数据时,使用一个函数结合每个 Observable 发射的最近数据项,并且基于这个函数的结果发射数据。 165 | 166 | 5. 辅助操作符 167 | - delay:让原始 Observable 在发射每项数据之前都暂停一段指定的时间段。 168 | - Do:Do 系列操作符就是为原始 Observable 的生命周期事件注册一个回调,当 Observable 的某个事件发生时就会调用这些回调。 169 | + doOnEach:当 Observable 每次发射一项数据时就好调用它一次,包括 onCompleted、onError 和 onNext。 170 | + doOnNext:只有执行 onNext 的时候会被调用。 171 | + doOnUnsubscribe:当观察者取消订阅 Observable 时就会被调用;Observable 通过 onError 或者 onComplete 结束时,会取消订阅所有的 Subscriber。 172 | + doOnCompleted:当 Observable 正常终止调用 onCompleted 时会被调用。 173 | + doOnError:当 Observable 异常终止调用 onError 时会被调用。 174 | + doOnTerminate:当 Observable 终止(无论是正常还是异常终止)之前会被调用。 175 | + finallyDo:当 Observable 终止(无论是正常还是异常终止)之后会被调用。 176 | - subscribeOn、observeOn: 177 | + subscribeOn用于指定 Observable 自身在哪个线程上运行。 178 | + observeOn 用来指定 Observer 所运行的线程。 179 | - timeout:在原始 Observable 过了指定的一段时长没有发射任何数据,timeout 会以一个 onError 通知终止这个 Observable,或者继续执行一个备用的 Observable。 180 | 181 | 6. 错误处理操作符 182 | - catch:拦截原始 Observable 的 onError 通知,将它替换为其他数据项或数据序列,让产生的 Observable 能够正常终止或者根本不终止。 183 | RxJava 将 catch 实现为以下3个不同的操作符: 184 | - onErrorReturn:Observable 遇到错误时返回原有 Observable 行为的备用 Observable,备用 Observable 会忽略原有 Observable 的 onError 调用,不会将错误传递给观察者。作为替代,会发射一个特殊的项并调用观察者的 onCompleted 方法。 185 | - onErrorResumeNext:Observable 遇到错误时返回原有 Observable 行为的备用 Observable,备用 Observable 会忽略原有 Observable 的 onError 调用,不会将错误传递给观察者。作为替代,会发射备用 Observable 的数据。 186 | - onExceptionResumeNext:和 onErrorResumeNext 类似,不同的是如果 onError 收到的 Throwable 不是一个 Exception,它会将错误传递给观察者的 onError 方法,不会使用备用的 Observable。 187 | - retry:不会将原始 Observable 的 onError 通知传递给观察者,会订阅这个 Observable,再给它一次机会无错误地完成其数据序列。 188 | 189 | 7. 条件操作符和布尔操作符 190 | - 布尔操作符: 191 | + all:根据一个函数对源 Observable 发射的所有数据进行判断,最终返回的结果就是这个判断结果。 192 | + contains、isEmpty: 193 | * contains:用来判断源 Observable 所发射的数据是否包含某一个数据。 194 | * isEmpty:用来判断源 Observable 是否发射过数据。 195 | - 条件操作符: 196 | + amb:对于给定两个或多个 Observable,只发射首先发射数据或通知的那个 Observable 的所以数据。 197 | + defaultIfEmpty:发射来自原始 Observable 的数据。如果原始 Observable 没有发射数据,就发射一个默认数据。 198 | 199 | 8. 转换操作符 200 | - toList:将发射多项数据且为每一项数据调用 onNext 方法的 Observable 发射的多项数据组合成一个 List,然后调用一次 onNext 方法传递整个列表。 201 | - toSortedList:类似于 toList,不同的是会对产生的列表排序,默认是自然升序。 202 | - toMap:收集原始 Observable 发射的所有数据项到一个 Map(默认是 HashMap),然后发射这个Map。 203 | 204 | ## RxJava 的操作符 205 | 1. 内置的 Scheduler 206 | - Schedulers.immediate():直接运行在当前进程,是 timeout、timeInterval 和 timestamp 操作符的默认调度器。 207 | - Schedulers.newThread():总是启用新线程,并在新线程执行操作。 208 | - Schedulers.io():I/O操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是用一个无数量上限的线程池,可以重用空闲的线程,有效率。 209 | - Schedulers.computation():计算所使用的 Scheduler,这个 Scheduler 使用固定线程池,大小为 CPU 的核数。不要把 I/O 操作放在 computation()中,否则 I/O 操作的等待时间会浪费 CPU。是 buffer、debounce、delay、interval、sample 和 skip 操作符的默认调度器。 210 | - Schedulers.trampoline():当我们想在当前线程执行一个任务时,并不是立即时,可以用 .trampoline() 将它入队。这个调度器将会处理它的队列并且按序运行队列中的每一个任务。是 repeat 和 retry 操作符默认的调度器。 211 | 另外,RxAndroid 也提供了一个 Scheduler: 212 | - AndroidSchedulers.mainThread() --- RxAndroid 库中提供的 Scheduler,它指定的操作在主线程中运行。 213 | 2. 控制线程 214 | 在 RxJava 中用 subscribeOn 和 observeOn 操作符来控制线程。 215 | 216 | 217 | 218 | 219 | 2017.9.2 220 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Activity的生命周期和启动模式.md: -------------------------------------------------------------------------------- 1 | # Activity的生命周期和启动模式 2 | ## Activity的生命周期 3 | 4 | #### 典型情况下的生命周期(指在有用户参与的情况下,Activity所经过的生命周期的改变)。 5 | --- 6 | - onCreate:表示Activity正在被创建,是生命周期的第一个方法。这个方法里可以做一些初始化工作。 7 | - onRestart:表示Activity正在重新启动。一般当当前Activity从不可见重新变为可见状态时会调用这个方法。如按下Home键或者打开一个新的Acticity,会执行onPause,onStop方法,接着又回到这个Activity,就会调用这个方法。 8 | - onStar:表示Activity正在被启动,即将开始。此时Activity已经可见,但还没有出现在前台,无法和用户交互。 9 | - onResume:表示Activity已经可见,并且出现在前台并开始活动。 10 | - onPause:表示Activity正在停止,正常情况下会紧接着调用onStop(特殊情况是如果这时候快速回到当前Activity,会调用onResume)。此时可以做一些存储数据,停止动画等工作,但不能太耗时。 11 | - onStop表示Activity即将停止。可以做一些稍微重量级的回收工作,但也不能太耗时。 12 | - onDestroy:表示Activity即将停止,是Activity生命周期中的最后一个回调。可以做一些回收工作和最终的资源释放。 13 | ![Activity生命周期的切换过程](http://7xq2jk.com1.z0.glb.clouddn.com/Activity%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%9A%84%E5%88%87%E6%8D%A2%E8%BF%87%E7%A8%8B.jpg) 14 | 15 | 附加说明: 16 | --- 17 | - 针对一个特定的Activity,第一次启动,回调如下:onCreate→onStart→onResume。 18 | - 当用户打开新的Activity或者切换到桌面,回调如下:onPause→onStop。有一种特殊情况是如果新的Activity采用了透明主题,那么当前的Activity不会回调onStop。 19 | 当用户再次回到原Activity时,回调如下:onRestart→onStart→onResume。 20 | - 当按Back键回退时,回调如下:onPause→onStop→onDestory。 21 | - 当Activity被系统回收后再次打开,生命周期方法回调过程和正常一样,但只是生命周期方法一样,不代表所有过程都一样。 22 | - 从整个生命周期来说,onCreate和onDestroy是配对的,分别标志着Actovity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart和onStop是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。 23 | 24 | #### 异常情况下的生命周期(指Activity被系统回收或者由于设备的Configuration发生改变从而导致Activitu被销毁重建) 25 | --- 26 | 27 | - 资源相关的系统配置发生改变导致Activity被杀死并重新创建。 28 | >默认情况下,如果Activity不做特殊处理,当系统配置发生改变后,Activity就好被销毁并重新创建,其生命周期如下。 29 | 30 | ![异常情况下Activity的重建过程](http://7xq2jk.com1.z0.glb.clouddn.com/%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5%E4%B8%8BActivity%E7%9A%84%E9%87%8D%E5%BB%BA%E8%BF%87%E7%A8%8B.png) 31 | 32 | onSaveInstanceState方法只会在Activity被异常终止的情况下调用,调用时机是在onStop之前,但和onPause没有既定的时序关系,可能在onPause之前调用,也可能在onPause之后调用。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。onRestoreInstanceState方法在onStart方法之后调用。 33 | 34 | - 关于保存和恢复View层次结构,系统工作的流程是: 35 | + 首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据 36 | + 然后Activity会委托Window去保存数据 37 | + 接着Window在委托它上面的顶级容器去保存数据,顶级容器是一个ViewGroup,一般来说很可能是一个DecorView 38 | + 最后顶层容器再去一一通知它的子元素来保存数据 39 | - 接收的位置选择onRestoreInstanceState或onCreate的区别: 40 | >onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定是有价值的,不需要额外地判断是否为空;但是onCreate不行吗,onCreate如果正常启动的话,其参数Bundle savedInstanceState为null,所以必须额外判断。通时官方建议建议使用onRestoreInstanceState去恢复数据。 41 | - 针对onSaveInstanceState方法,需要说明的是系统只会在Activity即将被销毁并且有机会重新显示的情况下才会回去调用。 42 | 43 | - 资源内存不足导致低优先级的Activity被杀死。 44 | 45 | 1. Activity按照优先级从高到低,分为如下三种: 46 | - 前台Activity---正在和用户交互的Activity,优先级最高; 47 | - 可见但非前台Activity---比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互; 48 | - 后台Activity---已经被暂停的Activity,比如执行了onStop,优先级最低。 49 | >当系统内存严重不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。另外一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程容易被杀死,比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,从而不会轻易地被系统杀死。 50 | 51 | 2. 解决当系统配置发生改变后,Activity会被重建的问题 52 | 如果当某项内容发生改变后,不想系统重建Activity,可以给Activity指定configChanges属性。 53 | 比如不想让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation值。 54 | ```` 55 | android:configChanges = "orientation" 56 | ```` 57 | 系统会去调用Activity的onConfigurationChangesd方法。 58 | 3.系统配置的项目和含义 59 | ![configChanges的项目和含义](http://7xq2jk.com1.z0.glb.clouddn.com/configchanges%E7%9A%84%E9%A1%B9%E7%9B%AE%E5%92%8C%E5%90%AB%E4%B9%89.jpg) 60 | 61 | >注意: 62 | screenSize和smallestScreenSize两个比较特殊,它们的行为和编译选项有关,和运行环境无关。 63 | 64 | ## Activity的启动模式 65 | 1. Android四种启动模式: 66 | - standard: 标准模式,是系统的默认模式,每次启动一个Activity都会重新创建一个新的实例,即使实例已经存在。创建的实例的生命周期符合典型情况下的Activity生命周期。一个任务栈中可以有多个实例,每个实例可以属于不同的任务栈。谁启动了Activity,那这个Activity,就在启动它的那个Activity所在的栈中运行。当用ApplicationContext去启动standard模式的Activity时就会出错,因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但Context属于非Activity类型,并没有所谓的任务栈。解决方法是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,为它在启动的时候创建一个新的任务栈,但这时候实际是以singleTask模式启动。 67 | >当我们使用ApplicationContext去启动standard模式的Activity的时候会报错,因为非Activity类型的Context并没有所谓的任务栈,解决的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会创建一个新的任务栈,其实这时候待启动的Activity实际上是以singleTask模式启动的。 68 | - singleTop: 栈顶复用模式。在这模式下,若新的Activity已经位于任务栈的栈顶,那么此Activity就不会被重新创建,同时它的onNewIntent方法会被回调,我们可以通过此方法取出当前请求的信息。另外,该Activity的onCreat和onStart方法不会被系统调用。如果新Activity的实例已存在,但不位于栈顶,仍然会被重建。 69 | - singleTask: 栈内复用模式。是一种单实例模式,只要Activity在一个栈中存在,即使多次启动Activity都不会重建实例,系统也会回调其onNewIntent方法。另外,singleTask模式的Activity切换到栈顶会导致它之上的栈内的Activity出栈。 70 | ![singleTask示例](http://7xq2jk.com1.z0.glb.clouddn.com/singleTask.png) 71 | - singleInstance: 单实例模式。是一种加强的singleTask模式,具有此种模式的Activity只能单独位于一个任务栈中,并且由于栈内复用的特性,后续的请求均不会创建新的Activity,除非任务栈被系统销毁。 72 | 2. Activity所需任务栈。 73 | 首先从**TaskAffinity**这个参数说起,这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有的Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singletTask启动模式或者allowTaskReparenting属性配对使用。另外任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态。 74 | 当TaskAffinity和singletTask启动模式配对使用的时候,是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。 75 | 当TaskAffinity和allowTaskReparenting结合的时候,会产生特殊的效果。当一个应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那个当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。 76 | 3. Activity指定启动模式的方法。 77 | 78 | - 第一种是通过AndroidManifest为Activity指定启动模式。 79 | ````java 80 | 85 | ```` 86 | - 第二种是通过在Intent中设置标志位来为Activity指定启动模式。 87 | ````java 88 | Intent intent = new Intent(); 89 | intent.setClass(ManiActivity.this,SecondActivity.class); 90 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 91 | startActivity(intent); 92 | ```` 93 | 区别: 94 | - 首先,优先级上第二种方式要高于第一种。当两种同时存在时,以第二种方式为准。 95 | - 其次,两种方式在限定范围上有所不同,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法为Activity指定singleInstance模式。 96 | >Tips: 97 | singleTask模式的Activity切换到栈顶会导致在它之上的栈内的Activity出栈。 98 | 99 | 4. Activity的Flags 100 | 101 | - FLAG_ACTIVITY_NEW_TASK 102 | >这个标记位的作用是为Activity我都指定“singleTask”启动模式,其效果和在XML中指定该启动模式相同。 103 | - FLAG_ACTIVITY_SINGLE_TOP 104 | >这个标记的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同。 105 | - FLAG_ACTIVITY_CLEAR_TOP 106 | >具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动的Activity的实例如果存在,那么系统就会调用它的onNewIntent;如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶, 107 | - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 108 | >具有这个标记的Activity不会出现在历史的Activity的列表中,当某些情况下,我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用,等同于XML中指定Activity的属性android:excludeFromRecents="true"。 109 | 110 | ## IntentFilter的匹配规则 111 | 1. Activity的启动分为两种 112 | 113 | - 显示调用 114 | - 隐式调用 115 | 显示调用需要明确地指定被启动对象的组件信息,包括包名和类名;隐式调用则不需要明确地指定组件信息。原则上一个Intent不应该即时显示调用又是隐式调用,如果二者共存的话以显示调用为主。 116 | IntentFilter中的过滤信息有action,category,data。 117 | 下面是一个过滤规则: 118 | ````java 119 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | ```` 135 | 136 | 2. 只有一个Intent同时匹配action类别,category类别,data类别才算是完全匹配,只有完全匹配才能成功启动目标Activity。另外,一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。 137 | 3. 匹配规则。 138 | 139 | - action匹配规则: 140 | 只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功,action匹配区分大小写。 141 | 一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。 142 | action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同。 143 | - category匹配规则: 144 | Intent中如果有category那么所有的category都必须和过滤规则中的其中一个category相同,如果没有category的话那么就是默认的category,即android.intent.category.DEFAULT,所以为了Activity能够接收隐式调用,配置多个category的时候必须加上默认的category。 145 | 也就是说,Intent中出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent也可以没有actegory,因为系统在用startActivity或者startActivityForResult的时候会默认为Intent加上"android.intent.category.DEFAULT"这个category。 146 | - data匹配规则: 147 | Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。 148 | data的结构很复杂,语法大致如下: 149 | ````java 150 | 157 | 158 | ```` 159 | 主要由mimeType和URI组成,其中mimeType代表媒体类型,如image/jpeg、audio/mpeg4-generic和video、*等,可以表示图片、文本、视频等不同的媒体格式;而URI的结构也复杂,大致如下: 160 | ```` 161 | ://:/[]|[]|[pathPattern] 162 | ```` 163 | 例如: 164 | ```` 165 | content://com.example.project:200/folder/subfolder/etc 166 | http://www.baidu.com:80/search/info 167 | ```` 168 | scheme:URI的模式,比如http、file、content等。 169 | host:URI的主机名,比如www.baidu.com. 170 | port:URI的端口号,比如80. 171 | 其中如果scheme或者host未指定那么URI就无效。 172 | path、pathPattern、pathPrefix都是表示路径信息,其中path表示完整的路径信息,pathPrefix表示路径的前缀信息;pathPattern表示完整的路径,但是它里面包含了通配符"*",表示0个或者多个任意字符。 173 | **data匹配规则的几种情况:** 174 | 1. 175 | ````java 176 | //URI有默认值 177 | 178 | 179 | ... 180 | 181 | ```` 182 | 如果过滤规则中的mimeType指定为"image/*"或者"text/*"等这种类型的话,那么即使过滤规则中没有指定URI,URI有默认的scheme是content和file!如果过滤规则中指定了scheme的话那就不是默认的scheme了。 183 | 例: 184 | ```` 185 | intent.setDataAndType(Uri.parse("file://abc"),"image/png"); 186 | ```` 187 | 如果要为Intent指定完整的data,必须要调用setDataAndType方法! 188 | 不能先调用setData然后调用setType,因为这两个方法会彼此清除对方的值。 189 | ````java 190 | public Intent setData(Uri data){ 191 | mData= data; 192 | mType = null; 193 | return this; 194 | } 195 | ```` 196 | 可以发现,setData会把mimeType置为null,setType也会把URI置为null。 197 | 2. 198 | ````java 199 | 200 | 201 | 202 | 203 | ```` 204 | 这种规则指定了两组data规则,且每个data都指定了完整的属性值,既有URI又有mimeType。 205 | 例: 206 | ````java 207 | intnet.setDataAndType(Uri.parse("http://abc"),"video/mpeg"); 208 | ```` 209 | 或 210 | ````java 211 | intnet.setDataAndType(Uri.parse("http://abc"),"audio/mpeg"); 212 | ```` 213 | 另外,data的下面两种写法作用是一样的: 214 | ````java 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | ```` 223 | Intent-filter的匹配规则对于Service和BroadcastReceiver也同样道理,不过系统对于Service的建议是尽量使用显示调用方式来启动服务。 224 | **如何判断是否有Activity能够匹配我们的隐式Intent?** 225 | - PackageManager的resolveActivity方法或者Intent的resolveActivity方法:如果找不到就会返回null 226 | - PackageManager的queryIntentActivities方法:它返回所有成功匹配的Activity信息。 227 | 针对Service和BroadcastReceiver等组件,PackageManager同样提供了类似的方法去获取成功匹配的组件信息,例如queryIntentServices、queryBroadcastReceivers等方法。有一类action和category比较重要,它们在一起用来标明这是一个入口Activity,并且会出现在系统的应用列表中。 228 | 在action和category中,有一类action和category比较重要。 229 | ```` 230 | 231 | 232 | ```` 233 | 这二者共同作用是用来标明这是一个入口Activity并且会出现在系统的应用列表中,缺一不可。 234 | 235 | 2017/8/13 236 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Android 动画深入分析.md: -------------------------------------------------------------------------------- 1 | # Android 动画深入分析 2 | ## View 动画 3 | 1. View 动画的作用对象是 View,支持4种动画效果,分别是平移、缩放动画、旋转动画和透明度动画。帧动画也属于 View 动画。 4 | 2. View 动画的种类 5 | View 动画的四种变换效果对应着 Animation 的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation 和 AlphaAnimation。 6 | 7 | View动画的四种变换 8 | ![View动画的四种变换](http://o8fk8z4sl.bkt.clouddn.com/View%E5%8A%A8%E7%94%BB%E7%9A%84%E5%9B%9B%E7%A7%8D%E5%8F%98%E6%8D%A2.png) 9 | 10 | 标签表示动画集合,对呀 AnimationSet 类,可以包含若干个动画。 11 | 两个属性: 12 | 13 | - android:interpolator 14 | 表示动画集合所采用的插值器,插值器影响动画的速度。 15 | - android:shareInterpolator 16 | 表示集合中的动画是否和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独指定所需要的插值器或使用默认值。 17 | 18 | 标签表示平移动画,对应 TranslateAnimation 类,可以使一个 View 在水平和竖直方向完成平移的动画效果。 19 | 属性含义: 20 | 21 | - android:fromXDelta --- 表示 x 的起始值; 22 | - android:toXDelta --- 表示 x 的结束值; 23 | - android:fromYDelta --- 表示 y 的起始值; 24 | - android:toYDelta --- 表示 y 的结束值。 25 | 26 | 标签表示缩放动画,对应 ScaleAnimation,可以使 View 具有放大或者缩小的动画效果。 27 | 属性含义: 28 | 29 | - android:fromXScale --- 水平方向缩放的起始值; 30 | - android:toXScale --- 水平方向缩放的结束值; 31 | - android:fromYScale --- 竖直方向缩放的起始值; 32 | - android:toYScale --- 竖直方向缩放的结束值; 33 | - android:pivotX --- 缩放的轴点的 x 坐标,它会影响缩放的效果; 34 | - android:pivotY --- 缩放的轴点的 y 坐标,它会影响缩放的效果。 35 | 36 | 标签表示旋转动画,对应 RotateAnimation,可以使 View 具有旋转的动画效果。 37 | 属性含义: 38 | 39 | - android:fromDegrees --- 旋转开始的角度; 40 | - android:toDegrees --- 旋转结束的角度; 41 | - android:pivotX --- 旋转的轴点的 x 坐标; 42 | - android:pivotY --- 旋转的轴点的 y 坐标。 43 | 44 | 标签表示透明度的动画,对应 AlphaAnimation,可以改变 View 的透明度。 45 | 属性含义: 46 | 47 | - android:fromAlpha --- 表示透明度的起始值; 48 | - android:toAlpha --- 表示透明度的结束值。 49 | 50 | View 动画的一些常用属性: 51 | 52 | - android:duration --- 动画的持续时间; 53 | - android:fillAfter --- 动画结束以后 View 是否停留在结束位置,true 表示 View 停留在结束位置,false 则不停留。 54 | 55 | 3. 自定义 View 动画,需要继承 Animation 这个抽象了,重写它的 initialize 和 applyTransformation 方法,在 initialize 方法中做一些初始化,在 applyTransformation 中进行相应的矩阵变换,需要采用 Camera 来简化矩阵变换的过程。 56 | 4. 帧动画是顺序播放一组预先定义好的图片,类似于电影播放。另外帧动画比如容易引起 OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。 57 | 58 | ## View 动画的特殊使用场景 59 | 1. LayoutAnimation 60 | LayoutAnimation 作用于 ViewGroup。 61 | 步骤: 62 | - 定义 LayoutAnimation 63 | 相关属性 64 | - android:delay --- 表示子元素开始动画的时间延迟。 65 | - android:animationOrder --- 表示子元素动画的顺序,有三种选项: 66 | + normal:表示顺序显示; 67 | + reverse :表示逆向显示; 68 | + random:表示随机播放入场动画。 69 | - anroid:animation --- 为子元素指定具体的入场动画。 70 | - 为子元素指定具体的如此动画 71 | - 为 ViewGroup 指定 android:layoutAnimation 属性:android:layoutAnimation = "@anim/anim_layout"。 72 | 2. Activity 的切换效果 73 | 主要用到 overridePendingTransition(int enterAnim,int exitAnim)这个方法,这个方法必须在 startActivity(Intent) 或者 finish() 之后调用才能生效。 74 | 参数含义: 75 | 76 | - enterAnim --- Activity 被打开时,所需要的动画资源 id; 77 | - exitAnim --- Activity 被暂停时,所需要的动画资源 id。 78 | 79 | ## 属性动画 80 | 1. 属性动画可以对任意对象的属性进行动画而不仅仅是 View,动画默认时间间隔300ms,默认帧率是10ms/帧。 81 | 属性动画需要定义在 **res/animator/**目录下。 82 | 83 | 2. 标签属性的含义 84 | - android:propertyName --- 表示属性动画的作用对象的属性的名称; 85 | - android:duration --- 表示动画的时长; 86 | - android:valueFrom --- 表示属性的起始值; 87 | - android:valueTo --- 表示属性的结束值; 88 | - android:startOffset --- 表示动画的延迟时间,当动画开始画,需要延迟多少毫秒才会真正播放此动画; 89 | - android:repeatCount --- 表示动画的重复次数,默认值是0,-1表示无限循环; 90 | - android:repeatMode --- 表示动画的重复模式,有“repeat”和“reverse”选项,分别表示连续重复和逆向重复; 91 | - android:valueType --- 表示 android:propertyName 所指定的属性的类型,有“intType”和“floatType” 两个可选项,分别表示属性的类型为整型和浮点型。另外,如果 android:propertyName 所指定的属性表示的是颜色,那么不需要指定 android:valueType,系统会自动对颜色类型的属性做处理。 92 | 93 | 3. TimeInterpolator,时间插值器,作业是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有 94 | - LinearInterpolator:线性插值器:匀速动画; 95 | - AccelerateDecelerateInterpolator:加速减速插值器:动画两头慢中间快; 96 | - DecelerateInterpolator:减速插值器:动画越来越慢。 97 | 98 | 4. TypeEvaluator,估值器,作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有 99 | - IntEvaluator:针对整型属性; 100 | - FloatEvaluator:针对浮点型属性; 101 | - ArgbEvaluator:针对 Color 属性。 102 | 103 | 5. 自定义插值器需要实现 Interpolator 或者 TimeInterpolator,自定义估值算法需要实现 TypeEvaluator。如果要实现对其他类型(非 int、float、Color)做动画,那么必须要自定义类型估值算法。 104 | 105 | 6. 属性动画提供了监听器用于监听动画的播放过程,主要有两个接口 106 | - AnimatorUpdateListener:可以监听动画开始、结束、取消以及重复播放; 107 | - AnimatorListener:会监听整个动画过程。 108 | 109 | 7. 属性动画的原理:属性动画要求动画作用的对象提供该属性的 get 和 set 方法,属性动根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用 set 方法,每次传递给 set 方法的值都不一样,确切来说是随时间的推移,所传递的值越来约接近最终值。 110 | 111 | 8. 使用动画的注意事项 112 | - OOM 问题 113 | 主要出现在帧动画中,实际开发中尽量避免使用帧动画。 114 | - 内存泄漏 115 | 主要出现在属性动画中的无限循环动画,需要在 Activity 退出时及时停止。 116 | - 兼容性问题 117 | - View 动画的问题 118 | 当出现动画完成后 View 无法隐藏的现象,即 setVisiblity(View.GONE)失效时,需要调用 view.clearAnimation() 清楚 View 动画来解决此问题。 119 | - 不要使用 px 120 | - 动画元素的交互 121 | - 硬件加速 122 | 123 | 124 | 2017.8.21 125 | W.Z.H 126 | -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Android 的 Drawable.md: -------------------------------------------------------------------------------- 1 | # Android 的 Drawable 2 | ## Drawable 简介 3 | 1. Drawable 是一个抽象类,它是所有 Drawable 对象的基类,每个具体的 Drawable 都是它的子类。 4 | 2. Drawable 的内部宽/高这个参数比较重要,通过 getIntrinsicWidth 和 getIntrinsicHeight 这两个方法可以获取到它们。但是并不是所以的 Drawable 都有内部宽/高。Drawable 的内部宽/高不等同于它的大小。Drawable 是没有大小的概念的。 5 | 6 | ## Drawable 的分类 7 | 1. BitmapDrawable 8 | 表示的是一张图片。 9 | 各个属性的含义: 10 | 11 | - android:src 12 | 图片的资源 id。 13 | - android:antialias 14 | 是否开启抗锯齿功能。 15 | - android:dither 16 | 是否开启抖动效果。 17 | - android:filter 18 | 是否开启过滤效果。 19 | - android:gravity 20 | gravity属性的可选项: 21 | ![gravity属性的可选项](http://o8fk8z4sl.bkt.clouddn.com/gravity%E5%B1%9E%E6%80%A7%E7%9A%84%E5%8F%AF%E9%80%89%E9%A1%B9.png) 22 | - android:mipMap 23 | 纹理映射。 24 | - android:tileMode 25 | 平铺模式。 26 | 27 | - disable 28 | 表示关闭平铺模式。 29 | - repeat 30 | 表示的是简单的水平和竖直方向上的平铺效果。 31 | - mirror 32 | 表示一种在水平和竖直方向上的镜面投影效果。 33 | - clamp 34 | 表示的效果是图片四周的像素会扩展到周围区域。 35 | 36 | 2. ShapeDrawable 37 | 通过颜色来构造的图形。 38 | 39 | - android:shape 40 | 表示图形的形状,有四个选项:rectangle(矩形)、oval(椭圆)、line(横线)和 ring(圆环)。 41 | 42 | ring的属性值: 43 | ![ring的属性值](http://o8fk8z4sl.bkt.clouddn.com/ring%E7%9A%84%E5%B1%9E%E6%80%A7%E5%80%BC.png) 44 | - 45 | 表示 shape 的四个角度,只适用于矩形 shape,有如下五个属性 46 | - andrdoid:radius --- 为四个角同时设定相同的角度,优先级较低,会被其他四个属性覆盖。 47 | - andrdoid:topLeftRadius --- 设定最上角的角度; 48 | - andrdoid:topRightRadius --- 设定右上角的角度; 49 | - andrdoid:bottomLeftRadius --- 设定最下角的角度; 50 | - andrdoid:bottomRightRadius --- 设定右下角的角度。 51 | - 52 | 与标签是互斥的,其中 solid 表示纯色填充,gradient 表示渐变效果。gradient 有如下几个属性: 53 | - android:angle --- 渐变的角度,默认为0,其值必须是45的倍数,0表示从左到右,90表示从上到下。 54 | - android:centerX --- 渐变的中心点的横坐标; 55 | - android:centerY --- 渐变的中心点的纵坐标; 56 | - android:startColor --- 渐变的起始色; 57 | - android:centerColor --- 渐变的中间色; 58 | - android:endColor --- 渐变的结束色; 59 | - android:gradientRadius --- 渐变半径,仅当 android:type = "radial"时有效; 60 | - android:useLevel --- 一般为 false,当 Drawable 作为 StateListDrawable 使用时为 true。 61 | - 62 | 这个标签表示纯色填充,通过 android:color 即可指定 shape 中填充的颜色。 63 | - 64 | Shape 的描边,有如下几个属性: 65 | - android:width --- 描边的宽度,越大则 shape 的边缘就会看起来越粗; 66 | - android:color --- 描边的颜色; 67 | - android:dashWidth --- 组成虚线的线段的宽度; 68 | - android:dashGap: --- 组成虚线的线段之间的间隔,间隔越大则虚线看起来空隙就越大。 69 | **注意:**如果 android:dashWidth 和 android:dashGap 有任何一个为0,那么虚线的效果将不能生效。 70 | - 71 | 表示空白,是包含它的 View 的空白,有四个属性: 72 | - android:left; 73 | - android:top; 74 | - android:right; 75 | - android:bottom。 76 | - 77 | shape 的固有大小,有两个属性: 78 | - android:width 79 | - android:height 80 | 分别表示 shape 的宽/高。 81 | 82 | 3. LayerDrawable 83 | LayerDrawable 对应于 XML 标签是,表示一种层次化的 Drawable 集合,通过将不同的 Drawable 放置在不同的层上面从而达到一种叠加后的效果。 84 | 默认情况下,layer-list 中的所有的 Drawable 都会被缩放至 View 的大小,对于 bitmap 来说,需要使用 android:gravity 属性才能控制图片的显示效果。 85 | 86 | 4. StateListDrawable 87 | StateListDrawable 对应于 标签,也是 Drawable 集合,主要用于设置可单击的 View 的背景。 88 | 属性的含义: 89 | 90 | - android:constantSize 91 | StateListDrawable 的固定大小是否不随着其状态的改变而改变。 92 | - android:dither 93 | 是否开启抖动效果。 94 | - android:variablePadding 95 | StateListDrawable 的 padding 表示是否随着其状态的改变而改变。 96 | 97 | View 的常见状态 98 | ![View 的常见状态](http://o8fk8z4sl.bkt.clouddn.com/View%E7%9A%84%E5%B8%B8%E8%A7%81%E7%8A%B6%E6%80%81.png) 99 | 100 | 5. LevelListDrawable 101 | LevelListDrawable 对应于 标签,也表示一个 Drawable 集合,集合中的每个 Drawable 都有 一个等级的概念,由 **android:minLevel** 和 **android:maxLevel** 来指定,在最小值和最大值之间的等级会对应此 item 中的 Drawable,Drawable 的等级范围是0~1000,最小等级是0,最大等级是10000。 102 | 103 | 6. TransitionDrawable 104 | TransitionDrawable 对应于 标签,用于实现两个 Drawable 之间的淡入淡出效果。 105 | 106 | 7. InsetDrawable 107 | InsetDrawable 对应于 标签,可以将其他 Drawable 内嵌到自己当中,并可以在四周留出一定的间距。 108 | 109 | 8. ScaleDrawable 110 | ScaleDrawable 对应于 标签,可以根据自己的等级将指定的 Drawable 缩放到一定的比例。 111 | 如果 ScaleDrawable 的级别越大那么内部的 Drawable 看起来就越小,如果 ScaleDrawable 的 XML 中所定义的缩放比例越大,那么内部的 Drawable 看起来就越小。ScaleDrawable 的作用更偏向于缩小一个特定的 Drawable。 112 | 113 | 9. ClipDrawable 114 | ScaleDrawable 对应于 标签,可以根据自己当前的等级来裁剪另一个 Drawable。 115 | 116 | ClipDrawable 的 gravity 属性 117 | ![ClipDrawable的gravity属性](http://o8fk8z4sl.bkt.clouddn.com/ClipDrawable%20%E7%9A%84%20gravity%20%E5%B1%9E%E6%80%A7.png) 118 | 119 | 120 | 2017.8.20 121 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Android 的消息机制.md: -------------------------------------------------------------------------------- 1 | # Android 的消息机制 2 | 1. Android 的消息机制主要是指 Hadnler,Hnadler 的运行需要底层的 MessageQueue 和 Lopper 支撑。MessageQueue 是消息队列,它的内部存储了一组信息,以队列的形式对外提供插入和删除的工作;Looper 是指消息循环,会以无限循环的形式去查找是否有新消息,如果有的话就处理,否则一直等待。ThreadLocal 是 Looper 中的一个特殊概念,作用是可以在每个线程中存储数据。ThreadLocal 可以在不同的线程中互不干扰地存储并提供数据,通过 ThreadLocal 可以获取每个线程的 Looper,另外线程是默认没有 Looper 的,需要使用 Handler 就必须为线程创建 Looper。 3 | 4 | ## Android 的消息机制概述 5 | 1. 系统不允许在子线程中访问 UI 的原因:因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态。如果对 UI 控件的访问加上锁机制会有两个缺点:首先加上锁机制会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。 6 | 2. Handler 的工作过程 7 | ![Handler 的工作过程](http://o8fk8z4sl.bkt.clouddn.com/Handler%20%E7%9A%84%E5%B7%A5%E4%BD%9C%E8%BF%87%E7%A8%8B.png) 8 | 9 | ## Android 的消息机制分析 10 | 1. ThreadLocal 的工作原理 11 | ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取到存储的数据,其他线程则无法获取到数据。 12 | 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。 13 | 14 | 存储规则:ThreadLocal 的值在 table 数组中的存储位置总是为 ThreadLocal 的 reference 字段所标识的对象的下一个位置。 15 | 16 | ThreadLocal 可以在多个线程中互不干扰地存储和修改数据,是因为 ThreadLocal 的 set 和 get 方法所操作的对象都是当前线程的 localValues 对象的 table 数组,可以在不同线程中访问同一个 ThreadLocal 的 set 和 get 方法,它们对 ThreadLocal 所做的读/写操作仅限于各自线程发内部。 17 | 18 | 2. 消息队列的工作原理 19 | 消息队列在 Android 中指的是 MessageQueue,MessageQueue主要包含插入和读取两个操作,对应 enqueueMessage(作用是往消息队列中插入一条消息) 和 next(作用是从消息队列中取出一条消息并将其从消息队列中移除) 两个方法。 20 | 21 | 3. Looper 的工作原理 22 | Looper.prepare() 为当前线程创建一个 Looper,接着通过 Looper.loop() 来开启消息循环。 23 | prepareMainLooper 方法主要是给主线程 ActivityThread 创建 Looper 使用的。 24 | Looper 提供的 getMainLooper 方法可以在任何地方获取到主线程的 looper。 25 | Looper 提供了 quit 和 quitSafely 来退出 Looper,区别是:quit 会直接退出 Looper;quitSafely 只是设定了一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。 26 | 在子线程中,如果收到为其创建了Looper,那么所有的事情完成以后应该调用 quit 方法来终止消息循环,否则这个子线程就会一直处于等待状态。 27 | Looper 最重要的一个方法是 loop 方法,只有调用了 loop 方法,消息循环系统才会真正地起作用。loop 是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回 null。 28 | 29 | 4. Handler 的工作原理 30 | Handler 的工作主要包含消息的发生和接收过程,消息的发送可以通过 post 的一系列方法以及 send 的一系列方法来实现,post 的一系列方法最终是通过 send 的一系列方法来实现的。 31 | Handler 发生消息的过程仅仅是向消息队列中插入一条消息,MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 收到消息后就开始处理,最终消息由 Looper 交由 Hnadler 处理,即 Handler 的 dispatchMessage 方法会被调用,这时 Hnadler 就进入了处理消息的阶段。 32 | 33 | Hnadler 出来消息的过程: 34 | 35 | - 首先,检查 Message 的 callback 是否为 null,不为 null 就通过 handleCallback 来处理消息。callback 是一个 Runnable 对象,实际上就是 Handler 的 post 方法所传递的 Runnable 参数。 36 | - 其次,检查 mCallback 是否为 null,不为 null 就调用 mCallback 的 handleMessage 方法来处理消息。Callback 是个接口,其意义是可以用来创建一个 Handler 的实例但不需要派生 Handler 的子类。 37 | - 最后,调用 Handler 的 handleMessage 方法来处理消息。 38 | Handler 处理消息流程图 39 | ![Handler 处理消息流程图](http://o8fk8z4sl.bkt.clouddn.com/Handler%20%E5%A4%84%E7%90%86%E6%B6%88%E6%81%AF%E6%B5%81%E7%A8%8B%E5%9B%BE.png) 40 | 41 | ## 主线程的消息循环 42 | 1. Android 的主线程就是 ActivityThread,主线程的入口方法为 main,在 main 方法中系统会通过 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 来开启主线程的消息循环。 43 | 主线程的消息循环开始后,ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 即 ActivityThread.H,它内部定义了一组消息类型,包含了四大组件的启动和停止等过程。 44 | 45 | ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信, AMS 以进程间通信的方式完成 ActivityThread 的请求后会回调 ActivityThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。 46 | 47 | 48 | 49 | 50 | 2017.8.24 51 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Android 的线程和线程池.md: -------------------------------------------------------------------------------- 1 | # Android 的线程和线程池 2 | 从用途上分,线程分为主线程和子线程;主线程主要处理和界面相关的事情,子线程则往往用于耗时操作。 3 | 4 | ## 主线程和子线程 5 | 主线程是指进程所拥有的线程。Android 中主线程交 UI 线程,主要作用是运行四大组件以及处理它们和用户的交互;子线程的作业则是执行耗时任务。 6 | 7 | ## Android 中的线程形态 8 | 1. AsyncTask 9 | AsyncTask 是一种轻量级的异步任务类,可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI, 10 | AsyncTask 是一个抽象的泛型类,提供了 Params(参数的类型)、Progress(后台任务执行进度的类型) 和 Result(后台任务的返回结果的类型) 这三个泛型参数, 11 | AsyncTask 提供了4个核心方法 12 | - onPreExcute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。 13 | - doInBackground(Params...params),在线程池中执行,此方法用于执行异步任务,params 参数表示异步任务的输入参数。在此方法中可以通过 publishProgress 方法来更新任务的进度,publishProgress 方法会调用 onProgressUpdate 方法,另外此方法需要返回计算结果给 onPostExecute 方法。 14 | - onProgressUpdate(Progress...values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。 15 | - onPostExecute(Resukt result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中 result 参数是后台任务的返回值,即 doInBackground 的返回值。 16 | 17 | onPreExcute 先执行,接着是 doInBackground,最后才是 onPostExecute。 18 | 当异步任务被取消时,onCancelled() 方法会被调用,这个时候 onPostExecute 则不会被调用。 19 | 2. AsyncTask 在具体的使用过程中的一些限制条件 20 | 21 | - AsyncTask 的类必须在主线程中加载; 22 | - AsyncTask 的对象必须在 UI 线程中创建; 23 | - 不要在程序中直接调用 onPreExecute、onPostExecute、doInBackground 和 onProgressUpdate 方法。 24 | - 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute 方法,否则会报运行时异常。 25 | - 在 Android 1.6之前,AsyncTask 是串行执行任务的,Android 1.6的时候 AsyncTask 开始采用线程池处理并行任务,但是从 Android 3.0开始为了避免 AsyncTask 所带来的并发错误,AsyncTask 又采用一个线程来串行执行任务。但是在 Android 3.0 以及后续的版本中,仍然可以通过 AsyncTask 的 executeOnExecutor 方法来并行地执行任务。 26 | 27 | 3. AsyncTask 的工作原理 28 | AsyncTask 中有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR) 和一个 Handler(InternalHandler),线程池 SerialExecutor 用于任务的排队,线程池 THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。 29 | 30 | 4. HandlerThread 31 | HandlerThread 继承了 Thread,是一种可以使用 Handler 的 Thread, 它的实现就是在 run 方法中通过 Looper.prepare() 来创建消息队列,并通过 Looper.loop() 来开启消息循环。 32 | 33 | 与普通的 Thread 相比,普通 Thread 主要用于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式来通知 HandlerThread 执行一个具体的任务。 34 | 35 | 由于 HandlerThread 的 run 方法是一个无限循环,因此当明确不需要在使用 HandlerThread 时,可以通过它的 quit 或者 quitSafely 方法来终止线程的执行。 36 | 37 | 5. IntentService 38 | IntentService 是一种特殊的 Service,继承了 Service 并且是一个抽象类,必须创建它的子类才能使用 IntentService。IntentService可用于执行后台耗时任务,任务执行后会自动停止,并且它的优先级比单纯的线程要高很多,不容易被系统杀死。在实现上,IntentService 封装了 HandlerThread 和 Handler。 39 | 40 | ## Android 中的线程池 41 | 1. 线程池的优点 42 | - 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销; 43 | - 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象; 44 | - 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。 45 | 46 | 2. ThreadPoolExecutor 47 | ThreadPoolExecutor 是线程的真正实现。 48 | ````java 49 | public ThreadPoolExecutor(int corePoolSize, 50 | int maximumPoolSize, 51 | long keepAliveTime, 52 | TimeUnit unit, 53 | BlockingQueue workQueue, 54 | ThreadFactory threadFactory) 55 | ```` 56 | 57 | - corePoolSize 58 | 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,及时处于闲置状态。 59 | - maximumPoolSize 60 | 线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞。 61 | - keepAliveTime 62 | 非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。 63 | - unit 64 | 用于指定 keepAliveTime 参数的时间单位,这是一个枚举,常用的有 TimeUnit、MILLSECONDS(毫秒)、TimeUnit.SECONDS(秒) 以及 TimeUnit.MINUTES(分钟)。 65 | - workQueue 66 | 线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。 67 | - threadFactory 68 | 线程工厂,为线程池通过创建新线程的功能。ThreadFactory 是一个接口,只有 一个方法:Thread newThread(Runnable r)。 69 | 70 | ThreadPoolExecutor 执行任务时遵循的规则 71 | 72 | - 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务; 73 | - 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行; 74 | - 如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量为达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。 75 | - 如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务, ThreadPoolExecutor 会调用 RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。 76 | 77 | 3. 线程池的分类 78 | - FixedThreadPool 79 | 通过 Executors 的 newFixedThreadPool 方法来创建。是一种线程数量固定的线程池,当线程处于空闲状态时并不会被回收,除非线程池被关闭。FixedThreadPool 中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制。 80 | - CachedThreadPool 81 | 通过 Executors 的 newCachedThreadPool 方法来创建。是一种线程数量不定的线程池,只有非核心线程,最大线程数为 Integer.MAX_VALUE。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收。CachedThreadPool 的任务队列相当于一个空集合,这样会导致任何任务都会被立即执行。 82 | - ScheduledThreadPool 83 | 通过 Executors 的 newScheduledThreadPool 方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。ScheduledThreadPool 这类线程池主要用于执行定时任务和具有固定周期的重复任务。 84 | - SingleThreadExecutor 85 | 通过 Executors 的 newSingleThreadExecutor 方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor 的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。 86 | 87 | 88 | 系统预置4种线程池的典型使用方法: 89 | 90 | ````java 91 | Runnable command = new Runnable(){ 92 | @Override 93 | public void run(){ 94 | SystemClock.sleep(2000); 95 | } 96 | 97 | ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4); 98 | fixedThreadPool.execute(command); 99 | 100 | ExecutorService cachedThreadPool =Executors.newCachedThreadPool(); 101 | cachedThreadPool.execute(command); 102 | 103 | ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); 104 | // 2000ms 后执行 command 105 | scheduledThreadPool.schedule(command,2000,TimeUnit.MILLISECONDS); 106 | // 延迟10ms,每个1000ms执行一次 command 107 | scheduledThreadPool.scheduleAtFixedRate(command,10,1000,TimeUnit.MILLISECONDS); 108 | 109 | ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); 110 | singleThreadExecutor.execute(command); 111 | 112 | } 113 | ```` 114 | 115 | 116 | 2017.8.24 117 | W.Z.H 118 | -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Android性能优化.md: -------------------------------------------------------------------------------- 1 | # Android 性能优化 2 | ## Android 的性能优化方法 3 | 1. 布局优化 4 | 布局优化的思想就是尽量减少布局文件的层级。 5 | 如何进行布局优化: 6 | 首先删除布局中无用的控件和层级;其次有选择地使用性能较低的 ViewGroup。 7 | 布局优化的另一种手段是采用标签、标签和 ViewStub 8 | 9 | 标签: 10 | >主要用于布局重用。 11 | 可以将一个指定的布局文件加载到当前布局文件中, 12 | 标签只支持以 android:layout_开头的属性,其他属性是不支持的。android:id 这个属性例外。 13 | 14 | 标签: 15 | >标签一般和标签一起使用从而减少布局的层级, 16 | 17 | ViewStub: 18 | >ViewStub 继承了 View,非常轻量且宽/高都是0。ViewStub 的意义在于按需即在所需的布局文件。 19 | 20 | 2. 绘制优化 21 | 绘制优化是指 View 的 onDraw 方法要避免执行大量的操作,主要体现在: 22 | - 首先,onDraw 中不要创建新的局部对象,因为 onDraw 方法可能会被频繁调用,会在一瞬间产生大量的临时对象,不仅占用过多内存还导致系统更加频繁 gc,降低程序的执行效率。 23 | - 另一方面,onDraw 方法中不要做耗时的任务,也不能执行成千上万次的循环操作。官方的标准中 View 的绘制帧率保证60fps 是最佳的,这就要求每帧的绘制时间不超过16ms(16ms = 1000/60)。 24 | 25 | 3. 内存泄漏优化 26 | 泄漏发生的场景: 27 | 28 | - 静态变量导致的内存泄漏 29 | - 单例模式导致的内存泄漏 30 | - 属性动画导致的内存泄漏 31 | 32 | 4. 响应速度优化和 ANR 日志分析 33 | Android 规定,Activity 如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件就会出现 ANR,BroadcastReceiver 如果10秒钟之内还未执行玩操作会出现 ANR。 34 | 当一个进程发生 ANR 之后,系统会在/data/anr 目录下创建一个文件 traces.txt,通过分析这个文件可以定位出 ANR 的原因。 35 | 36 | 5. ListView 和 Bitmap 优化 37 | ListView: 38 | 首先要采用 ViewHolder 并避免在 getView 执行耗时操作; 39 | 其次要根据列表的滑动状态来控制任务的执行频率; 40 | 最后可以尝试开启硬件的加速来使 ListView 的滑动更加流畅。 41 | 42 | 6. 线程优化 43 | 线程优化的思想是采用线程池,避免程序中存在大量的 Thread。 44 | 45 | 7. 一些性能建议 46 | - 避免创建过多的对象; 47 | - 不要过多使用枚举,枚举占用的内存空间比整型大; 48 | - 常量请使用 static final 来修饰; 49 | - 使用一些 Android 特有的数据结构; 50 | - 适当使用软引用和弱引用; 51 | - 采用内存缓存和磁盘缓存; 52 | - 尽量采用静态内部类,可以避免潜在的由于内部类而导致的内存泄漏。 53 | 54 | ## 提高程序的可维护性 55 | - 命名要规范,要正确地传达出变量或者方法的含义,少用缩写。 56 | - 代码的排版上需要留出合理的空白区分不同的代码块,其中同类变量的声明要放在一组,两类变量之间要留出一行空白作为区分。 57 | - 仅为非常关键的代码添加注释,其他地方不写注释。 58 | 59 | 60 | 61 | 62 | 2017.8.30 63 | W.Z.H 64 | 65 | -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/Bitmap 的加载和 Cache.md: -------------------------------------------------------------------------------- 1 | # Bitmap 的加载和 Cache 2 | ## Bitmap 的高效加载 3 | 1. Bitmap 在 Android 中指的是一张图片。 4 | 2. BitmapFactory 类提供了四类方法:decodeFile、decodeResource、decodeStream 和 decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载一个 Bitmap 对象,其中 decodeFile 和 decodeResource 又间接调用了 decodeStream 方法。 5 | 3. 高效地加载 Bitmap 的核心思想就是采用 BitmapFactory.Options 来加载所需尺寸的图片。主要用到了 inSampleSize 参数,即采样率。inSampleSize 的取值应该总是为2的指数,如果外界传递给系统的 inSampleSize 不为2的指数,那么系统会向下取整并选择一个最接近2的指数来代替。 6 | 4. 获取采样率的流程: 7 | - 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true并加载图片; 8 | - 从 BitmapFactory.Options 中取出图片的原始宽高信息,它们对应于 outWidth 和 outHeight 参数; 9 | - 根据采样率的规则并结合目标 View 的所需大小计算出采样率 inSampleSize; 10 | - 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 false,然后重新加载图片。 11 | 12 | 5. 代码实现: 13 | ````java 14 | public static Bitmap decodeSampleBitmapFromResource(Resource res,int resId,int reqWidth,int reqHeight){ 15 | //First decode with inJustDecodeBounds = true to check dimensions 16 | 17 | final BitmapFactory.Options options = new BitmapFactory.Options(); 18 | options.inJustDecodeBounds = true; 19 | BitmapFactory.decodeResource(res,redId,options); 20 | 21 | // Calculate inSampleSize 22 | options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight); 23 | return BitmapFactory.decodeResource(res,resId,options); 24 | } 25 | 26 | 27 | public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){ 28 | //Raw height and width of image 29 | final int height = options.outHeight; 30 | final int width = options.outWidth; 31 | int inSampleSize = 1; 32 | 33 | if(height > reqHeigth || width > reqWidth){ 34 | final int halfHeight = height / 2; 35 | final int halfWidth = width / 2; 36 | //Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width larger than the requested height and width. 37 | 38 | while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth){ 39 | inSampleSize *= 2; 40 | } 41 | } 42 | return inSampleSize; 43 | } 44 | ```` 45 | 加载并显示图片 46 | ````java 47 | mImageView.setImageBitmap( 48 | decodeSampleBitmapFromResource(getResource(),R.id.myimage,100,100)); 49 | ```` 50 | 51 | ## Android 中的缓存策略 52 | 1. 一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。 53 | 2. 目前常用的一种算法是 LRU,是近期最少使用算法,核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用 LRU 算法的缓存有两种:LruCache(用于实现内存缓存) 和 DiskLruCache(充当存储设备缓存), 54 | 3. LruCache 55 | LruCache 是一个泛型类,内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象,提供了 get 和 put 方法来完成缓存的获取和添加操作,当缓存满时,LruCache 会移除较早使用的缓存对象,然后再添加新的缓存对象。 56 | 57 | - 强引用:直接的对象引用; 58 | - 软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被 gc 回收; 59 | - 弱引用: 当一个对象只有弱引用存在时,此对象会随时被 gc 回收。 60 | 61 | LruCache 是线程安全的。 62 | LruCache 的典型的初始化过程: 63 | ````java 64 | int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 65 | int cacheSize = maxMemory / 8; 66 | mMenoryCache = new LruCache(cacheSize){ 67 | @Override 68 | // sizeOf() 方法的作用是计算缓存对象的大小 69 | protected int sizeOf(String key, Bitmap bitmap){ 70 | return bitmap.getRowBytes() * bitmap.getHeight() / 1024; 71 | } 72 | }; 73 | ```` 74 | 75 | 从 LruCache 中获取一个缓存对象 76 | ````java 77 | mMenoryCache.put(key,bitmap); 78 | ```` 79 | 80 | 4. DiskLruCache 81 | DiskLruCache 用于实现存储设备缓存,即磁盘缓存,通过将缓存对象写入文件系统从而实现缓存的效果。 82 | 83 | DiskLruCache 的创建 84 | DiskLruCache 并不能通过构造方法来创建,提供了 open 方法用于创建自身。 85 | ````java 86 | public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 87 | ```` 88 | open 方法有四个参数: 89 | 90 | - 第一个参数表示磁盘缓存在文件系统中的存储路径; 91 | - 第二个参数表示应用的版本号,一般设为1即可; 92 | - 第三个参数表示单节点所对应的数据的个数,一般设为1即可。 93 | - 第四个参数表示缓存的总大小,当缓存大小超出这个设定值之后, DiskLruCache 会清楚一些缓存从而保证大小不大于这个设定值。 94 | 95 | DiskLruCache 的缓存添加 96 | DiskLruCache 的缓存添加操作是通过 Editor 完成的,Editor 表示一个缓存对象的编辑对象。 97 | 98 | DiskLruCache 的缓存查找 99 | 缓存查找过程需要将 url 转换为 key,然后通过 DiskLruCache 的 get 方法得到一个 Snapshot 对象,接着再通过 Snapshot 对象即可得到缓存的文件输入流,有了文件输出流,自然就可以得到 Bitmap 对象。 100 | 101 | 5. ImageLoader 的实现 102 | 一般来说,ImageLoader 应该具备如下功能: 103 | - 图片的同步加载; 104 | - 图片的异步加载; 105 | - 图片的压缩; 106 | - 内存缓存; 107 | - 磁盘缓存; 108 | - 网络垃圾。 109 | 110 | 111 | 112 | 113 | 2017.8.29 114 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/IPC机制.md: -------------------------------------------------------------------------------- 1 | # IPC机制 2 | ## Android IPC简介 3 | 1. IPC: Inter-Process Communication 的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。 4 | 2. 进程与线程。 5 | 按照操作系统中的描述,线程是 CPU 调度最小单元,是一种有限的系统资源;进程一般指一个执行单元,在 PC 或者移动设备上指一个程序或者与一个应用。 6 | 一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。 7 | ANR:Application Not Responding,应用无响应。解决这个问题就需要用到线程,把一些耗时的任务放在线程中即可。 8 | 9 | ## Android中的多进程模式 10 | 1. 正常情况下,在 Android 中多进程是指一个应用中存在多个进程的情况。 11 | 2. 在 Andorid 中使用多进程只有一种方法,就是给四大组件在 AndroidManifest 中指定 android:process 属性。 12 | 还有另一种方法就是通过 JNI 在 native 层去 fork 一个新的进程。 13 | 3. 默认进程就是包名。 14 | 4. ":remote"和"com.ryg.chapter_2.remote" 这两种方式的区别: 15 | - 首先,":"的含义是指要在当前的进程名前面附加上当前的包名,是一种简写的方法。而另一种是完整的命名方式,不会附加包名信息。 16 | - 其次,进程名以":"开头的进程属于当前应用的私有进程,进程名不以":"开头的进程属于全局进程,其他应用通过 ShareID 方式可以和它跑在同一个进程中。 17 | 5. Android 系统会为每个应用分配一个唯一的 UID ,具有相同 UID 的应用才能共享数据。另外,两个应用需要有相同的 ShareUID 且签名相同才能跑在同一个进程中。可以共享 data 目录、组件信息,还可以共享内存数据。 18 | 6. Android 为每一个应用分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机中访问同一个类的对象会产生多份副本。 19 | 7. 所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。 20 | 8. 使用多进程会造成的问题: 21 | - 静态成员和单例模式完全失效。 22 | - 线程同步机制完全失效。 23 | - SharePreferences 的可靠性下降。因为 SharePreferences 不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,因为 SharePreferences 底层是通过读/写XML文件来实现的,并发写显然是可能出现问题的。 24 | - Application 会多次创建。运行在同一个进程中的组件是属于同一个虚拟机和同一个 Application 的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。 25 | 26 | ## IPC基础概念介绍 27 | 1. Serializable 是 Java 提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。 28 | ````java 29 | public class User implements Serializable{ 30 | private static final long serialVersionUID = 519061723721295573l; 31 | 32 | public int userId; 33 | public String userName; 34 | public boolean isMale; 35 | ... 36 | } 37 | ```` 38 | 只需要采用 ObjectOutputStream 和 ObjectInputStream 即可实现对象的序列化和反序列化。 39 | ````java 40 | //序列化过程 41 | User user = new User(0,"jake",true); 42 | ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt")); 43 | out.writeObject(user); 44 | out.close(); 45 | 46 | //反序列化过程 47 | ObjectInputStream int= new ObjectInputStream(new FileInputStream("cache.txt")); 48 | User newUser = in.readObject(); 49 | in.close(); 50 | ```` 51 | 2. serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。 52 | serialVersionUID 的工作机制: 53 | > 序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统就会去检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变化,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。 54 | 55 | 一般我们应该手动指定 serialVersionUID 的值,这样序列化和反序列化时两者的 serialVersionUID 是相同的,可以正常进行反序列化;不手动指定 serialVersionUID 的值,反序列化时当前类有所改变,那么系统就会重新计算当前类的 hash 值并把它赋值给 serialVersionUID ,这时候当前类的 serialVersionUID 就会和序列化的数据中的 serialVersionUID 不一致,导致反序列化失败,程序出现crash。 56 | 静态成员变量属于类不属于对象,不会参与序列化过程。 57 | 用 transient 关键字标记的成员变量不参与序列化过程。 58 | 59 | 3. Parcelable 接口是 Android 中提供的新的序列化方式,实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。 60 | ````java 61 | public class User implements Parcelable{ 62 | public int userId; 63 | public String userName; 64 | public boolean isMale; 65 | 66 | public Book book; 67 | 68 | public User(int userId,String userName,boolean isMale){ 69 | this.userId = userId; 70 | this.userName = userName; 71 | this.isMale = isMale; 72 | } 73 | 74 | //内容描述功能 75 | public int describeContents(){ 76 | return 0; //几乎在所有情况下这个方法都返回0,仅当当前对象中存在文件描述时,返回1。 77 | } 78 | 79 | //序列化 80 | public void writeToParcel(Parcel out, int flags){ 81 | out.writeInt(userId); 82 | out.writeString(userName); 83 | out.writeInt(isMale ? 1 : 0); 84 | out.writeParcelable(book,0); 85 | } 86 | 87 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator(){ 88 | //创建序列化对象 89 | public User createFromParcel(Parcel in){ 90 | return new User(in); 91 | } 92 | //创建序列化数组 93 | public User[] newArray(int size){ 94 | return new User[size]; 95 | } 96 | }; 97 | 98 | //完成反序列化 99 | private User(Parcel in){ 100 | userId = in.readInt(); 101 | userName = in.readString(); 102 | isMale = in.readInt() == 1; 103 | book = in.readParcelable(Thread.currentThread().getContextClassLoader()); 104 | } 105 | } 106 | ```` 107 | Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输。 108 | 109 | Parcelable的方法说明。 110 | 1. createFromParcel(Parcel in):从序列化后的对象中创建原始对象。 111 | 2. newArray(int size) :创建指定长度的原始对象数组。 112 | 3. User(Parcel in):从序列化后的对象中创建原始对象。 113 | 4. writeToParcel(Parcel out,int flags):将当前对象写入序列化结构中,其中 flags 标识两种值:0或者1(标记位:PARCELABLE_WRITE_RETURN_VALUE)。为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0。 114 | 5. describeContents:返回当前对象的内容描述,如果含有文件描述符,返回1(标记位:CONTENTS_FILE_DESCRIPTOR),否则返回0,几乎所有情况都为0。 115 | 4. Serializable 和 Parcelable 的区别。 116 | Serializable 是 Java 中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作,Parcelable 是 Android 中的序列化方式,主要用在内存序列化上,适合在 Android 平台上使用,缺点就是使用起来稍微麻烦,但是效率很高。如果要将对象序列化到存储设备中或者将对象序列化后通过网络传输建议使用 Serializable。 117 | 118 | ## Binder 119 | 120 | 1. Binder 是 Android 中的一个类,实现了 IBinder 接口。 121 | 从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式。 122 | 从 Android Framework 角度来说,Binder 是 ServiceManager 链接各种 Manager(ActivityManager、WindowManager)和相应 ManagerService 的桥梁。 123 | 从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介。 124 | Android 开发中,Binder 主要用 Service 中,包括 AIDL 和 Messenger。 125 | 2.解释 126 | - DESCRIPTOR 127 | Binder 的唯一标识,一般用当前 Binder 的类名表示。 128 | - asInterface(android.os.IBinder obj) 129 | 用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。 130 | - asBinder 131 | 此方法用于返回当前 Binder 对象。 132 | - onTransact 133 | 这个方法运行在服务端中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。如果此方法返回 false,那么客户端的请求会失败。 134 | - Proxy#getBookList 135 | - Proxy#addBook 136 | 137 | **Binder的工作机制** 138 | ![Binder 的工作机制](http://7xq2jk.com1.z0.glb.clouddn.com/Binder%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.png) 139 | 140 | 3. 手动实现一个Binder 141 | 1. 声明一个AIDL性质的接口,只需要继承 IInterface 接口即可,IInterface 接口中只有一个 asBinder 方法。 142 | 2. 实现 Stub 类和 Stub 类中的 Proxy 代理类。 143 | 144 | 4. Binder 中提供了两个配对的方法 linkToDeath 和 unlinkToDeath,通过 linkToDeath 可以给 Binder 设置一个死亡代理,当 Binder 死亡时,就会收到通知,从而重新发起连接请求来恢复连接。 145 | 首先声明一个 DeathRecipient 对象,DeathRecipient 是一个接口,其内部只有一个方法 binderDied,当 Binder 死亡的时候,系统就会回调 binderDied 方法,然后移除之前绑定的 binder 代理并重新绑定远程服务: 146 | ````java 147 | private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){ 148 | @override 149 | public void binderDied(){ 150 | if(mBookManager == null) 151 | return; 152 | mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0); 153 | mBookManager = null; 154 | //TODO: 这里重新绑定远程 Service 155 | } 156 | } 157 | ```` 158 | 其次,在客户端绑定远程服务成功后,给 binder 设置死亡代理: 159 | ````java 160 | mSerivce = IMessageBoxManager.Stub.asInterface(binder); 161 | binder,linkToDeath(mDeathRecipient,0); 162 | ```` 163 | 164 | ## Android中的IPC方式 165 | 1. 文件共享适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。 166 | 2. 不建议在进程间通信中使用 SharePreferences,因为由于系统对它的读/写有一定的缓存策略,即在内存中会有一份 SharePreferences 文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,SharePreferences 有很大几率会丢失数据。 167 | 3. Messenger 可以在不同进程中传递 Message 对象,它的底层实现是 AIDL。 168 | 4. Messenger 的工作原理 169 | ![Messenger 的工作原理](http://7xq2jk.com1.z0.glb.clouddn.com/Messenger%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86.png) 170 | 5.AIDL 文件支持的数据类型 171 | 172 | - 基本数据类型(int、long、char、boolean、double等); 173 | - String 和 CharSequence; 174 | - List:只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持; 175 | - Map: 只支持 HashMap,里面的每个元素都必须被 AIDL 支持,包括 key 和 value; 176 | - Parcelable:所有实现了 Parcelable 接口的对象; 177 | - AIDL:所有 AIDL 接口本身也可以在 AIDL 文件中使用。 178 | 179 | 6. AIDL 中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout, in 表示输入型参数,out 表示输出型参数,inout 表示输入输出型参数。 180 | 7. RemoteCallbackList 是系统专门提供的用于删除跨进程 listener 的接口,是一个泛型,支持管理任意的 AIDL 接口。工作原理是在它的内部有一个 Map 结构专门用来保存所有的 AIDL 回调,这个 Map 的 key 是 IBinder 类型,value 是 Callback 类型。 181 | 8. Binder 意外死亡后重新连接服务。 182 | 183 | - 给 Binder 设置 DeathRecipient 监听,当 Binder 死亡时,会收到 binderDied 方法的回调,在 binderDied 方法中可以重连远程服务。 184 | - 在 onServiceDisconnected 中重连远程服务。 185 | 区别在于:onServiceDisconnected 在客户端的 UI 线程中被回调,而 binderDied 在客户端的 Binder 线程池中被回调。 186 | 187 | 9. 如何在 AIDL 中进行权限验证。 188 | 189 | - 可以在 onBind 中进行验证,验证不通过就直接返回 null。验证方式比如使用 permission 验证,在 AndroidMenifest 中声明所需的权限 190 | ````java 191 | 194 | ```` 195 | 如果是自己内部的应用想绑定到我们的服务中,只需要在它的 AndroidMenifest 文件中采用如下方式使用 permission : 196 | ````java 197 | 198 | ```` 199 | - 可以在服务端的 onTransact 方法中进行权限验证,如果验证失败就直接返回false,这样服务端就不会终止执行 AIDL 中的方法从而达到保护服务端的效果。 200 | 201 | 10. ContentProvider 的六个抽象方法: 202 | 203 | - onCreate:代表 ContentProvider 的创建,做一些初始化操作。 204 | - query:查找。 205 | - update:更新。 206 | - insert:插入。 207 | - delete:删除。 208 | - getType:用来返回一个 Uri 请求所对应的 MIME 类型(媒体类型),比如图片、视频等,如果不关心可以直接在方法中返回 null 或者 "\*/\*"。 209 | onCreate 由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在 Binder 线程池中。 210 | 211 | 11. ContentProvider 的权限可以细分为读权限和写权限,分别对应 android:readPermission 和 android:writePermission 属性,如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读/写操作,否则外界应用会异常终止。 212 | 12. android:authorities 是 ContentProvider 的唯一标识,通过这个属性外部应用就可以访问。 213 | 214 | ## Binder 连接池 215 | 1. Binder 连接池的工作原理 216 | ![Binder连接池的工作原理](http://o8fk8z4sl.bkt.clouddn.com/Binder%E8%BF%9E%E6%8E%A5%E6%B1%A0%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86.png) 217 | 218 | ## 选择合适的IPC方式 219 | 1. IPC 方式的优缺点和适用场景 220 | ![IPC 方式的优缺点和适用场景](http://o8fk8z4sl.bkt.clouddn.com/IPC%E6%96%B9%E5%BC%8F%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9%E5%92%8C%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF.png) 221 | 222 | 223 | 2017.8.15 224 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/JNI 和 NDK 编程.md: -------------------------------------------------------------------------------- 1 | # JNI 和 NDK 编程 2 | 1. Java JNI 本意是 Java Native Interface (Java 本地接口),是为了方便 Java 调用 C、C++等本地代码所封装的一层接口。 3 | 2. NDK 是 Android 所提供的一个工具集合,通过 NDK 可以在 Android 中更加方便地通过 JNI 来访问本地代码。 4 | 使用 NDK 的好处: 5 | - 提高代码的安全性; 6 | - 可以很方便地使用目前已有的 C/C++开源库; 7 | - 便于平台间的移植; 8 | - 通过程序在某些特定情形下的执行效率,但是并不能明显提升 Android 程序的性能。 9 | 10 | ## JNI 的开发流程 11 | 1. JNI 的开发流程: 12 | 13 | - 在 Java 中声明 native 方法; 14 | - 编译 Java 源文件得到 class 文件,然后通过 Java 命令导出 JNI 的头文件; 15 | - 实现 JNI 方法; 16 | - 编译 so 库并在 Java 中调用。 17 | 18 | ## NDK 的开发流程 19 | 1. NDK 的开发流程: 20 | 21 | - 下载并配置 NDK; 22 | - 创建一个 Android 项目,并声明所需的 native 方法; 23 | - 实现 Android 项目中所声明的 native 方法; 24 | - 切换到 jni 目录的父目录,然后通过 ndk-build 命令编译产生 so 库。 25 | 26 | ## JNI 的数据类型和类型签名 27 | 1. JNI 基本数据类型的对应关系 28 | ![JNI 基本数据类型的对应关系](http://o8fk8z4sl.bkt.clouddn.com/JNI%20%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%AF%B9%E5%BA%94%E5%85%B3%E7%B3%BB.png) 29 | 2. JNI 引用类型的对应关系 30 | ![JNI 引用类型的对应关系](http://o8fk8z4sl.bkt.clouddn.com/JNI%20%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%AF%B9%E5%BA%94%E5%85%B3%E7%B3%BB.png) 31 | 3. 基本数据类型的签名 32 | ![基本数据类型的签名](http://o8fk8z4sl.bkt.clouddn.com/%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E7%9A%84%E7%AD%BE%E5%90%8D.png) 33 | 34 | ## JNI 调用 Java 方法的流程 35 | 1. JNI 调用 Java 方法的流程是先通过类名找到类,然后再根据方法名找到方法的 id,最后调用这个方法。 36 | 37 | 38 | 39 | 2017.8.30 40 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/View 的工作原理.md: -------------------------------------------------------------------------------- 1 | # View 的工作原理 2 | ## ViewRoot 和 DecorView 3 | 1. ViewRoot 对应于 ViewRootImpl 类,是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。View 绘制流程是从 ViewRoot 的 performTraversals 方法开始的,经过 measure、layout 和 draw 三个过程才能最终将一个 View 绘制出来,其中 measure 用来测量 View 的宽和高,layout 用来确定 View 在父容器中的放置位置,draw 负责将 View 绘制在屏幕上。 4 | 5 | **performTraversals的大致流程** 6 | ![performTraversals的大致流程](http://o8fk8z4sl.bkt.clouddn.com/performTraversals%E7%9A%84%E5%A4%A7%E8%87%B4%E6%B5%81%E7%A8%8B.png) 7 | performTraversals 会依次调用 performMeasure、performLayout 和 preformDraw 三个方法,这三个方法分别完成了顶级 View 的 measure、layout 和 draw 这三大流程,其中 performMeasure 中会调用 measure 方法,在 measure 方法中又回调用 onMeasure 方法,在 onMeasure 方法中则会对所有子元素进行 measure 过程,这个时候 measure 流程就就从父容器传递到子元素中,完成一次 measure 过程,接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。 8 | 9 | ## MeasureSpec 10 | 1. MeasureSpec 代表一个32为 int 值,高2位代表 SpecMode,低30位代表 SpecSize,SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小。 11 | 2. SpecMode有三类 12 | 13 | - UNSPECIFIED 14 | 父容器不对 View 有任何限制,这种情况一般用于系统内部,表示一种测量状态。 15 | - EXACTLY 16 | 父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终代大小就是 SpecSize 所指定的值,对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式。 17 | - AT_MOST 18 | 父容器指定了一个可用大小即 SpecSize,View 的大小不能大于这个值,具体要看不同 View 的具体实现,对应于 LayoutParams 中的 wrap_content。 19 | 20 | 3. MeasureSpec 不是唯一由 LayoutParams 决定的,LayoutParams 需要和父容器一起才能决定 View 的 MeasureSpec,从而进一步决定 View 的宽/高。 21 | 对于 DecorView,其 MeasureSpec 由窗口的尺寸和其自身的 LayoutParams 来共同确定; 对于普通 View,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定。 22 | 4. DecorView 的 MeasureSpec 的产生过程遵守如下规则: 23 | 24 | - LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小。 25 | - LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小。 26 | - 固定大小:精确模式,大小为 LayoutParams 中指定的大小。 27 | 28 | 5. 当 View 采用固定宽/高的时候,不过父容器的 MeasureSpec 是什么,View 的 MeasureSpec 都是精确模式并且其大小遵循 LayoutParams 中的大小。 29 | 当 View 的宽/高是 match_parent 时,如果父容器的模式是精准模式,那么 View 也是精准模式并且其大小是父容器的剩余空间; 如果父容器是最大模式,那么 View 也是最大模式并且其大小不会超过父容器的剩余空间。 30 | 当 View 的宽/高是 wrap_content 时,不管父容器的模式是精准还是最大化,View 的模式总是最大化并且大小不能超过父容器的剩余空间。 31 | 32 | 6. 普通View的MeasureSpec的创建规则 33 | ![普通View的MeasureSpec的创建规则](http://o7qv8ih35.bkt.clouddn.com/%E6%99%AE%E9%80%9AView%E7%9A%84MeasureSpec%E7%9A%84%E5%88%9B%E5%BB%BA%E8%A7%84%E5%88%99.png) 34 | 35 | ## View 的工作流程 36 | 1. View 的工作流程主要是指 measure、layout、draw 这三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高,layout 确定 View 的最终宽/高和四个顶点的位置,drea 则将 View 绘制到屏幕上。 37 | 2. measure过程 38 | 39 | - View 的 measure 过程 40 | View 的 measure 过程由其 measure 方法来完成,measure 方法是一个 final 类型方法,在 View 的 measure 方法中回去调用 View 的 onMeasure 方法。 41 | 42 | getSuggestedMinimumWidth 的逻辑:如果 View 没有设置背景,那么返回 android:miniWidth 这个属性所指定的值,这个值可以为0;如果 View 设置了背景,则返回 android:minWidth 和背景的最小宽度这两者中的最大值,getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 的返回值就是 View 在 UNSPECIFIED 情况下的测量宽/高。 43 | 44 | 直接继承 View 的自定义控件需要重写 onMeasure 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 就相当于使用 match_parent。 45 | 46 | - ViewGroup 的 measure 过程 47 | 对于 ViewGroup 来说,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行这个过程。和 View 不同的是,ViewGroup 是一个抽象类,没有重写 View 的 onMeasure 方法,但是提供了一个 measureChildren 的方法。 48 | 49 | measureChildren 的思想就是取出子元素的 LayoutParams,然后再通过 getChildMeasureSpec 来创建子元素的 MeasureSpec,接着将 MeasureSpec直接传递给 View 的 measure 方法来进行测量。 50 | 51 | 实际上在 onCreate、onStart、onResume 中均无法正确得到某个 View 的宽/高信息,因为 View 的 measure 过程和 Activity 的生命周期方法不是同步执行的,无法保证 Activity 执行了 onCreate、onStart、onResume 时某个 View 已经测量完毕了。 52 | 解决方法: 53 | 54 | - Activity/View#onWindowFocusChanged 55 | 这个方法的含义是:View 已经初始化完毕,宽/高已经准备好了,这个时候去获取宽/高是没有问题,但是 onWindowFocusChanged 会被调用多次,当 Activity 的窗口得到焦点和失去焦点时均会被调用一次。 56 | - view.post(runnable) 57 | 通过 post 可以将一个 runnable 投递到消息队列的尾部,然后等待 Looper 调用此 runnable 的时候,View 也已经初始化好了。 58 | - ViewTreeObserver 59 | - view.measure(int widthMeasureSpec,int heightMeasureSpec) 60 | 通过手动对 View 进行 measure 来得到 View 的宽/高。这个需要根据 View 的 LayoutParams 来分情况处理: 61 | 1. match_parent: 62 | 直接放弃,无法 measure 出具体的宽/高,因为我们无法知道 parentSize 的大小,理论上不可能测量出 View 的大小。 63 | 2. 具体的数值(dp/px) 64 | 3. wrap_content 65 | 66 | **两种错误的用法** 67 | 一是违背了系统内部实现规范;其次不能保证一定能 measure 出正确的结果。 68 | 第一种错误用法 69 | ````java 70 | int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1,MeasureSpec.UNSPECIFIED); 71 | int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1,MeasureSpec.UNSPECIFIED); 72 | view.measure(widthMeasureSpec,heightMeasureSpec); 73 | ```` 74 | 第二种错误用法 75 | ````java 76 | view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); 77 | ```` 78 | 79 | - layout 过程 80 | Layout 的作用是 ViewGroup 用来确定子元素的位置,当 ViewGroup 的位置被确定后,它在 onLayout 中会遍历所有子元素并调用其 layout 方法,在 layout 方法中 onLayout 方法又会被调用。layout 方法确定 View 本身的位置,onLayout 方法则会确定所有子元素的位置。 81 | 82 | 大致流程: 83 | 首先会通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft、mRight、mTop 和 mBottom 这四个值,从而确定 View 在父容器中的位置。 84 | 接着会调用 onLayout 方法,用途是父容器确定子元素的位置。 85 | 86 | 和 onMeasure 方法类似,onLayout 的具体实现同样和具体的布局有关,所以 View 和 ViewGroup 均没有真正实现 onLayout 方法。 87 | 88 | - draw 过程 89 | 作用是将 View 绘制到屏幕上。 90 | View 的绘制过程遵循如下几步: 91 | 92 | - 绘制背景 background.draw(canvas); 93 | - 绘制自己 (onDraw); 94 | - 绘制 children(dispatchDraw); 95 | - 绘制装饰(onDrawScrollBars)。 96 | 97 | ## 自定义 View 98 | 1. 自定义 View 的分类 99 | 100 | - 继承 View 重写 onDraw 方法 101 | 主要用于实现一些不规则的效果往往需要静态或者动态地实现一些不规则的图形。采用这种方式需要自己支持 wrap_content,并且 padding 也需要自己处理。 102 | - 继承 ViewGroup 派生特殊的 Laout 103 | 主要用于实现自定义的布局,需要合适地处理 ViewGroup 的测量、布局这两个过程,并同时处理子元素的测量和布局过程。 104 | - 继承特定的 View(比如 TextView) 105 | 用于扩展某种已有的 View 的功能。 106 | - 继承特定的 ViewGroup (比如 LinearLayout) 107 | 与方法2的区别是方法2更接近 View 的底层。 108 | 109 | 2. 自定义 View 须知 110 | 111 | - 让 View 支持 wrap_content; 112 | - 如果有必要,让 View 支持 padding; 113 | - 尽量不要在 View 中使用 Handler,没必要; 114 | - View 中如果有线程或者动画,需要及时停止,参考 View#onDtachedFromWindow; 115 | - View 带有滑动嵌套情形时,需要处理好滑动冲突; 116 | 117 | 3. 添加自定义属性的方法 118 | 119 | - 在 values 目录下面创建自定义属性的 XML,在 XML 中声明一个自定义属性的集合; 120 | - 在 View 的构造方法中解析自定义属性的值并做相应处理; 121 | - 在布局文件中使用自定义属性。 122 | 需要注意的是为了使用自定义属性,必须在布局文件中添加 schemas 声明 123 | ```` 124 | xmlns:app = http://schemas.android.com/apk/res-auto 125 | ```` 126 | 或 127 | ```` 128 | xmlns:app = http://schemas.android.com/apk/res-auto/com.ryg_chapter_4 129 | ```` 130 | 131 | 132 | 133 | 2017.8.18 134 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/View的事件体系.md: -------------------------------------------------------------------------------- 1 | # View 的事件体系 2 | ## View 3 | 1. View 是 Android 中所有控件的基类,是一种界面层的抽象,代表了一个控件。ViewGroup 也继承了 View,意味着 View 本身就可以是单个控件也可以是由多个控件组成的一组控件。 4 | **View 树的结构** 5 | ![View 树的结构](http://o8fk8z4sl.bkt.clouddn.com/viewgroup.png) 6 | 2. View 的位置主要由它的四个顶点来决定,分别对应于 View 的四个属性: 7 | - top:左上角纵坐标; 8 | - left:左上角横坐标; 9 | - right:右下角横坐标; 10 | - bottom:右下角纵坐标; 11 | 需要注意的是这些坐标是相对于 View 的父容器来说的,是一种相对坐标。 12 | **View 的位置坐标和父容器的关系** 13 | ![View 的位置坐标和父容器的关系](http://o8fk8z4sl.bkt.clouddn.com/View%20%E7%9A%84%E4%BD%8D%E7%BD%AE%E5%9D%90%E6%A0%87%E5%92%8C%E7%88%B6%E5%AE%B9%E5%99%A8%E7%9A%84%E5%85%B3%E7%B3%BB.png) 14 | Viewd 的宽高和坐标的关系: 15 | >width = right - left 16 | height = bottom - top 17 | 18 | View 四个参数的获取方式 19 | 20 | - Left = getLeft(); 21 | - Right = getRight(); 22 | - Top = getTop(); 23 | - Bottom = getBottom(); 24 | 25 | Android 3.0后,View 新增了 x、y、translationX 和 translationY 四个参数。其中 x 和 y 是 View 左上角的坐标,translationX 和 translationY 是 View 左上角相对于父容器的偏移量,并且 translationX 和 translationY 的默认值为0。 26 | View 在平移过程中,top 和 left 表示的是原始左上角的位置信息,其值不会发生改变,发生改变的是 x、y、translationX 和 translationY。 27 | 28 | 3. MotionEvent 29 | 在手指接触屏幕后所产生的一系列事件中,典型的事件类型有: 30 | 31 | - ACTION_DOWN --- 手指刚接触屏幕; 32 | - ACTION_MOVE --- 手指在屏幕上移动; 33 | - ACTION_UP --- 手指从屏幕上松开的一瞬间; 34 | 正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件: 35 | 36 | - 点击屏幕后离开松开,事件序列为 DOWN -> UP; 37 | - 点击屏幕滑动一会再松开,事件序列为 DOWN -> MOVE -> ... -> MOVE -> UP。 38 | 39 | getX/getY 返回的是相对于当前 View 左上角的 x 和 y 坐标,getRawX/getRawY 返回的是相对于手机屏幕左上角的 x 和 y 坐标。 40 | 41 | 4. TouchSlop 是系统所能识别出的被认为是滑动的最小的距离,这是一个常量,和设备有关,可以通过 **ViewConfiguration.get(getContext()).getScaledTouchSlop()** 来获取这个常量。 42 | 43 | 5. VelocityTracker 44 | 速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。 45 | **使用方法:** 46 | 首先在 View 的 onTouchEvent 方法中追踪当前单击事件的速度: 47 | ````java 48 | VelocityTracker velocityTracker = VelocityTracker.obtained(); 49 | velocityTracker.addMovenment(event); 50 | ```` 51 | 接着当先知道当前的滑动速度时,可以采用如下方式获取当前的速度: 52 | ````java 53 | velocityTracker.computeCurrentVelocity(1000); //获取速度之前必须先计算速度,所以这个方法必须放在下面2个方法前面。 54 | int xVelocity = (int) velocityTracker.getXVelocity(); 55 | int yVelocity = (int) velocityTracker.getYVelocity(); 56 | ```` 57 | 这里的速度是指一段时间内手指所滑过的像素数,可以为负数,即当手指从右往左滑动时水平方向速度即为负数,公式: 58 | **速度 = (终点位置 - 起点位置)/ 时间段** 59 | 最后,当不需要使用的时候,调用 clear 方法来重置并回收内存 60 | ````java 61 | velocityTracker.clear(); 62 | velocityTracker.recycle(); 63 | ```` 64 | 65 | 6. GustureDetector 66 | 手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。 67 | **使用方法:** 68 | 首先创建一个 GustureDetector 对象并实现 OnGestureListener 接口,也可以实现 OnDoubleTapListener 从而能够监听双击行为: 69 | ````java 70 | GustureDetector mGustureDetector = new GustureDetector(this); 71 | //解决长按屏幕后无法拖动的现象 72 | mGustureDetector.setIsLongpressEnabled(false); 73 | ```` 74 | 接着接管目标 View 的 onTouchEvent 方法,在监听 View 的 onTouchEvent 方法中添加如下实现: 75 | ````java 76 | boolean consume = mGustureDetector.onTouchEvent(event); 77 | return consume; 78 | ```` 79 | **GustureDetector和onDoubleTaoListener中的方法介绍** 80 | ![GustureDetector和onDoubleTaoListener中的方法介绍](http://o8fk8z4sl.bkt.clouddn.com/GustureDetector%E5%92%8ConDoubleTaoListener%E4%B8%AD%E7%9A%84%E6%96%B9%E6%B3%95%E4%BB%8B%E7%BB%8D.jpg) 81 | >参考: 82 | 如果只是监听相关滑动,建议自己在 onTouchEvent 中实现,如果要监听双击这种行为的话,就使用 GestureDetector。 83 | 84 | 7. Scoller 85 | 弹性滑动对象,用于实现 View 弹性滑动,需要与 View 的 computeScroll 方法配合使用。 86 | **使用方法:** 87 | ````java 88 | Scroller scroller = new Scroller(mContext); 89 | 90 | //缓慢滚动到指定位置 91 | private void smoothScrollTo(int destX,int destY){ 92 | int scrollX = getScrollX(); 93 | int delta = destX - scrollX; 94 | //1000ms 内滑向 destX,效果就是慢慢滑动 95 | mScroller.startScroll(scrollX,0,dalta,0,1000); 96 | invalidate(); 97 | } 98 | 99 | @override 100 | public void computeScroll(){ 101 | if(mScroller.computeScrollOffset()){ 102 | scrollTp(mScroller.getCurrX(),mScroller.getCurrY()); 103 | postInvalidate(); 104 | } 105 | } 106 | ```` 107 | 108 | ## View 的滑动 109 | 1. 通过三种方式可以实现 View 的滑动: 110 | - 通过 View 本身提供的 scrollTo/scrollBy 方法来实现滑动。 111 | **mScrollX 和 mScrollY 的改变规则:** 112 | 在滑动的过程中,mScrollX 的值总是等于 View 左边缘和 View 内容左边缘在水平方向的距离,mScrollY 的值总是等于 View 上边缘和 View 的内容上边缘在竖直方向的距离。View 边缘是指 View 的位置,由四个顶点组成,View 的内容边缘是指 View 中的内容的边缘,scrollTo 和 scrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置,mScrollX 和 mScrollY 的单位为像素,当 View 左边缘在 View 内容边缘的右边时,mScrollX 为正值,反之为负值;当 View 上边缘在 View 内容上边缘的下边时,mScrollY 为正值,反之为负。也就是说如果从左往右滑动,那么 mScrollX 为负值,反之为正值;如果从上往下滑动,那么 mScrollY 为负值,反之为正值。 113 | 114 | 使用 scrollTo 和 scrollBy 来实现 View 的滑动,只能将 View 的内容进行移动,并不能将 View 本身进行移动,也就是说,不管怎么滑动,也不可能将当前 View 滑动到附近 View 所在的区域。 115 | 116 | - 通过动画给 View 施加平移效果来实现滑动。 117 | 通过动画能够让一个 View 进行平移,平移是一种滑动,使用动画来移动 View ,主要操作 View 的translationX 和 translationY 属性,既可以采用传统的 View 动画,也可以采用属性动画。 118 | 使用动画来做 View 的滑动需要注意一点,View 动画是对 View 的影像做操作,并不能真正改变 View 的位置参数,包括宽/高,如果希望动画后的状态得以保留还必须将 fillAfter 属性设置为 true,否则动画完成后其动画结果会消失。使用属性动画不存在这种问题,但是3.0以下无法使用属性动画。 119 | - 通过改变 View 的 LayoutParams 使得 View 重新布局从而实现滑动。 120 | ````java 121 | MarginLayoutParams params = (MarginLayoutParams) mButton1.getLayoutParams(); 122 | params.width += 100; 123 | params.leftMargin += 100; 124 | mButton1.requestLayout(); 125 | //或者 mButton1.setLayoutParams(params); 126 | ```` 127 | 2. 各种滑动方式的对比 128 | 129 | - scrollTo/scrollBy:操作简单,适合对 View 内容的滑动; 130 | - 动画:操作简单,主要适用于没有交互的 View 和实现复杂的动画效果; 131 | - 改变布局参数:操作稍微负责,适用于没有交互的 View。 132 | 133 | ## 弹性滑动 134 | 1. Scoller 如何让 View 弹性滑动的 135 | invalidate 方法会导致 View 重绘,在 View 的 draw 方法中又会去调用 computeScroll 方法,computeScroll 方法会在 View 中是一个空实现,因为这个 computeScroll 方法, View 才能实现弹性滑动。 136 | 2. Scroller 的工作原理 137 | Scroller 本身并不能实现 View 的滑动,需要配合 View 的 computeScroll 方法才能完成弹性滑动的效果,它不断让 View 重绘,每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔 Scroller 就可以得出 View 当前的滑动位置,知道了滑动位置就可以通过 scrollTo 方法来完成 View 的滑动。就这样,View 的每一次重绘都会导致 View 进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动。 138 | 3. 延时策略的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以用 Handler 和 View 的 postDelayed 方法,也可以使用线程的 sleep 方法。 139 | 140 | ## View 的事件分发机制 141 | 1. 所谓点击事件的事件分发,其实就是对 MotionEvent 事件的分发过程,即当一个 MotionEvent 产生了以后,系统需要把这个时间传递给一个具体的 View ,而这个传递的过程就是分发过程。 142 | 点击事件的分发过程由三个方法共同完成。 143 | 144 | - public boolean dispatchTouchEvent(MotionEvent ev) 145 | 用来进行事件的分发。如果事件能够传递给当前 View ,那么此方法一定会被调用,返回结果受当前 View 的 onTouchEvent 和下级 View 的 dispatchTouchEvent 方法的影响,表示是否消耗当前事件。 146 | - public boolean onInterceptTouchEvent(MotionEvent event) 147 | 在上述方法内部调用,用来判断是否拦截某个事件,如果当前 View 拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。 148 | - public boolean onTouchEvent(MotionEvent event) 149 | 在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前 View 无法再次接收到事件。 150 | 151 | **用伪代码表示此三者的关系:** 152 | ````java 153 | public boolean dispatchTouchEvent(MotionEvent ev){ 154 | boolean consume = false; 155 | if(onInterceptTouchEvent(ev)){ 156 | consume = onTouchEvent(ev); 157 | }else{ 158 | consume = child.dispatchTouchEvent(ev); 159 | } 160 | return consume; 161 | } 162 | ```` 163 | **传递规则:** 164 | 对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给它,这是它的 dispatchTouchEvent 就会被调用,如果这个 ViewGroup 的 onInterceptTouchEvent 方法返回 true 就表示它要拦截当前事件,接着事件就会交个这个 ViewGroup 处理,即它的 onTouchEvent 方法就会被调用;如果 ViewGroup 的 onInterceptTouchEvent 返回 false 就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的 dispatchTouchEvent 方法就会被调用,如此反复我都直到事件被最终处理。 165 | 166 | 当一个 View 需要处理事件时,如果设置了 OnTouchListener,那么 OnTouchListener 中的 onTouch 方法会被调用,这时事件如何处理还要看 onTouch 的返回值,如果返回 false,则当前 View 的 onTouchEvent 方法会被调用,如果返回 true,那么 onTouchEvent 方法将不会被调用。所以,给 View 设置的 OnTouchListener 其优先级比 onTouchEvent 要高,onClickListener 的优先级最低。 167 | 168 | 当一个事件产生后,它的传递过程遵循如下顺序:Activity -> Window -> View ,即事件总是先传递给 Activity,Activity 再传递给 Window,最后 Window 再传递给顶级 View,顶级 View 接收到事件后,就会按照事件分发机制去分发事件。 169 | 170 | 2. 当 ViewGroup 决定拦截事件后,那么后续的点击事件将会默认交给它处理并且不再调用它的 onInterceptTouchEvent 方法,FLAG_DISALLOW_INTERCEPT 这个标志的作用是让 ViewGroup 不再拦截事件,但前提是 ViewGroup 不拦截 ACTION_DOWN 事件。 171 | 172 | ## View 的滑动冲突 173 | 1. 常见的滑动冲突场景分为三种 174 | 175 | - 场景1 --- 外部滑动方向和背部滑动方向不一致; 176 | - 场景2 --- 外部滑动方向和内部滑动方向一致; 177 | - 场景3 --- 上两种情况的嵌套。 178 | 179 | 2. 处理规则 180 | 181 | - 对于场景1,处理规则是:当用户左右滑动时,需要让外包的 View 拦截点击事件,当用户上下滑动时,需要让内部 View 拦截点击事件。具体来说就是根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件。 182 | - 对于场景2,一般都能在业务上找到突破点。 183 | - 对于场景3,一般都能在业务上找到突破点。 184 | 185 | 3. 滑动冲突的解决方式 186 | 187 | - 外部拦截法 188 | 是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就不拦截。外部拦截法需要重写父容器的 onInterceptTouchEvent 方法,在内部做相应的拦截即可。 189 | - 内部拦截法 190 | 是指父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,需要配合 requestDisallowInterceptTouchEvent 方法才能正常工作,需要重写子元素的 dispatchTouchEvent 方法。 -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/四大组件的工作过程.md: -------------------------------------------------------------------------------- 1 | # 四大组件的工作过程 2 | ## 四大组件的运行状态 3 | 1. Activity 是一种展示型组件,用于向用户直接地展示一个界面,并且可以接收用户的输入信息从而进行交互。Activity 的启动由 Intent 触发,Intent 分为显示和隐式,显示 Intent 可以明确地指向一个 Activity 组件,隐式 Intent 则指向一个或多个目标 Activity 组件。在实际开发中可以通过 Activity 的 finish 方法来结束一个 Activity 组件的运行。 4 | 2. Service 是一种计算型组件,用于在后台执行一系列计算任务。Service 组件有启动状态和绑定状态,当处于启动状态时,Service 内部可以做一些后台计算,并且不需要和外界有直接的交互。当 Service 组件处于绑定状态时,Service 内部也同样可以进行后台计算,可以很方便地和 Service 组件进行通信。Service 组件的停止需要灵活采用 stopService 和 unBindService 这两个方法才能完全停止一个 Service 组件。 5 | 3. BroadcastReceiver 是一种消息型组件,用于在不同的组件乃至不同的应用之间传递消息。BroadcastReceiver 也叫广播,有静态注册和动态注册两种注册方式。静态注册是指在 AndroiManifest 中注册广播,此种形式的广播不需要启动就可以收到相应的广播;动态注册需要通过 Context.registerReceiver() 来实现,在不需要的时候要通过 Context.unRegisterReceiver() 来解除广播,此种形态的广播必须要应用启动才能注册并接收广播,实际开发中通过 Context 的一系列 send 方法来发生广播,被发送的广播会被系统发送给感兴趣的广播接收者,发送和接收过程的匹配是通过广播接收者 来描述的。BroadcastReceiver 组件没有停止的概念,不需要停止。 6 | 4. ContentProvicder 是一种数据共享型组件,用于想其他组件乃至其他应用共享数据。它的内部需要实现增删改查这四种操作,内部维持着一份数据集合, ContentProvicder 对数据集合的具体实现并没有任何要求,也不需要手动停止。 7 | 8 | ## Activity 的工作过程 9 | 1. 10 | Activity的启动过程在ActivityStackSypervisor和ActivityStack之间传递顺序 11 | ![ 12 | Activity的启动过程在ActivityStackSypervisor和ActivityStack之间传递顺序](http://o8fk8z4sl.bkt.clouddn.com/Activity%E7%9A%84%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B%E5%9C%A8ActivityStackSypervisor%E5%92%8CActivityStack%E4%B9%8B%E9%97%B4%E4%BC%A0%E9%80%92%E9%A1%BA%E5%BA%8F.png) 13 | 2. performLaunchActivity 这个方法主要完成的工作 14 | - 从 ActivityClientRecord 中获取待启动的 Activity 的组件信息; 15 | - 通过 Instrumentation 的 newsActivity 方法使用类加载器创建 Activity 对象; 16 | - 通过对 LoadedApk 的 makeApplication 方法来尝试创建 Application 对象; 17 | - 创建 ContextImpl 对象并通过 Activity 的 attach 方法来完成一些重要数据的初始化, 18 | - 调用 Activity 的 onCreate 方法。 19 | 20 | ## Service 的工作过程 21 | 1. Service 分为两种工作状态,一种是启动状态,主要用于执行后台计算;另一种是绑定状态,主要用于其它组件和 Service 的交互。Service 的这两种状态是可以共存的,即 Service 既可以处于启动状态也可以同时处于绑定状态。 22 | 2. Service 的启动过程 23 | 1. handleCreateService 主要完成的工作 24 | - 首先通过类加载器创建 Service 的实例; 25 | - 然后创建 Application 对象并调用其 onCreate; 26 | - 接着创建 ContextImpl 对象并通过 Service 的 attach 方法建立二者之间的关系; 27 | - 最后调用 Service 的 onCreate 方法将 Service 对象存储到 ActivityThread 中的一个列表中。 28 | 3. Service 的绑定过程 29 | 1. bindServiceCommon 方法主要完成的工作 30 | - 首先将客户端的 ServiceConnection 对象转化为 ServiceDispatcher.InnerConnection 对象; 31 | - 接着 bindServiceCommon 方法会通过 AMS 来完成 Service 的具体绑定过程,对应于 AMS 的 bindService 方法。 32 | 33 | ## BroadcastReceiver 的工作过程 34 | 1. 广播的发送类型有 35 | - 普通广播 36 | - 有许广播 37 | - 粘性广播 38 | 2. 广播的发送和接收其本质是一个过程的两个阶段。 39 | 3. 两个标记位 40 | - FLAG_INCLUDE_STOPPED_PACKAGES:表示包含已经停止的应用,这个时候广播会发生给已经停止的应用。 41 | - FLAG_EXCLUDE_STOPPED_PACKAGES:表示不包含已经停止的应用,这个时候广播不会发送给已经停止的应用。 42 | 43 | ## ContentProvicder 的工作过程 44 | 1. ContentProvicder 的启动过程 45 | ![ContentProvicder 的启动过程](http://o8fk8z4sl.bkt.clouddn.com/ContentProvider%E7%9A%84%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.png) 46 | 47 | 2. 一般来说,ContentProvicder 都应该是单实例的,但由它的 android:multiprocess 属性来决定的,当 android:multiprocess 为 false 时,ContentProvicder 是单实例;当 android:multiprocess 为 true 时,ContentProvicder 为多实例,这个时候在每个调用者的进程中都存在一个 ContentProvicder 对象。 48 | 3. ActivityThread 的 handleBindApplication 完成 Application 的创建以及 ContentProvicder 的创建的步骤: 49 | - 创建 ContextImpl 和 Instrumentation; 50 | - 创建 Application 对象; 51 | - 启动当前进程的 ContentProvicder 并调用其 onCreate 方法; 52 | - 调用 Application 的 onCreate 方法。 53 | 54 | 55 | 56 | 57 | 2017.8.24 58 | W.Z.H -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/理解 RemoteViews.md: -------------------------------------------------------------------------------- 1 | # 理解 RemoteViews 2 | RemoteViews 表示的是一个 View 结构,可以在其他进程中显示,RemoteViews 提供了一组基础的操作用于跨进程更新它的界面。RemoteViews 在 Android 中的使用场景有两种:通知栏和桌面小部件。 3 | 4 | ## RemoteViews 的应用 5 | 1. AppWidgetProvider 是 Android 中提供的用于实现桌面小部件的类,其本质是一个广播,即 BroadcastReceiver。 6 | 7 | - onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在带一次调用。 8 | - onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由 updatePeriodMillis 来指定,每个周期小部件都会自动更新一次。 9 | - onDeleted:每删除一次桌面小部件就调用一次。 10 | - onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。 11 | - onReceive:这是广播的内置方法,用于分发具体的事件给其他方法。会根据不同的 Action 来分别调用 onEnable、onDisabled 和 onUpdate 等方法。 12 | 13 | 2. PendingIntent 14 | PendingIntent 表示一种处于 pending 状态的意图,而 pending 状态表示的是一种待定、等待、即将发生的意思。和 Intent 的区别在于:PendingIntent 是在将来的某个待定的时刻发生,而 Intent 是立刻发生。PendingIntent 通过 send 和 cancel 方法来发送和取消特定的待定 Intent。 15 | 16 | PendingIntent 支持三种待定意图:启动 Activity、启动 Service 和发送广播 17 | **PendingIntent的主要方法** 18 | ![PendingIntent的主要方法](http://o8fk8z4sl.bkt.clouddn.com/PendingIntent%E7%9A%84%E4%B8%BB%E8%A6%81%E6%96%B9%E6%B3%95.png) 19 | requestCode 表示 PendingIntent 发送方的请求码,多数情况下设为0即可,也会影响到 flags 的效果。flags 常见类型有: 20 | 21 | - FLAG_ONE_SHOT 22 | 当前描述的 PendingIntent 只能使用一次,然后它就会自动 cancel,如果后续还有相同的 PendingIntent,那么它们的 send 方法就会调用失败。对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续的通知单击后将无法打开。 23 | - FLAG_NO_CREATE 24 | 当前描述的 PendingIntent 不会主动创建,如果当前 PendingIntent 之前不存在,那么 getActivity、getService 和 getBroadcast 方法就会直接返回 null。这个标记位无法单独使用。 25 | - FLAG_CANCEL_CURRENT 26 | 当前描述的 PendingIntent 如果已经存在,那么它都会被 cancel,然后系统会创建一个新的 PendingIntent。对于消息通知栏来说,那些被 cancel 的消息单击后将无法打开。 27 | - FLAG_UPDATE_CURRENT 28 | 当前描述的 PendingIntent 如果已经存在,那么它们都会被更新,即它们的 Intent 中的 Extras 会被替换成新的。 29 | 30 | PendingIntent 的匹配规则:如果两个 PendingIntent 它们内部的 Intent 相同并且 requestCode 也相同,那么这两个 PendingIntent 就是相同的。 31 | Intent 的匹配规则:如果两个 Intent 的 ComponentName 和 intent-filter 都相同,那么这两个 Intent 就是相同的。 32 | 33 | ## RemoteViews 的内部机制 34 | 1. RemoteViews 支持的类型: 35 | 36 | - Layout 37 | FrameLayout、LinearLayout、RelativeLayout、GridLayout 38 | - View 39 | AnalogClock、Button、Chronometer、ImageButton、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub 40 | 41 | 2. RemoteViews 的部分 set 方法 42 | ![RemoteViews的部分set方法](http://o7qv8ih35.bkt.clouddn.com/RemoteViews%E7%9A%84%E9%83%A8%E5%88%86set%E6%96%B9%E6%B3%95.png) 43 | 44 | 3. RemoteViews 的内部机制 45 | ![RemoteViews的内部机制](http://o8fk8z4sl.bkt.clouddn.com/RemoteViews%E7%9A%84%E5%86%85%E9%83%A8%E6%9C%BA%E5%88%B6.png) 46 | -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/理解 Window 和 WindowManager.md: -------------------------------------------------------------------------------- 1 | # 理解 Window 和 WindowManager 2 | Window 是一个抽象类,它的具体实现是 PhoneWindow,创建一个 Window 只需要通过 WindowManager 即可完成,WindowManager 是外界访问 Window 的人口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程,Android 中所有的视图都是通过 Window 来呈现的,Window 实际是 View 的直接管理者。 3 | 4 | ## Window 和 WindowManager 5 | 1. WindowManager.LayoutParams 中的 flags 和 type 参数。 6 | Flags 参数表示 Window 的属性, 7 | - FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也不需要接受各种输入事件,同时会开启 FLAG_NOT_TOUVH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。 8 | - FLAG_NOT_TOUVH_MODAL:在此模式下,系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理。 9 | - FLAG_SHOW_WHEN_LOCKED:开启此模式可以让 Window 显示在锁屏的界面上。 10 | 11 | Type 参数表示 Window 的类型。Window有三种类型: 12 | 13 | - 应用 Window:对应着一个 Activity; 14 | - 子 Window:不能单独存在,需要附属在特定的父 Window 之中; 15 | - 系统 Window:是需要声明权限才能创建的 Window。 16 | 17 | Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 上面,应用 Window 的层级范围是1-999,子 Window 的层级范围是 1000-1999,系统 Window 的层级范围是 2000-2999。这些层级范围对应着 WindowManager.LayoutParams 的 type 参数。 18 | 19 | WindowManager 提供的三个常用方法:添加 View,更新 View,删除 View。 20 | 21 | ## Window 的内部机制 22 | 1. Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,是以 View 的形式存在,View 才是 Window 存在的实体。 23 | 24 | 2. Window 的添加过程 25 | Window 的添加过程需要通过 WindowManager 的 addView 来实现。 26 | - 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数; 27 | - 创建 ViewRootImpl 并将 View 添加到列表中; 28 | - 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程。 29 | 30 | 3. Window 的删除过程 31 | 先通过 WindowManagerImpl,再通过 WindowManagerGlobal 来实现。 32 | removeView 的逻辑是首先通过 findViewLocked 来查找待删除的 View 的索引,这个查找过程就是建立的数组遍历,然后再调用 removeViewLocked 来做进一步的删除。 33 | 在 WindowManager 中提供了两种删除接口 removeView 和 removeViewImmediate,分别表示异步删除和同步删除。 34 | 异步删除操作:由 ViewRootImpl 的 die 方法完成,在异步删除的情况下,die 方法只是发生了一个请求删除的消息后就立刻返回,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews 中,mDyingViews 表示待删除的 View 列表。 35 | dispatchDetachedFromWindow 方法主要做四件事: 36 | - 垃圾回收相关的工作,比如清除数据和消息、移除回调; 37 | - 通过 Session 的 remove 方法删除 Window:mWindowSession.remove(mWindow),这同样是一个 IPC 过程,最终会调用 WindowManagerService 的 removeWindow 方法。 38 | - 调用 View 的 dispatchDetachedFromWindow 方法,在内部会调用 View 的 onDetachedFromWindow() 以及 onDetachedFromWindowInternal()。 39 | - 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams 以及 mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除。 40 | 41 | 4. Window 的更新过程 42 | updateViewLayout 方法: 43 | 首先需要更新 View 的 LayoutParams 并替换掉老的 LayoutParams,接着再更新 ViewRootImpl 中的 LayoutParams,这是通过 ViewRootImpl 的 setLayoutParams 方法来实现的。在 ViewRootImpl 中会通过 scheduleTraversals 方法来对 View 重新布局,包括测量、布局、重绘这三个过程。ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图,这个过程最终室友 WindowManagerService 的 relayoutWindow() 来具体实现,同样也是一个 IPC。 44 | 45 | ## Window 的创建过程 46 | 1. Activity 的 Window 的创建过程 47 | PhoneWindow 的 setContentView 方法大致遵循如下几个步骤 48 | - 如果没有 DecorView,那么就创建它; 49 | DecorView 的创建过程由 installDecor 方法来完成,在方法内部会通过 generateDecor 方法来直接创建 DecorView。为了初始化 DecorView 的结构,PhoneWindow 还需要通过 generateLayout 方法来加载具体的布局文件到 DecorView 中。 50 | - 将 View 添加到 DecorView 的 mContentParent 中; 51 | mLayoutInflater.inflate(layoutResID,mContentParent); 52 | - 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变。 53 | 54 | 在 ActivityThread 的 handleResumeActivity 方法中,首先会调用 Activity 的 onResume 方法,接着会调用 Activity 的 makeVisible(),这时候 DecorView 才真正地完成了添加和显示这两个过程,Activity 才能被用户看到。 55 | 56 | 2. Dialog 的 Window 的创建过程 57 | Dialog 的 Window 的创建过程和 Activity 类似, 58 | - 创建 Window 59 | - 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中 60 | - 将 DecorView 添加到 Window 中并显示 61 | 62 | 当 Dialog 被关闭时,它会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)。 63 | 64 | 3. Toast 的 Window 的创建过程 65 | 在 Toast 内部有两个 IPC 过程:第一类是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回调 Toast 里的 TN 接口。 66 | Toast 的显示过程:它调用了 NMS 中的 enqueueToast 方法,enqueueToast 方法第一个参数表示当前应用的包名,第二个参数 tn 表示远程回调,第三个参数表示 Toast 的时长,enqueueToast 首先将 Toast 请求封装为 ToastRecord 对象并将其添加到一个名为 mToastQueue 的队列中,mToastQueue 其实是一个 ArrayList,mToastQueue 中最多能同时存在50个 ToastRecord,这样做是为了放置 DOS;当 ToastRecord 被添加到 mToastQueue 中后,NMS 就好通过 showNextToastLocked 方法来显示当前的 Toast,Toast 的显示是由 ToastRecord 的 callback 来完成的,这个 callback 实际上就是 Toast 中的 TN 对象的远程 Binder;Toast 显示以后, NMS 还会通过 scheduleTimeoutLocked 方法来发送一个延时消息。 67 | 68 | Toast 的隐藏也是通过 ToastRecord 的 callback 来完成的,也是一次 IPC 过程。 69 | 70 | Toast 的显示和隐藏过程实际上是通过 Toast 中的 TN 这个类来实现的,有 show 和 hide 两个方法,分别对应 Toast 的显示和隐藏,因为这两个方法是被 NMS 以跨进程的方式调用的,运行在 Binder 线程池中,内部使用了 Handler 来将执行环境切换到 Toast 请求所在的线程,两个方法中的 mShow 和 mHide 是两个 Runnable,内部分别调用了 handleShow 和 handleHide 方法,handleShow 和 handleHide 才是真正完成显示和隐藏 Toast 的地方,TN的 handleShow 中会将 Toast 的视图添加到 Window 中,handleHide 中会将 Toast 的视图从 Window 中移除。 71 | 72 | 73 | 74 | 2017.8.22 75 | W.Z.H 76 | 77 | 78 | -------------------------------------------------------------------------------- /Android/《Android开发艺术探索》笔记/综合技术.md: -------------------------------------------------------------------------------- 1 | # 综合技术 2 | ## 使用 CrashHandler 来获取应用的 crash 信息 3 | 1. 获取应用 crash 信息的方式: 4 | - 首先需要实现一个 UncaughtExceptionHandler 对象,在它的 uncaughtException 方法中获取异常信息并将其存储在 SD 卡中或者上传到服务器供开发人员分析; 5 | - 然后调用 Thread 中的 setDefaultUncaughtExceptionHandler 方法将它设置为默认线程的异常处理器,由于默认异常处理器是 Thread 类的静态成员,它的作用是当前进程的所有线程。 6 | 7 | ## 使用 multidex 来解决方法数越界 8 | 1. 在代码中加入支持 multidex 的功能有三种方案: 9 | 10 | - 在 manifest 文件中指定 Application 为 MultidexApplication 11 | ````java 12 | 16 | 17 | ```` 18 | - 让应用的 Application 继承 MultiDexApplication, 19 | ````java 20 | public class TestApplication extends MultiDexApplication{ 21 | ... 22 | } 23 | ```` 24 | - 如果不想让应用的 Application 继承 MultiDexApplication,可以选择继承重写 Application 的 attachBaseContext 方法,这个方法比 Application 的 onCreate 要先执行, 25 | ````java 26 | public class TestApplication extends Application{ 27 | @Override 28 | protected void attachedBaseContext(Context base){ 29 | super.attachBaseContext(base); 30 | MultiDex.install(this); 31 | } 32 | } 33 | ```` 34 | 35 | 2. multidex 可能带来的问题: 36 | 37 | - 应用启动速度会降低,甚至可能出现 ANR 现象 38 | - 由于 Dalvik linearAlloc 的 bug,这可能导致使用 multidex 的应用无法在 Android 4.0 以前的手机上运行,因此需要做大量的兼容性测试。 39 | 40 | ## Android 的动态加载技术 41 | 1. 宿主是指普通的 apk,而插件一般是指经过处理的 dex 或者 apk,在主流的插件化框架中多采用经过特殊处理的 apk 来作为插件。 42 | 2. 插件化的三个基础性问题: 43 | 44 | - 资源访问 45 | 插件中凡是以 R 开头的资源都不能访问,因为宿主程序中并没有插件资源。 46 | 解决方法: 47 | 加载资源的方法是通过反射,通过调用 AssetManager 中的 addAssetPath 方法,我们可以将一个 apk 中的资源加载到 Resource 对象中,由于 addAssetPath 是隐藏的 API 我们无法直接调用,只能通过反射;接着在代理 Activity 中实现 getAsset() 和 getResource()。 48 | - Activity 生命周期的管理 49 | 管理 Activity 的生命周期的方式有许多,如 反射方式和接口方式。 50 | 反射方式: 51 | 首先通过 Java 的反射去获取 Activity 的各种生命周期的方法,然后在代理 Activity 中去调用插件 Activity 对应的生命周期方法即可。 52 | 缺点是:一方面反射代码写起来比较复杂,另一方面是过多使用反射会有一定的性能开销。 53 | 接口方式: 54 | 这种方式是将 Activity 的生命周期方法提取出来作为一个接口,然后通过代理 Activity 去调用插件 Activity 的生命周期方法。 55 | - 插件 ClassLoader 的管理 56 | 57 | ## 反编译 58 | 59 | 60 | 61 | 2017.8.30 62 | W.Z.H -------------------------------------------------------------------------------- /Android/笔记/Android 中常见的内存泄漏.md: -------------------------------------------------------------------------------- 1 | # Android 中常见的内存泄漏 2 | ## Activity 对象未被回收 3 | ### 静态变量引用 Activity 对象 4 | 通过静态变量引用 Activity 对象时,会导致 Activity 对象所占内存泄漏。主要是因为静态变量是驻扎在 JVM 的方法区,因此,静态变量引用的对象是不会被 GC 回收的,因为它们所引用的对象本身就是 GC ROOT,即最终导致 Activity 对象不被回收,从而也就造成内存泄漏。 5 | ### 静态 View 6 | 有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View是一个静态变量,所以导致Activity不被回收。因此在使用静态View时,需要确保在资源回收时,将静态View detach掉。 7 | ### 内部类 8 | 因为非静态内部类持有外部类的引用,所以如果我们在一个外部类中定义一个静态变量,这个静态变量是引用内部类对象,将会导致内存泄漏,因为这相当于间接导致静态类引用外部类。 9 | ### 匿名类 10 | 与内部类一样,匿名类也会持有外部类的引用 11 | ### Handler 12 | 如果在Activity中定义Handler对象,那么Handler肯定是持有Activty的引用。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。当然,如果消息正在准备(处于延时入队期间)放入到消息队列中也是一样的。 13 | 解决办法就是,将Handler放入单独的类或者将Handler放入到静态内部类中(静态内部类不会持有外部类的引用)。如果想要在handler内部去调用所在的外部类Activity,可以在handler内部使用弱引用的方式指向所在Activity,这样不会导致内存泄漏。 14 | ### Threads和TimerTask 15 | Threads和Timer导致内存泄漏的原因跟内部类一样。虽然在新的线程中创建匿名类,但是只要是匿名类/内部类,它都会持有外部类引用。 16 | ### 监听器 17 | 当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。 18 | ## 集合对象造成的泄漏 19 | 当我们定义一个静态的集合类时,这可能会导致内存泄漏!静态变量所引用的对象是不会被回收掉的。而静态集合类中,包含有大量的对象,这些对象不会被回收。另外,如果集合中保存的对象又引用到了其他的大对象,如超长字符串、Bitmap、大数组等,很容易造成OOM。 20 | ## 资源对象没关闭造成内存泄漏 21 | 当打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用Bitmap资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。 22 | ## 使用对象池避免频繁创建对象 23 | 在我们需要频繁创建使用某个类时,或者是在for循环里面创建新的对象时,导致JVM不断创建同一个类。 -------------------------------------------------------------------------------- /Android/笔记/Android 自定义 View 的知识点.md: -------------------------------------------------------------------------------- 1 | # Android 自定义 View 的知识点 2 | ## Android 中 getWidth() 和 getMeasuredWidth() 之间的区别 3 | getMeasuredWidth() 获取的是 View 原始的大小,也就是这个 View 在 XML 文件中配置或者是代码中设置的大小。getWidth() 获取的是这个 View 最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。 4 | 5 | ## View 的坐标系 6 | 屏幕的左上角为坐标的原点,屏幕上边缘往右为X轴正方向,屏幕左边缘往下为Y轴正方向。 7 | 8 | - View 自身坐标: 9 | - getLeft():子 View 的左边缘相对于父 View 左边缘的距离,单位是像素。 10 | - getTop():子 View 的右边缘相对于父 View 左边缘的距离,单位是像素。 11 | - getRight():子 View 的上边缘相对于父 View 上边缘的距离,单位是像素。 12 | - getBottom():子 View 的下边缘相对于父 View 上边缘的距离,单位是像素。 13 | - View 自身宽高: getWidth(),getMeasuredWidth(),getHeight(),getMeasuredHeight() 14 | - MotionEvent 获取坐标: 15 | - getX():点击事件的点在控件中的X坐标,即点相对于控件左边缘的距离。 16 | - getY():点击事件的点在控件中的Y坐标,即点相对于控件上边缘的距离: 17 | - getRawX():点击事件的点在整个屏幕中的X坐标,即点相对于屏幕左边缘的距离。 18 | - getRawY():点击事件的点在整个屏幕中的Y坐标,即点相对于屏幕上边缘的距离。 19 | - View 的 Padding: 20 | - getPaddingLeft():View 里的 content 距离 View 左边缘的距离。 21 | - getPaddingTop():View 里的 content 距离 View 上边缘的距离。 22 | - getPaddingRight():View 里的 content 距离 View 右边缘的距离。 23 | - getPaddingBottom():View 里的 content 距离 View 下边缘的距离。 24 | 25 | ![ 26 | 自定义 View 的示例](http://7xq2jk.com1.z0.glb.clouddn.com/%E8%87%AA%E5%AE%9A%E4%B9%89%20View%20%E7%9A%84%E7%A4%BA%E4%BE%8B.png) 27 | **图片摘自:http://blog.csdn.net/jason0539/article/details/42743531** 28 | 29 | 30 | -------------------------------------------------------------------------------- /Android/笔记/Android中的Theme.md: -------------------------------------------------------------------------------- 1 | # Androdi中的Theme 2 | 在AndroidManifest.xml文件中有 3 | ```` 4 | android:theme="@style/AppTheme" 5 | ```` 6 | 这段代码,用来引用res/values/styles.xml 中的主题样式。 7 | ```` 8 | 9 | 14 | ```` 15 | AppTheme的主题样式继承自parent中定义的主题,我们修改parent后的值可以实现不同的主题。 16 | 17 | 使用android系统中自带的主题要加上“android:”,如:android:Theme.Black 18 | 使用v7兼容包中的主题不需要前缀,直接:Theme.AppCompat 19 | 20 | 下面列举下一些主题 21 | 系统自带主题: 22 | ```` 23 | API 1: 24 | android:Theme 根主题 25 | android:Theme.Black 背景黑色 26 | android:Theme.Light 背景白色 27 | android:Theme.Wallpaper 以桌面墙纸为背景 28 | android:Theme.Translucent 透明背景 29 | android:Theme.Panel 平板风格 30 | android:Theme.Dialog 对话框风格 31 | 32 | API 11: 33 | android:Theme.Holo Holo根主题 34 | android:Theme.Holo.Black Holo黑主题 35 | android:Theme.Holo.Light Holo白主题 36 | 37 | API 14: 38 | Theme.DeviceDefault 设备默认根主题 39 | Theme.DeviceDefault.Black 设备默认黑主题 40 | Theme.DeviceDefault.Light 设备默认白主题 41 | 42 | API 21: (网上常说的 Android Material Design 就是要用这种主题) 43 | Theme.Material Material根主题 44 | Theme.Material.Light Material白主题 45 | 46 | 47 | 兼容包v7中带的主题: 48 | Theme.AppCompat 兼容主题的根主题 49 | Theme.AppCompat.Black 兼容主题的黑色主题 50 | Theme.AppCompat.Light 兼容主题的白色主题 51 | ```` 52 | 53 | Theme.AppCompat主题是兼容主题,是指如果运行程序的手机API是21则就相当于是Material主题,如果运行程序的手机API是11则就相当于是Holo主题,以此类推 54 | 55 | --- 56 | 参考: 57 | [总结一下Android中主题(Theme)的正确玩法](http://www.cnblogs.com/zhouyou96/p/5323138.html) -------------------------------------------------------------------------------- /Android/笔记/Android面试题整理.md: -------------------------------------------------------------------------------- 1 | # Android面试题整理 2 | 3 | 1. 什么是ANR和Force Close?如何避免? 4 | ANR: Application Not Responding 5 | 产生原因: 6 | - 主线程(UI线程)响应用户操作事件事件超过5秒。 7 | - BroadcastReceive超过10秒钟未执行完毕。 8 | 避免方法: 9 | Android应用程序完全运行在一个独立的线程中。任何在主线程中运行的,需要消耗大量时间的操作都会引发ANR。因此,需要消耗大量事件的操作如访问网络和数据库,都要放到子线程中或者使用异步方式来完成。 10 | 11 | Force Close 12 | 产生原因: 13 | 程序出现异常,一般像空指针、数组越界、类型转换异常等。 14 | 避免方法: 15 | 编写程序时要思维缜密,异常出现时可以通过logcat查看抛出异常代码出现的位置,然后去程序中进行修改。 16 | 17 | 2. 请描述下Activity的生命周期。 18 | ![Activity的生命周期](https://uploadfiles.nowcoder.com/images/20170123/976341_1485165828993_C4FBAB681EDE2673E8C75536C5B00931) 19 | 20 | - onCreate 21 | >在Activity第一次被创建的时候调用,可以在这个方法中完成Activity的初始化操作,比如加载布局,绑定事件。 22 | - onStart 23 | >在Activity由不可见变为可见时候调用。 24 | - onResume 25 | >在Activity准备好和用户交互时调用,此时Activity一定位于返回栈的栈顶,并处于运行状态。 26 | - onPause 27 | >在系统准备去启动或者恢复另一个Activity时候调用。可以在这个方法中将一些消耗CPU的资源释放掉,以及保持一些关键数据(但不能做耗时操作,否则影响新的栈顶Activity的使用)。 28 | - onStop 29 | >在Activity完全看不见的时候调用。与onPause的区别:如果启动一个对话框。那么onPause会执行,onStop不会。 30 | - onDestroy 31 | >在Activity被销毁前调用,之后活动变为销毁状态。 32 | - onRestart 33 | >这个活动由停住状态变化运行状态之前调用,也就是被重新启动了。 34 | 35 | 我们可以将Activity分为三种生存周期 36 | 37 | - 完整生存期 38 | >由 onCreate->onDestroy 39 | - 可见生存期 40 | >由 onStart->onStop 41 | - 前台生存期 42 | >由 onResume->onPause 43 | 44 | 45 | 3. 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 46 | 多线程的实现方法: 47 | 48 | - 继承Thread类。 49 | 1. 自定义类MyThread继承Thread类。 50 | 2. MyThread类里面重写run()。 51 | 3. 创建对象。 52 | 4. 启动线程。 53 | - 实现Runnable结口。 54 | 1. 自定义类MyRunnable实现Runnable接口。 55 | 2. 重新run()方法。 56 | 3. 创建MyRunnable类对象。 57 | 4. 创建Thread类的对象,并把上一步的对象作为构造参数传递。 58 | - 实现Callable接口,重写call()方法。但是这个方法不常用。 59 | 60 | 同步的实现方法: 61 | 62 | - synchronized关键字:包括synchronized方法和synchronized块。 63 | - wait()方法和notify()方法。 64 | - Lock接口及其实现类RenntrantLock。 65 | 66 | 4. 如何退出Activity?如何安全退出已调起多个Activity的Application? 67 | - finish() 。 68 | - 新建一个类ActivityCollector用于管理全部的Activity对象,每生成一个Activity对象就将其添加到ActivityCollector一个List中,在ActivityCollector中实现一个finishAll()方法,用于结束list中所有的Activity对象。 69 | 70 | 5. 用至少两种方式实现一个Singleton(单例模式)。 71 | - 静态内部类实现:这种方式是 Singleton 类被装载了, instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance 。 72 | ```` 73 | public class Signleton{ 74 | private static class SingletonHolder{ 75 | private static final Singleton INSTANCE = new Singleton(); 76 | } 77 | private Singleton(){} 78 | public static final Singleton getInstance(){ 79 | return SingletonHolder.INSTANCE; 80 | } 81 | } 82 | ```` 83 | - 双重检查锁:在JDK1.5之后,双重检查锁定才能够正常达到单例效果。(因为volitile关键字)。 84 | ```` 85 | public class Signleton{ 86 | private volatile static Singleton singleton; 87 | private Singleton(){} 88 | public static Singleton getSingleton(){ 89 | if(singleton == null){ 90 | synchronized(Singleton.class){ 91 | if(singleton == null){ 92 | singleton = new Singleton(); 93 | } 94 | } 95 | } 96 | return singleton; 97 | } 98 | } 99 | ```` 100 | 101 | 6. Intent传递数据时,下列的数据类型哪些可以被传递? 102 | - Serializable:将Java对象序列化为二进制文件的Java序列化技术是Java系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现Serializable接口,使用ObjectInputStream和ObjectOutputStream进行对象的读写。 103 | - charsequence:在JDK1.4中,引入了CharSequence接口,实现了这个接口的类有:CharBuffer、String、StringBuffer、StringBuilder这四个类。CharBuffer为nio里面用的一个类,String实现这个接口理所当然,StringBuffer也是一个CharSequence,StringBuilder是Java抄袭C#的一个类,基本和StringBuffer一样,效率高,但是不保证线程安全,在不需要多线程的环境下可以考虑。 104 | 提供这么一个接口,有些处理String或者StringBuffer的类就不用重载了。但是这个接口提供的方法有限,只有下面几个:charat、length、subSequence、toString这几个方法,如果有必要,重载比较好,避免用instanceof这个操作符。 105 | - Parcelable:android提供了一只新的类型:Parcel。本类被用作封装数据的容器,封装后的数据可以通过Intent或IPC传递。除了基本类型以外,只有实现了Parcelable接口类才能被放入Parcel中。 106 | - Bunldle:Bundle是将数据传递到另一个上下文中或报酬或回复你自己状态的数据存储方式。它的数据不是持久化状态。 107 | 108 | 7. 一个GLSurfaceView类 , 具有以下特点 : 109 | - 管理一个平面, 这个平面是一个特殊的内存块 , 它可以和 android 视图系统混合 。 110 | - 管理一个EGL 显示 , 它能够让 OpenGL 渲染到一个平面 。 111 | - 接受一个用户提供的实际显示的Renderer 对象 。 112 | - 使用一个专用线程去渲染从而和UI 线程解耦 。 113 | - 支持on-demand 和连续的渲染。 114 | - 可选的包, 追踪 和 / 或者错误检查这个渲染器的 OpenGL 调用 。 115 | 116 | 8. 简述Andriod如何处理UI与耗时操作的通信,有哪些方式及各自的优缺点。 117 | 118 | 主要有三种方法,一为Handler,二为AsyncTask,三为自己开子线程执行耗时操作,然后调用Activity的runOnUiThread()方法更新ui。 119 | - handler机制是,在主线程中创建handler对象,当执行耗时操作时,创建一个线程,在这个线程中执行耗时操作,通过调用handler的sendMessage,post等方法,更新ui界面。 120 | handler机制的优点是结构清晰,功能明确,但是代码过多。 121 | - AysncTask本质上是一个线程池,所有的异步任务都会在这个线程池中的工作线程中执行,当需要操作ui界面时,会和工作线程通过handler传递消息。 122 | AysncTask简单,快捷,但是可能会新开大量线程,消耗系统资源,造成Force Close。 123 | - 自己开子线程执行耗时操作,然后调用Activity的runOnUiThread()方法更新ui。这种方法需要把context对象强制转换成activity后使用。 124 | 第三种方法最好用,代码也非常简单,只是需要传递context对象。 125 | 126 | 9. Android中Looper的实现原理,为什么调用Looper.prepare()就在当前线程关联了一个Looper对象,它是如何实现的。 127 | 128 | - 线程间通信机制 129 | 首先,looper、handler、messagequeue三者共同实现了android系统里线程间通信机制。如在A、B两个子线程之间需要传递消息,首先给每个子线程绑定一套looper、handler、messagequeue,然后这三个对象都与其所属线程对应。然后A线程通过调用B线程的Handler对象,发送消息。这个消息被Handler发送到B线程的messageQueue中,而属于B线程的Looper对象一直在for循环里无限遍历MessageQueue,一旦发现该消息队列里收到新的消息,就会对消息进行处理,处理过程中会回调自身Handler的headleMessage方法。从而实现了不同线程间通信。 130 | - Looper实现原理 131 | Looper类里包含了一个消息队列对象和一个线程对象。当创建Looper时,会自动创建一个消息队列,同时将内部线程对象指向创建Looper的线程。当开启Looper后(looper.loop()),会自动进入无限for循环中,不断去遍历消息队列,如果没有消息阻塞,有消息则回调handler的handlemessage方法进行处理。 132 | - Looper.prepare() 133 | 首先,要使用Looper机制一般会在当前线程创建Hnadler对象,里面会自动创建一个looper对象消息队列,这里面的消息队列属于当前线程空间。但此时的looper还不会去遍历,也没有绑定到当前线程。其中,looper对象内部也包含一个空消息队列对象和空线程。通过Looper.prepare()方法,先让该消息队列指向当前线程的消息队列,让控线程也指向对其线程,从而实现绑定。 134 | 135 | 10. 分辨率相关 136 | 137 | 在 Android 中, 1pt 大概等于 2.22sp以上, 138 | 与分辨率无关的度量单位可以解决这一问题。Android支持下列所有单位。 139 | px(像素):屏幕上的点。 140 | in(英寸):长度单位。 141 | mm(毫米):长度单位。 142 | pt(磅):1/72英寸。 143 | dp(与密度无关的像素):一种基于屏幕密度的抽象单位。在每英寸160点的显示器上,1dp = 1px。 144 | dip:与dp相同,多用于android/ophone示例中。 145 | sp(与刻度无关的像素):与dp类似,但是可以根据用户的字体大小首选项进行缩放。 146 | 分辨率:整个屏是多少点,比如800x480,它是对于软件来说的显示单位,以px为单位的点。 147 | density(密度)值表示每英寸有多少个显示点,与分辨率是两个概念。apk的资源包中, 148 | 当屏幕density=240时使用hdpi标签的资源 149 | 当屏幕density=160时,使用mdpi标签的资源 150 | 当屏幕density=120时,使用ldpi标签的资源。 151 | 一般android设置长度和宽度多用dip,设置字体大小多用sp. 在屏幕密度为160,1dp=1px=1dip, 1pt = 160/72 sp 1pt = 1/72 英寸.当屏幕密度为240时,1dp=1dip=1.5px. 152 | 153 | 11. andorid数据持久化的方法。 154 | Android数据持久化有五种方式: 155 | 156 | - SharedPreferences 157 | - 内部存储(例如通过openFileOutput()打开一个文件输入输出流) 158 | - SQLite Database 159 | - 网络连接(将数据存储到服务器上) 160 | - 外部存储(SD卡) -------------------------------------------------------------------------------- /Android/笔记/GreenDao 的简单使用.md: -------------------------------------------------------------------------------- 1 | # GreenDao 的简单使用 2 | 3 | ## GreenDao 的使用方法 4 | 1. app 目录下的 build.gradle 配置如下: 5 | ````groovy 6 | apply plugin: 'org.greenrobot.greendao' 7 | 8 | ... 9 | dependencies { 10 | implementation 'org.greenrobot:greendao:3.2.2' 11 | } 12 | ```` 13 | 14 | 2. 在bulid.gradle下进行配置 15 | ````groovy 16 | buildscript { 17 | repositories { 18 | jcenter() 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | classpath 'com.android.tools.build:gradle:3.1.1' 24 | classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' 25 | } 26 | } 27 | ```` 28 | 29 | 3. 对greendao的generator生成文件进行配置 30 | ````groovy 31 | greendao { 32 | schemaVersion 1 // 版本 33 | daoPackage '生成文件包名' // 一般为app包名+生成文件的文件夹名 34 | targetGenDir 'src/main/java' // 生成文件路径 35 | } 36 | ```` 37 | 38 | 4. 使用 GreenDao 39 | 新建一个 MyApplication 类,继承自 Application, 40 | ````java 41 | 42 | public class MyApplication extends Application { 43 | 44 | private static volatile MyApplication instance = null; 45 | 46 | private DaoMaster.DevOpenHelper mHelper; 47 | 48 | private SQLiteDatabase db; 49 | 50 | private DaoMaster mDaoMaster; 51 | 52 | private DaoSession daoSession; 53 | 54 | private MyApplication(){} 55 | 56 | public static synchronized MyApplication getInstance(){ 57 | if (instance == null) { 58 | instance = new MyApplication(); 59 | } 60 | return instance; 61 | } 62 | 63 | @Override 64 | public void onCreate(){ 65 | super.onCreate(); 66 | setDataBase(); 67 | } 68 | 69 | 70 | /** 71 | *配置数据库 72 | * 73 | */ 74 | private void setDataBase(){ 75 | // 创建数据库 shop.db 76 | mHelper = new DaoMaster.DevOpenHelper(this,"shop.db",null); 77 | // 获取可写数据库 78 | db = mHelper.getWritableDatabase(); 79 | // 回去数据库对象 80 | DaoMaster = new DaoMaster(db); 81 | // 获取Dao 对象管理者 82 | daoSession = mDaoMaster.newSession(); 83 | 84 | } 85 | 86 | public DaoSession gerDaoSession(){ 87 | return daoSession; 88 | } 89 | 90 | public SQLiteDatabase getDb(){ 91 | return db; 92 | } 93 | 94 | } 95 | 96 | ```` 97 | 98 | ## 操作 99 | ### 增 100 | 101 | insert(Entity entity):插入一条记录 102 | 103 | ````java 104 | /** 105 | * 增 106 | */ 107 | public void insert(){ 108 | String date = new Date().toString(); 109 | //第一个是id值,因为是自增长所以不用传入 110 | mDayStep = new dayStep(null,date,0); 111 | dao.insert(mDayStep); 112 | } 113 | 114 | ```` 115 | 116 | ### 删 117 | 118 | deleteByKey(Long key):根据主键删除一条记录。 119 | delete(Entity entity):根据实体类删除一条记录,一般结合查询方法没,查询出一条记录之后删除。 120 | deleteAll():删除所有记录。 121 | 122 | ````java 123 | /** 124 | * 删 125 | * @param i 删除数据的id 126 | */ 127 | public void delete(long i){ 128 | //当然Greendao还提供了其他的删除方法,只是传值不同而已 129 | dao.deleteByKey(i); 130 | } 131 | 132 | ```` 133 | ### 改 134 | 135 | update(Entitiy entity):更新一条记录; 136 | ````java 137 | /** 138 | *改 139 | * @param i 140 | * @param date 141 | */ 142 | public void correct(long i,String date){ 143 | mDayStep = new dayStep((long) i,date,0); 144 | dao.update(mDayStep); 145 | } 146 | 147 | ```` 148 | 149 | ### 查 150 | 151 | - loadAll() : 查询所有记录; 152 | - load(Long key) : 根据主键查询一条记录; 153 | - queryBuilder().list() : 返回:List; 154 | - queryBuilder.where(UserDao.Properties.Name.eq("")).list() : 返回:List; 155 | - queryRaw(String where,String selectionArg) : 返回:List。 156 | 157 | ````java 158 | 159 | /** 160 | * 查 161 | */ 162 | public void Search(){ 163 | //方法一 164 | List mDayStep = dao.loadAll(); 165 | //方法二 166 | //List mDayStep = dao.queryBuilder().list(); 167 | //方法三 惰性加载 168 | //List mDayStep = dao.queryBuilder().listLazy(); 169 | for (int i = 0; i < mDayStep.size(); i++) { 170 | String date = ""; 171 | date = mDayStep.get(i).getDate(); 172 | Log.d("cc", "id: "+i+"date: "+date); 173 | } 174 | 175 | } 176 | 177 | 178 | /** 179 | *查找符合某一字段的所有元素 180 | */ 181 | public void searchEveryWhere(String str){ 182 | List mList = dao.queryBuilder() 183 | .where(dao.date 184 | .eq(str)) 185 | .build() 186 | .listLazy(); 187 | } 188 | 189 | 190 | ```` 191 | ### 修改或者替换 192 | ````java 193 | /** 194 | *修改或者替换(有的话就修改,没有则替换) 195 | */ 196 | public void insertOrReplace(long i,String date){ 197 | mDayStep = new dayStep((long) i,date,0); 198 | dao.insertOrReplace(mDayStep); 199 | } 200 | 201 | ```` 202 | ## Greendao注解含义 203 | 204 | @Entity 实体标识: 205 | 206 | - schema:告知GreenDao当前实体属于哪个schema; 207 | - active:标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法; 208 | - nameInDb:在数据库中使用的别名,默认使用的是实体的类名; 209 | - indexs:定义索引,可以跨越多个列; 210 | - createInDb:标记创建数据库表。 211 | 212 | 基础属性注解: 213 | 214 | - @Id:主键 Long 型,可以通过@Id(autoincrement = true)设置自增长; 215 | - @Property:设置一个非默认关系映射所对应的列名,默认是使用字段名。例如: @Property(nameInDb = "name"); 216 | - @NotNull:设置数据库表当前列不能为空; 217 | - @Transient:添加此标记后不会生成数据库表的列。 218 | 219 | 索引注解: 220 | 221 | - @Index:使用@Index作为一个属性来创建一个索引,通过name设置索引别名,也可以通过unique给索引添加约束; 222 | - @Unique:向数据库添加了一个唯一的约束。 223 | 224 | 关系注解: 225 | 226 | - @ToOne:定义与另一个实体(一个实体对象)的关系; 227 | - @ToMany:定义与多个实体对象的关系。 228 | 229 | 230 | -------------------------------------------------------------------------------- /Android/笔记/Service小记.md: -------------------------------------------------------------------------------- 1 | # Service 小记 2 | ## 先来扯点 3 | Service作为Android四大组件之一,相信学过Android的都知道这货,它在每一个应用程序中都扮演着重要的角色. 4 | Service是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件.Service可由其他应用组件启动,而且即使用户切换到其他应用,Service仍将在后台继续运行.Service主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务.必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态. 5 | Service和Activity很相似,但是区别在于:Service一直在后台运行,没有用户界面,所以不会到前台,如果Service被启动起来,就和Activity一样,具有自己的声明周期. 6 | 7 | >注:在开发中,Activity和Service的选择标准是:如果程序组件在需要在运行时向用户呈现某种界面,或者程序需要与用户进行交互,那么使用Activity,否则考虑使用Service. 8 | 9 | 另外,需要注意的是,Service和Thread不是一个意思,不要被Service的后台概念所迷惑.实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程中的.因此,我们需要在Service的内部手动创建子线程,并在这里执行具体的任务,否则可能造成ANR的问题. 10 | 11 | ## Service的形式 12 | Service基本上分为两种形式: 13 | 14 | - Started(启动的) 15 | + 当应用组件(如 Activity)通过调用 startService() 启动Service时,Service即处于“启动”状态.一旦启动,Service即可在后台无限期运行,即使启动Service的组件已被销毁也不受影响.通常,一个开启的Service执行单一操作并且不会给调用者返回结果.例如,它可能通过网络下载或上传文件.操作完成后,Service会自行停止运行. 16 | - Bound(绑定的) 17 | + 当应用组件通过调用 bindService() 绑定到Service时,Service即处于“绑定”状态.一个绑定的Service提供客户端/服务器接口允许组件和Service交互,甚至跨进程操作使用进行间通信(IPC).仅当与另一个应用组件绑定时,绑定服务才会运行.多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁. 18 | 19 | ## Service的使用 20 | ### 几个方法 21 | 想要创建一个service,你必须创建一个Service的子类(或一个它的存在的子类).在你的实现中,你需要重写一些回调方法来处理Service生命周期的关键方面并且对于组件绑定到Service提供一个机制.应重写的最重要的回调方法包括: 22 | 23 | - onStartCommand() 24 | + 当其它组件(例如activity)通过调用**startService()** 来请求Service启动时系统会调用这个方法.一旦这个方法执行,service就启动并且可以无限地运行在后台.如果你实现这个,它主要负责当任务完成后停止service,通过调用**stopSelf()** 或 **stopService()**.(如果你只想提供绑定,你不需要实现这个方法.) 25 | - onBind() 26 | + 当其它组件通过调用**bindService()**来绑定到Service时系统会调用这个方法.在实现的这个方法中,你必须提供一个客户端用来和Service交互的接口,通过返回一个IBinder.你必须实现这个方法,但如果你不想允许绑定,你应该返回null. 27 | - onCreate() 28 | + 首次创建Service时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前).如果服务已在运行,则不会调用此方法. 29 | - onDestroy() 30 | + 当Service不再使用且将被销毁时,系统将调用此方法.Service应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等.这是Service接收的最后一个调用. 31 | 32 | 33 | >如果希望在Service组件做某些事情,那么只要在onCreate()或onStratCommand()方法中定义相关业务代码即可.而当服务销毁时,我们应该在onDestroy()方法中去回收那些不再使用的资源. 34 | 35 | ### 使用清单文件声明服务 36 | 如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务. 37 | ````java 38 | 39 | ... 40 | 41 | 42 | ... 43 | 44 | 45 | ```` 46 | 47 | ### 创建启动Service 48 | 直接用代码了,比较清楚. 49 | 50 | ````java 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | Log.d(TAG, "onCreate() executed"); 55 | } 56 | 57 | @Override 58 | public int onStartCommand(Intent intent, int flags, int startId) { 59 | Log.d(TAG, "onStartCommand() executed"); 60 | return super.onStartCommand(intent, flags, startId); 61 | } 62 | 63 | @Override 64 | public void onDestroy() { 65 | super.onDestroy(); 66 | Log.d(TAG, "onDestroy() executed"); 67 | } 68 | 69 | @Override 70 | public IBinder onBind(Intent intent) { 71 | return null; 72 | } 73 | ```` 74 | 然后在activity_main中添加2个按钮. 75 | ````java 76 | 80 | 81 |