├── .stackedit-data └── templates.json ├── .stackedit-trash ├── README.1.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSJ9.sync ├── README.1.md ├── README.md ├── README.md.1.1.md ├── README.md.1.md └── README.md.md ├── Android BroadCastReceiver再理解.md ├── Android Context的理解.md ├── Android Stduio打开报错.md ├── Android View的事件分发机制和滑动冲突解决.md ├── Android stduio instant run install app error.md ├── Android view滑动冲突的处理.md ├── Android 事件分发机制.md ├── Android中Gradle基础介绍.md ├── Android中Gradle的基础构建.md ├── Android中adb命令抓取logcat 日志.md ├── Android权限之通知、自启动跳转.md ├── Fragment中onHiddenChanged、setUserVisibleHint触发条件.md ├── HttpClient与HttpURLConnection的区别.md ├── ListView、RecycleView的item点击事件无效.md ├── Mac os 配置gradle环境变量.md ├── Mac os 配置java环境变量.md ├── README.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSIsInRlbXBsYXRlSWQiOiJqZWt5bGxTaXRlIn0.publish ├── README.md ├── activity的生命周期再理解.md ├── android 进程保活机制.md ├── git使用.md ├── webview上传文件与h5交互.md ├── 实际面试的问答补充.md ├── 排序算法总结.md ├── 面试知识点 问答.md └── 面试知识点 └── 排序算法总结.md /.stackedit-data/templates.json: -------------------------------------------------------------------------------- 1 | {"id":"templates","type":"data","data":{},"hash":-972705388} -------------------------------------------------------------------------------- /.stackedit-trash/README.1.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSJ9.sync: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoganZy/AndroidTotal/08f8ddbb142da71719af8503bc040b26e21d4b40/.stackedit-trash/README.1.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSJ9.sync -------------------------------------------------------------------------------- /.stackedit-trash/README.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

AndroidTotal

7 |

一些Android项目开发中的知识累积

8 |
9 |

目录

10 |
11 | 19 | 20 | -------------------------------------------------------------------------------- /.stackedit-trash/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

AndroidTotal

7 |

一些Android项目开发中的知识累积

8 |
9 |

目录

10 |
11 | 26 | ) 27 | -------------------------------------------------------------------------------- /.stackedit-trash/README.md.1.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

AndroidTotal

7 |

一些Android项目开发中的知识累积

8 |
9 |

目录

10 |
11 | 35 | 36 | -------------------------------------------------------------------------------- /.stackedit-trash/README.md.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

AndroidTotal

7 |

一些Android项目开发中的知识累积

8 |
9 |

目录

10 |
11 | 35 | 36 | -------------------------------------------------------------------------------- /.stackedit-trash/README.md.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

AndroidTotal

7 |

一些Android项目开发中的知识累积

8 |
9 |

目录

10 |
11 | 19 | 20 | -------------------------------------------------------------------------------- /Android BroadCastReceiver再理解.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Android BroadCastReceiver再理解

7 |

什么是BroadcastReceiver?、

8 |

广播(Broadcast)是在组件之间传播数据的一种机制,这些组件可以位于不同的进程中,起到进程间通信的作用

9 |

BroadcastReceiver 是对发送出来的 Broadcast 进行过滤、接受和响应的组件。首先将要发送的消息和用于过滤的信息(Action,Category)装入一个 Intent 对象,然后通过调用 Context.sendBroadcast()sendOrderBroadcast() 方法把 Intent 对象以广播形式发送出去。 广播发送出去后,所以已注册的 BroadcastReceiver 会检查注册时的 IntentFilter 是否与发送的 Intent 相匹配,若匹配则会调用 BroadcastReceiver 的 onReceiver() 方法

10 |

所以当我们定义一个 BroadcastReceiver 的时候,都需要实现 onReceiver() 方法。BroadcastReceiver 的生命周期很短,在执行 onReceiver() 方法时才有效,一旦执行完毕,该Receiver 的生命周期就结束了

11 |

BroadcastReceiver的种类

12 | 18 |

BroadcastReceiver的注册

19 |

静态注册

20 |
21 |

静态注册即在清单文件中为 BroadcastReceiver 进行注册,使用**< receiver >**标签声明,并在标签内用 < intent-filter > 标签设置过滤器。这种形式的 BroadcastReceiver 的生命周期伴随着整个应用,如果这种方式处理的是系统广播,那么不管应用是否在运行,该广播接收器都能接收到该广播。

22 |
23 |
<receiver 
 24 |     android:enabled=["true" | "false"]
 25 | 	//此broadcastReceiver能否接收其他App的发出的广播
 26 | 	//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
 27 |     android:exported=["true" | "false"]
 28 |     android:icon="drawable resource"
 29 |     android:label="string resource"
 30 | 	//继承BroadcastReceiver子类的类名
 31 |     android:name=".mBroadcastReceiver"
 32 | 	//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
 33 |     android:permission="string"
 34 | 	//BroadcastReceiver运行所处的进程
 35 | 	//默认为app的进程,可以指定独立的进程
 36 | 	//注:Android四大基本组件都可以通过此属性指定自己的独立进程
 37 |     android:process="string" >
 38 | 
 39 | 	//用于指定此广播接收器将接收的广播类型
 40 | 	//本示例中给出的是用于接收网络状态改变时发出的广播
 41 | 	 <intent-filter>
 42 | 		<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
 43 |     </intent-filter>
 44 | </receiver>
 45 | 
46 |

动态注册

47 |
48 |

动态注册 BroadcastReceiver 是在代码中定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调用 Context.registerReceiver() 方法,调用 Context.unregisterReceiver() 方法取消注册,此时就不需要在清单文件中注册 Receiver 了

49 |
50 |
  // 选择在Activity生命周期方法中的onResume()中注册
 51 |   @Override
 52 |   protected void onResume(){
 53 |       super.onResume();
 54 | 	  // 1. 实例化BroadcastReceiver子类 &  IntentFilter
 55 |       mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
 56 |       IntentFilter intentFilter = new IntentFilter();
 57 |       // 2. 设置接收广播的类型
 58 |       intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
 59 |       // 3. 动态注册:调用Context的registerReceiver()方法
 60 |       registerReceiver(mBroadcastReceiver, intentFilter);
 61 |   }
 62 |   // 注册广播后,要在相应位置记得销毁广播
 63 |   // 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
 64 |   // 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
 65 |   // 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
 66 |   @Override
 67 |   protected void onPause() {
 68 |       super.onPause();
 69 |       //销毁在onResume()方法中的广播
 70 |       unregisterReceiver(mBroadcastReceiver);
 71 |   }
 72 | 
73 |

在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。

74 |
75 |

不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
76 | 当系统因为内存不足(优先级更高的应用需要内存,请看上图红框)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
77 | 假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。
78 | 但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。

79 |
80 |

两种注册方式的区别

81 |

broadcastreceiver.png

82 | 85 |
    86 |
  1. 消息订阅者(广播接收者)
  2. 87 |
  3. 消息发布者(广播发布者)
  4. 88 |
  5. 消息中心(AMS,即Activity Manager Service
  6. 89 |
90 |

shiyitu.png

91 |

广播的类型

92 | 112 |

具体说明如下:

113 |

普通广播(Normal Broadcast)
114 | 即 开发者自身定义 intent的广播(最常用)。发送广播使用如下:

115 |
  Intent intent = new Intent();
116 |   //对应BroadcastReceiver中intentFilter的action
117 |   intent.setAction(BROADCAST_ACTION);
118 |   //发送广播
119 |   sendBroadcast(intent);
120 | 
121 |

若被注册了的广播接收者中注册时intentFilter的action与上述匹配,则会接收此广播(即进行回调onReceive())。如下mBroadcastReceiver则会接收上述广播

122 |
<receiver 
123 |     //此广播接收者类是mBroadcastReceiver
124 |     android:name=".mBroadcastReceiver" >
125 |     //用于接收网络状态改变时发出的广播
126 |     <intent-filter>
127 |         <action android:name="BROADCAST_ACTION" />
128 |     </intent-filter>
129 | </receiver>
130 | 
131 |

若发送广播有相应权限,那么广播接收者也需要相应权限
132 | 2. 系统广播(System Broadcast)

133 |

Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
134 | 每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
135 | 系统操作 action
136 | 监听网络变化 android.net.conn.CONNECTIVITY_CHANGE
137 | 关闭或打开飞行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
138 | 充电时或电量发生变化 Intent.ACTION_BATTERY_CHANGED
139 | 电池电量低 Intent.ACTION_BATTERY_LOW
140 | 电池电量充足(即从电量低变化到饱满时会发出广播 Intent.ACTION_BATTERY_OKAY
141 | 系统启动完成后(仅广播一次) Intent.ACTION_BOOT_COMPLETED
142 | 按下照相时的拍照按键(硬件按键)时 Intent.ACTION_CAMERA_BUTTON
143 | 屏幕锁屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS
144 | 设备当前设置被改变时(界面语言、设备方向等) Intent.ACTION_CONFIGURATION_CHANGED
145 | 插入耳机时 Intent.ACTION_HEADSET_PLUG
146 | 未正确移除SD卡但已取出来时(正确移除方法:设置–SD卡和设备内存–卸载SD卡) Intent.ACTION_MEDIA_BAD_REMOVAL
147 | 插入外部储存装置(如SD卡) Intent.ACTION_MEDIA_CHECKING
148 | 成功安装APK Intent.ACTION_PACKAGE_ADDED
149 | 成功删除APK Intent.ACTION_PACKAGE_REMOVED
150 | 重启设备 Intent.ACTION_REBOOT
151 | 屏幕被关闭 Intent.ACTION_SCREEN_OFF
152 | 屏幕被打开 Intent.ACTION_SCREEN_ON
153 | 关闭系统时 Intent.ACTION_SHUTDOWN
154 | 重启设备 Intent.ACTION_REBOOT
155 | 注:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播

156 |
    157 |
  1. 有序广播(Ordered Broadcast)
  2. 158 |
159 |

定义
160 | 发送出去的广播被广播接收者按照先后顺序接收
161 | 有序是针对广播接收者而言的

162 |

广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)

163 |

按照Priority属性值从大-小排序;
164 | Priority属性相同者,动态注册的广播优先;
165 | 特点

166 |

接收广播按顺序接收
167 | 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
168 | 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
169 | 具体使用
170 | 有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:

171 |

sendOrderedBroadcast(intent);
172 | 4. App应用内广播(Local Broadcast)

173 | 183 |

其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
184 | 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
185 | 即会出现安全性 & 效率性的问题。

186 |
187 |

解决方案

188 |
189 | 192 |
193 |

1.App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。
194 | 2.相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高

195 |
196 | 199 |

1.注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
200 | 2.在广播发送和接收时,增设相应权限permission,用于权限验证;
201 | 3.发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

202 |
203 |

通过**intent.setPackage(packageName)**指定报名

204 |
205 | 209 |
210 |

注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册

211 |
212 |
//注册应用内广播接收器
213 | //步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver 
214 | mBroadcastReceiver = new mBroadcastReceiver(); 
215 | IntentFilter intentFilter = new IntentFilter(); 
216 | //步骤2:实例化LocalBroadcastManager的实例
217 | localBroadcastManager = LocalBroadcastManager.getInstance(this);
218 | //步骤3:设置接收广播的类型 
219 | intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
220 | //步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 
221 | localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
222 | //取消注册应用内广播接收器
223 | localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
224 | //发送应用内广播
225 | Intent intent = new Intent();
226 | intent.setAction(BROADCAST_ACTION);
227 | localBroadcastManager.sendBroadcast(intent);
228 | 
229 |

5. 粘性广播(Sticky Broadcast)

230 |

由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

231 |

使用私有权限

232 |

使用动态注册广播接收器存在一个问题,即系统内的任何应用均可监听并触发我们的 Receiver 。通常情况下我们是不希望如此的

233 |

解决办法之一是在清单文件中为 < receiver > 标签添加一个 android:exported=“false” 属性,标明该 Receiver 仅限应用内部使用。这样,系统中的其他应用就无法接触到该 Receiver 了

234 |

此外,也可以选择创建自己的使用权限,即在清单文件中添加一个 < permission > 标签来声明自定义权限

235 |
    <permission
236 |         android:name="com.example.permission.receiver"
237 |         android:protectionLevel="signature" />
238 | 
239 | 
240 |

自定义权限时必须同时指定 protectionLevel 属性值,系统根据该属性值确定自定义权限的使用方式

241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 |
属性值限定方式
normal默认值。较低风险的权限,对其他应用,系统和用户来说风险最小。系统在安装应用时会自动批准授予应用该类型的权限,不要求用户明确批准(虽然用户在安装之前总是可以选择查看这些权限)
dangerous较高风险的权限,请求该类型权限的应用程序会访问用户私有数据或对设备进行控制,从而可能对用户造成负面影响。因为这种类型的许可引入了潜在风险,所以系统可能不会自动将其授予请求的应用。例如,系统可以向用户显示由应用请求的任何危险许可,并且在继续之前需要确认,或者可以采取一些其他方法来避免用户自动允许
signature只有在请求该权限的应用与声明权限的应用使用相同的证书签名时,系统才会授予权限。如果证书匹配,系统会自动授予权限而不通知用户或要求用户的明确批准
signatureOrSystem系统仅授予Android系统映像中与声明权限的应用使用相同的证书签名的应用。请避免使用此选项,“signature”级别足以满足大多数需求,“signatureOrSystem”权限用于某些特殊情况

首先,新建一个新的工程,在它的清单文件中创建一个自定义权限,并声明该权限。protectionLevel 属性值设为“signature

268 |
    <permission
269 |         android:name="com.example.permission.receiver"
270 |         android:protectionLevel="signature" />
271 |     <uses-permission android:name="com.example.permission.receiver" />
272 | 
273 |

然后,发送含有该权限声明的 Broadcast 。这样,只有使用相同证书签名且声明该权限的应用才能接收到该 Broadcast 了

274 |
    private final String PERMISSION_PRIVATE = "com.example.permission.receiver";
275 |     @Override
276 |     protected void onCreate(Bundle savedInstanceState) {
277 |         super.onCreate(savedInstanceState);
278 |         setContentView(R.layout.activity_main);
279 |     }
280 |     public void sendPermissionBroadcast(View view) {
281 |         sendBroadcast(new Intent("Hi"), PERMISSION_PRIVATE);
282 |     }
283 | 
284 |

回到之前的工程
285 | 首先在清单文件中声明权限

286 |
<uses-permission android:name="com.example.permission.receiver" />
287 | 
288 |

创建一个 BroadcastReceiver

289 |
public class PermissionReceiver extends BroadcastReceiver {
290 |     private final String TAG = "PermissionReceiver";
291 |     public PermissionReceiver() {
292 |     }
293 |     @Override
294 |     public void onReceive(Context context, Intent intent) {
295 |         Log.e(TAG, "接收到了私有权限广播");
296 |     }
297 | }
298 | 
299 |

然后注册广播接收器。因为 Android Studio 在调试的时候会使用相同的证书为每个应用签名,所以,在之前新安装的App发送出广播后,PermissionReceiver 就会输出 Log 日志

300 |
    private final String PERMISSION_PRIVATE = "com.example.permission.receiver";
301 | 
302 |     private PermissionReceiver permissionReceiver;
303 | 
304 |     @Override
305 |     protected void onCreate(Bundle savedInstanceState) {
306 |         super.onCreate(savedInstanceState);
307 |         setContentView(R.layout.activity_main);
308 | 
309 |         IntentFilter intentFilter1 = new IntentFilter("Hi");
310 |         permissionReceiver = new PermissionReceiver();
311 |         registerReceiver(permissionReceiver, intentFilter1, PERMISSION_PRIVATE, null);
312 |     }
313 | 
314 |     @Override
315 |     protected void onDestroy() {
316 |         super.onDestroy();
317 |         unregisterReceiver(permissionReceiver);
318 |     }
319 | 
320 |

特别注意

321 |

对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

322 | 328 |

BroadcastReceiver一些使用场景

329 | 335 | 336 | -------------------------------------------------------------------------------- /Android Context的理解.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Android Context的理解

7 |

转载自 https://www.jianshu.com/p/94e0f9ab3f1d
8 | Activity mActivity =new Activity()

9 |

作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

10 |

** Context到底是什么**

11 |

Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。

12 |

那Context到底是什么呢?一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。

13 |

如何生动形象的理解Context

14 |

上面的概念中采用了通俗的理解方式,将Context理解为“上下文”或者“场景”,如果你仍然觉得很抽象,不好理解。在这里我给出一个可能不是很恰当的比喻,希望有助于大家的理解:一个Android应用程序,可以理解为一部电影或者一部电视剧,Activity,Service,Broadcast Receiver,Content Provider这四大组件就好比是这部戏里的四个主角:胡歌,霍建华,诗诗,Baby。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在Context环境下(摄像机镜头)。那Button,TextView,LinearLayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在Context环境下),所以Button mButton=new Button(Context)是可以的。虽然不很恰当,但还是很容易理解的,希望有帮助。

15 |

源码中的Context

16 |
/**
 17 |  * Interface to global information about an application environment.  This is
 18 |  * an abstract class whose implementation is provided by
 19 |  * the Android system.  It
 20 |  * allows access to application-specific resources and classes, as well as
 21 |  * up-calls for application-level operations such as launching activities,
 22 |  * broadcasting and receiving intents, etc.
 23 |  */
 24 | public abstract class Context {
 25 |     /**
 26 |      * File creation mode: the default mode, where the created file can only
 27 |      * be accessed by the calling application (or all applications sharing the
 28 |      * same user ID).
 29 |      * @see #MODE_WORLD_READABLE
 30 |      * @see #MODE_WORLD_WRITEABLE
 31 |      */
 32 |     public static final int MODE_PRIVATE = 0x0000;
 33 |     
 34 |     public static final int MODE_WORLD_WRITEABLE = 0x0002;
 35 | 
 36 |     public static final int MODE_APPEND = 0x8000;
 37 | 
 38 |     public static final int MODE_MULTI_PROCESS = 0x0004;
 39 | 
 40 |     .
 41 |     .
 42 |     .
 43 |     }
 44 | 
 45 | 
46 |

源码中的注释是这么来解释Context的:Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。既然上面Context是一个抽象类,那么肯定有他的实现类咯,我们在Context的源码中通过IDE可以查看到他的子类最终可以得到如下关系图:

47 |

48 |

Context.png

49 |

Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所以函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。一句话总结:Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。

50 |

一个应用程序有几个Context

51 |

其实这个问题本身并没有什么意义,关键还是在于对Context的理解,从上面的关系图我们已经可以得出答案了,在应用程序中Context的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有Activity,Service持有Context,那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。上面的关系图也从另外一个侧面告诉我们Context类在整个Android系统中的地位是多么的崇高,因为很显然Activity,Service,Application都是其子类,其地位和作用不言而喻。

52 |

Context能干什么

53 |

Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到Context。

54 |

 55 | TextView tv = new TextView(getContext());
 56 | 
 57 | ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
 58 | 
 59 | AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);
 60 | 
 61 | getApplicationContext().getContentResolver().query(uri, ...);
 62 | 
 63 | getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
 64 | 
 65 | getContext().startActivity(intent);
 66 | 
 67 | getContext().startService(intent);
 68 | 
 69 | getContext().sendBroadcast(intent);
 70 | 
 71 | 
72 |

Context作用域

73 |

虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

74 |

75 |

Context作用域.png

76 |

从上图我们可以发现Activity所持有的Context的作用域最广,无所不能。因为Activity继承自ContextThemeWrapper,而Application和Service继承自ContextWrapper,很显然ContextThemeWrapper在ContextWrapper的基础上又做了一些操作使得Activity变得更强大,这里我就不再贴源码给大家分析了,有兴趣的童鞋可以自己查查源码。上图中的YES和NO我也不再做过多的解释了,这里我说一下上图中Application和Service所不推荐的两种使用情况。
77 | 1:如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。
78 | 2:在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
79 | 一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

80 |

如何获取Context

81 |

通常我们想要获取Context对象,主要有以下四种方法
82 | 1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
83 | 2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
84 | 3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
85 | 4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

86 |

getApplication()和getApplicationContext()

87 |

上面说到获取当前Application对象用getApplicationContext,不知道你有没有联想到getApplication(),这两个方法有什么区别?相信这个问题会难倒不少开发者。

88 |

89 |

getApplication()&getApplicationContext().png

90 |

程序是不会骗人的,我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是Application本身的实例。那么问题来了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。

91 |
publicclassMyReceiverextendsBroadcastReceiver{
 92 | 
 93 | @Override
 94 | publicvoidonReceive(Contextcontext,Intentintent){
 95 | ApplicationmyApp=(Application)context.getApplicationContext();
 96 | 
 97 | }
 98 | 
 99 | }
100 | 
101 | 
102 |

Context引起的内存泄露

103 |

但Context并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。

104 |

错误的单例模式

105 |
public class Singleton {
106 |     private static Singleton instance;
107 |     private Context mContext;
108 | 
109 |     private Singleton(Context context) {
110 |         this.mContext = context;
111 |     }
112 | 
113 |     public static Singleton getInstance(Context context) {
114 |         if (instance == null) {
115 |             instance = new Singleton(context);
116 |         }
117 |         return instance;
118 |     }
119 | }
120 | 
121 | 
122 |

这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

123 |

View持有Activity引用

124 |
public class MainActivity extends Activity {
125 |     private static Drawable mDrawable;
126 | 
127 |     @Override
128 |     protected void onCreate(Bundle saveInstanceState) {
129 |         super.onCreate(saveInstanceState);
130 |         setContentView(R.layout.activity_main);
131 |         ImageView iv = new ImageView(this);
132 |         mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
133 |         iv.setImageDrawable(mDrawable);
134 |     }
135 | }
136 | 
137 | 
138 |

有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

139 |

正确使用Context

140 |

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
141 | 1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
142 | 2:不要让生命周期长于Activity的对象持有到Activity的引用。
143 | 3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

144 |
145 |

Written with StackEdit.

146 |
147 | 148 | -------------------------------------------------------------------------------- /Android Stduio打开报错.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Android Stduio不能正常打开,报错

7 |

报错信息

8 |
 Internal error. Please report to http://code.google.com/p/android/issues
 9 | 
10 | java.lang.RuntimeException: com.intellij.ide.plugins.PluginManager$StartupAbortedException: Fatal error initializing 'com.intellij.util.indexing.FileBasedIndex'
11 |     at com.intellij.idea.IdeaApplication.run(IdeaApplication.java:159)
12 |     at com.intellij.idea.MainImpl$1$1$1.run(MainImpl.java:46)
13 |     at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
14 |     at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:744)
15 |     at java.awt.EventQueue.access$400(EventQueue.java:97)
16 |     at java.awt.EventQueue$3.run(EventQueue.java:697)
17 |     at java.awt.EventQueue$3.run(EventQueue.java:691)
18 |     at java.security.AccessController.doPrivileged(Native Method)
19 |     at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
20 |     at java.awt.EventQueue.dispatchEvent(EventQueue.java:714)
21 |     at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:697)
22 |     at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:524)
23 |     at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:335)
24 |     at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
25 |     at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
26 |     at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
27 |     at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
28 |     at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
29 |     at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
30 | Caused by: com.intellij.ide.plugins.PluginManager$StartupAbortedException: Fatal error initializing 'com.intellij.util.indexing.FileBasedIndex'
31 |     at com.intellij.ide.plugins.PluginManager.handleComponentError(PluginManager.java:244)
32 |     at com.intellij.openapi.components.impl.PlatformComponentManagerImpl.handleInitComponentError(PlatformComponentManagerImpl.java:39)
33 |     at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentConfigComponentAdapter$1.getComponentInstance(ComponentManagerImpl.java:570)
34 |     at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentConfigComponentAdapter.getComponentInstance(ComponentManagerImpl.java:590)
35 |     at com.intellij.util.pico.DefaultPicoContainer.getLocalInstance(DefaultPicoContainer.java:225)
36 |     at com.intellij.util.pico.DefaultPicoContainer.getInstance(DefaultPicoContainer.java:212)
37 |     at com.intellij.util.pico.DefaultPicoContainer.getComponentInstance(DefaultPicoContainer.java:199)
38 |     at org.picocontainer.alternatives.AbstractDelegatingMutablePicoContainer.getComponentInstance(AbstractDelegatingMutablePicoContainer.java:75)
39 |     at com.intellij.openapi.components.impl.ComponentManagerImpl.createComponent(ComponentManagerImpl.java:121)
40 |     at com.intellij.openapi.application.impl.ApplicationImpl.createComponent(ApplicationImpl.java:371)
41 |     at com.intellij.openapi.components.impl.ComponentManagerImpl.createComponents(ComponentManagerImpl.java:112)
42 |     at com.intellij.openapi.components.impl.ComponentManagerImpl.init(ComponentManagerImpl.java:89)
43 |     at com.intellij.openapi.components.impl.stores.ApplicationStoreImpl.load(ApplicationStoreImpl.java:87)
44 |     at com.intellij.openapi.application.impl.ApplicationImpl.load(ApplicationImpl.java:508)
45 |     at com.intellij.idea.IdeaApplication.run(IdeaApplication.java:151)
46 |     ... 18 more
47 | Caused by: java.lang.RuntimeException: java.io.FileNotFoundException: C:\Users\UserName\.AndroidStudio\system\index\todoindex\TodoIndex.ver (The system cannot find the path specified)
48 |     at com.intellij.util.indexing.FileBasedIndexImpl.initExtensions(FileBasedIndexImpl.java:332)
49 |     at com.intellij.util.indexing.FileBasedIndexImpl.initComponent(FileBasedIndexImpl.java:359)
50 |     at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentConfigComponentAdapter$1.getComponentInstance(ComponentManagerImpl.java:548)
51 |     ... 30 more
52 | Caused by: java.io.FileNotFoundException: C:\Users\UserName\.AndroidStudio\system\index\todoindex\TodoIndex.ver (The system cannot find the path specified)
53 |     at java.io.FileOutputStream.open(Native Method)
54 |     at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
55 |     at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
56 |     at com.intellij.util.indexing.IndexInfrastructure$1.execute(IndexInfrastructure.java:95)
57 |     at com.intellij.util.indexing.IndexInfrastructure$1.execute(IndexInfrastructure.java:90)
58 |     at com.intellij.openapi.util.io.FileUtilRt.doIOOperation(FileUtilRt.java:517)
59 |     at com.intellij.util.indexing.IndexInfrastructure.rewriteVersion(IndexInfrastructure.java:90)
60 |     at com.intellij.util.indexing.FileBasedIndexImpl.registerIndexer(FileBasedIndexImpl.java:390)
61 |     at com.intellij.util.indexing.FileBasedIndexImpl.initExtensions(FileBasedIndexImpl.java:290)
62 |     ... 32 more
63 | 
64 |

这种错误情况,还是比较罕见的,我也是在一次运行app崩溃后,导致AS自动关闭,再次打开就出现这个错误。

65 |

解决方案:
66 | 这个bug在Androids open source bug tracker上,已经有解决方案,https://code.google.com/p/android/issues/detail?id=74458 在这里有详细的介绍。下面贴出被采取最多的意见:

67 |
This Works without the loss any settings or project. It will take you to your previous state at the time of editing an open file.  
68 | 
69 | 1. go to your home directory. ie /home/XXXXXX/.AndroidStudio.X.X  
70 | 2. rename the .AndroidStudio.X.X to any thing else i.e. back_up  
71 | 3. run your android studio.  
72 | 4. it will prompt you to import current setting or create a new version  
73 | 5. choose the import setting and sellect the back_up directory.  
74 | 6. Bravo You are good to go.
75 | 
76 |

如果这种方法不能解决问题,可以参考这里 StackOverFlow

77 | 78 | -------------------------------------------------------------------------------- /Android View的事件分发机制和滑动冲突解决.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Android View的事件分发机制和滑动冲突解决

7 |

文章转载自https://blog.csdn.net/qian520ao/article/details/77429593

8 |

初探View事件

9 |

前言View的事件分发和滑动冲突处理是老生常谈的知识了,因为最近撸了一个仿QQ侧滑删除,所以对该View事件有了更深入的总结。老铁们是时候走一波star了。
10 | 我们常说的View事件是指: 从手指亲密接触屏幕的那一刻到手指离开屏幕的这个过程,该事件序列以down事件为起点,move事件为过程,up事件为终点。
11 | 一次down-move-up这一个事件过程我们称为一个事件序列。所以我们今天研究的对象就是MotionEvent。

12 |

事件分发

13 |

理论知识

14 | 37 |

其实上面的关系可以用以下代码简单描述。

38 |
public boolean dispatchTouchEvent(MotionEvent ev){
 39 |     boolean consume = false;//是否消费事件
 40 |     if(onInterceptTouchEvent(ev)){//是否拦截事件
 41 |         consume = onTouchEvent(ev);//拦截了,交给自己的View处理
 42 |     }else{
 43 |         consume = child.dispatchTouchEvent(ev);//不拦截,就交给子View处理
 44 |     }
 45 | 
 46 |     return consume;//true:消费事件,终止。false:交给父onTouchEvent处理。并不再往下传递当前事件。
 47 | }
 48 | 
49 |

有图有真相

50 |

android view-dispatch.jpg

51 |

实战讲解

52 |

验证View的事件分发

53 | 58 |
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 59 |     xmlns:tools="http://schemas.android.com/tools"
 60 |     android:layout_width="match_parent"
 61 |     android:layout_height="match_parent"
 62 |     tools:context="qdx.viewtouchevent.MainActivity">
 63 | 
 64 | //最外层为activity(白色背景)
 65 |     <qdx.viewtouchevent.CustomViewGroup
 66 |         android:layout_width="300dp"
 67 |         android:layout_height="400dp"
 68 |         android:layout_gravity="right"
 69 |         android:background="#84bf96">
 70 | //CustomViewGroup(绿色背景)包含CustomView(黄色背景)
 71 |         <qdx.viewtouchevent.CustomView
 72 |             android:layout_width="150dp"
 73 |             android:layout_height="300dp"
 74 |             android:layout_gravity="right"
 75 |             android:background="#f2eada" />
 76 | 
 77 |     </qdx.viewtouchevent.CustomViewGroup>
 78 | 
 79 | </FrameLayout>
 80 | 
 81 | 
82 |

demo1.jpeg

83 |

如上图所示,down事件由activity->ViewGroup->View,因为View并没有处理down事件,所以事件消费情况为false,并且最后由View->ViewGroup->activity传递。

84 |

验证不消耗ACTION_DOWN事件

85 |

我们再来验证①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件系列都与该View无关,交由父类处理(父类的onTouchEvent方法)
86 | 根据上面文字描述,因为我们的CustomViewGroup和CustomView都没有去处理任何事件,即当前序列的所有事件都return false,所以我们也无法接收/处理其他事件(move,up)

87 |

我们再将customView设置为可点击状态,即消费touch事件。setClickable(true);

88 |

验证 ViewGroup事件拦截

89 |

viewGroup将事件拦截后,②而且这一个事件序列(当前和其它事件)都只能由该ViewGroup处理,并且不会再调用该onInterceptTouchEvent方法去询问是否拦截。

90 |

通过上面的几个验证,我们越来越接近真相,用通俗的话来解释就是:

91 |
92 |

老板发现BUG解决,一开始是由上级往下级问话。(类似责任链设计模式)
93 | 例如突然间出现了BUG,老板问小组A有没有空处理一下BUG(即分发ACTION_DOWN),小组A说没时间(return false),那么老板就不会把这个序列的BUG(ACTION_MOVE和ACTION_UP)交给小组A。如果再次出现BUG,老板还会再次询问小组A。①
94 | 如果你举手揽了这个BUG(即拦截),那么这一事件的BUG都交由你解决,并且相同序列的BUG老板不会问话,直接找你处理。②

95 |
96 |

Activity的事件分发

97 |

Activity的事件分发还关系到View的绘制和加载机制,等待下一篇来更详细认识这个知识点。

98 |
public boolean dispatchTouchEvent(MotionEvent ev) {
 99 |     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
100 |         onUserInteraction();
101 |     }
102 | 
103 |     //最终获取到顶级View(ViewGroup)分发事件
104 |     //(getWindow().getDecorView().findViewById(android.R.id.Content)).getChildAt(0)
105 |     if (getWindow().superDispatchTouchEvent(ev)) {
106 |         return true;
107 |     }
108 | 
109 |     //如果所有的View都没有处理事件,则由Activity亲自出马
110 |     return onTouchEvent(ev);
111 | }
112 | 
113 |

ViewGroup的事件拦截

114 |
        public boolean dispatchTouchEvent(MotionEvent ev) {
115 |         ......
116 | 
117 |         final int action = ev.getAction();
118 |         final int actionMasked = action & MotionEvent.ACTION_MASK;
119 | 
120 |         if (actionMasked == MotionEvent.ACTION_DOWN) {
121 |             cancelAndClearTouchTargets(ev);
122 | 
123 |             //清除FLAG_DISALLOW_INTERCEPT,并且设置mFirstTouchTarget为null
124 |             resetTouchState(){
125 |                 if(mFirstTouchTarget!=null){mFirstTouchTarget==null;}
126 |                 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
127 |                 ......
128 |             };
129 |         }
130 |         final boolean intercepted;//ViewGroup是否拦截事件
131 | 
132 |         //mFirstTouchTarget是ViewGroup中处理事件(return true)的子View
133 |         //如果没有子View处理则mFirstTouchTarget=null,ViewGroup自己处理
134 |         if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
135 |             final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
136 |             if (!disallowIntercept) {
137 |                 intercepted = onInterceptTouchEvent(ev);//onInterceptTouchEvent
138 |                 ev.setAction(action);
139 |             } else {
140 |                 intercepted = false;
141 | 
142 |                 //如果子类设置requestDisallowInterceptTouchEvent(true)
143 |                 //ViewGroup将无法拦截MotionEvent.ACTION_DOWN以外的事件
144 |             }
145 |         } else {
146 |             intercepted = true;
147 | 
148 |             //actionMasked != MotionEvent.ACTION_DOWN并且没有子View处理事件,则将事件拦截
149 |             //并且不会再调用onInterceptTouchEvent询问是否拦截
150 |         }
151 | 
152 |         ......
153 |         ......
154 |       }
155 | 
156 |

我们将上面的结论再次写下来,方便对照。
157 | ①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件都与该View无关,交由父类处理(父类的onTouchEvent方法)(dispatchTouchEvent)
158 | ②而且这一个事件序列(当前和其它事件)都只能由该ViewGroup处理,并且不会再调用该onInterceptTouchEvent方法去询问是否拦截。(onInterceptTouchEvent return true)

159 | 164 |

ViewGroup的事件分发

165 |
    public boolean dispatchTouchEvent(MotionEvent ev) {
166 |     final View[] children = mChildren;
167 | 
168 |     for (int i = childrenCount - 1; i >= 0; i--) {
169 |         final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
170 |         final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
171 | 
172 |         ......
173 | 
174 |         if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) 
175 |         {
176 |             ev.setTargetAccessibilityFocus(false);
177 |             //如果子View没有播放动画,而且点击事件的坐标在子View的区域内,继续下面的判断
178 |             continue;
179 |         }
180 |         //判断是否有子View处理了事件
181 |         newTouchTarget = getTouchTarget(child);
182 | 
183 |         if (newTouchTarget != null) {
184 |             //如果已经有子View处理了事件,即mFirstTouchTarget!=null,终止循环。
185 |             newTouchTarget.pointerIdBits |= idBitsToAssign;
186 |             break;
187 |         }
188 | 
189 |         if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
190 |             //点击dispatchTransformedTouchEvent代码发现其执行方法实际为
191 |             //return child.dispatchTouchEvent(event); (因为child!=null)
192 |             //所以如果有子View处理了事件,我们就进行下一步:赋值
193 | 
194 |             ......
195 | 
196 |             newTouchTarget = addTouchTarget(child, idBitsToAssign);
197 |             //addTouchTarget方法里完成了对mFirstTouchTarget的赋值
198 |             alreadyDispatchedToNewTouchTarget = true;
199 | 
200 |             break;
201 |         }
202 |     }
203 | }
204 | 
205 | private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
206 |     final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
207 |     target.next = mFirstTouchTarget;
208 |     mFirstTouchTarget = target;
209 |     return target;
210 | }
211 | 
212 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
213 |         View child, int desiredPointerIdBits) {
214 |         ......
215 | 
216 |         if (child == null) {
217 |         //如果没有子View处理事件,就自己处理
218 |             handled = super.dispatchTouchEvent(event);
219 |         } else {
220 |        //有子View,调用子View的dispatchTouchEvent方法
221 |             handled = child.dispatchTouchEvent(event);
222 | 
223 |         ......
224 | 
225 |         return handled;
226 | }
227 | 
228 |

上面为ViewGroup对事件的分发,主要有2点

229 |

如果有子View,则调用子View的dispatchTouchEvent方法判断是否处理了事件,如果处理了便赋值mFirstTouchTarget,赋值成功则跳出循环。
230 | ViewGroup的事件分发最终还是调用View的dispatchTouchEvent方法,具体如上代码所述。
231 | 至此View的事件分发机制已经演练完毕,如果事件分发机制理解深入的话,那么处理滑动冲突便是手到擒来了。

232 |

View的滑动冲突

233 |

关于View的滑动冲突我们就开门见山吧,因为上述的事件分发已经有足够的理论知识了,我们可以单刀赴会了。

234 |

针对上图,这个是比较普遍的滑动冲突事件,我们先拿它来开刀。

235 |

好记性不如烂笔头,我们再次把结论搬到战场上
236 | ①另外如果不消耗ACTION_DOWN事件,那么down,move,up事件都与该View无关,交由父类处理(父类的onTouchEvent方法)(dispatchTouchEvent)
237 | ②而且这一个事件序列(当前和其它事件)都只能由该ViewGroup处理,并且不会再调用该onInterceptTouchEvent方法去询问是否拦截。(onInterceptTouchEvent return true)

238 |

I : 当ACTION_MOVE和ACTION_UP事件到来时,如果没有子元素处理事件(mFirstTouchTarget==null),则ViewGroup的onInterceptTouchEvent不会再被调用,而且同一序列中的其它事件都会默认交给它处理(第34行 intercepted=true);

239 |

外部拦截

240 |

外部拦截顾名思义就是由父ViewGroup对事件拦截处理(所以重写onInterceptTouchEvent方法即可),子View只能眼巴巴的处理父View“吃剩”的事件。主要有以下几点。

241 |

父类不能拦截ACTION_DOWN,也就是说必须返回false,根据上述①②和 I 可得。
242 | 父类在ACTION_MOVE的时候根据需求,判断是否拦截。
243 | ACTION_UP事件建议返回false或者super.onInterceptTouchEvent,因为如果已经拦截的话,那么并不会调用onInterceptTouchEvent方法再次询问。如果不拦截,而且返回true,子View可能就无法触发onClick等相关事件。
244 | ViewGroup : 需要重写onInterceptTouchEvent,判断是否拦截即可。
245 | 但是有一种情况:用户正在水平滑动(事件已拦截给ViewGroup),但是水平滑动停止前用户再进行竖直滑动,下面代码我用isSolve进行简单的处理。

246 |
private boolean isIntercept;
247 | private boolean isSolve;//是否完成了拦截判断,如果决定拦截,那么同系列事件就不能设置为不拦截
248 | 
249 | @Override
250 | public boolean onInterceptTouchEvent(MotionEvent ev) {
251 |     switch (ev.getAction()) {
252 |         case MotionEvent.ACTION_DOWN:
253 |             mPointGapF.x = ev.getX();
254 |             mPointGapF.y = ev.getY();
255 |             return false;//down的时候拦截后,就只能交给自己处理了
256 | 
257 |         case MotionEvent.ACTION_MOVE:
258 |             if (!isSolve) {//是否已经决定拦截/不拦截?
259 |                 isIntercept = (Math.abs(ev.getX() - mPointGapF.x) > Math.abs(ev.getY() - mPointGapF.y)*2);//如果是左右滑动,且水平角度小于30°,就拦截
260 |                 isSolve = true;
261 |             }
262 |             return isIntercept;//如果是左右滑动,就拦截
263 |     }
264 |     return super.onInterceptTouchEvent(ev);
265 | }
266 | 
267 | @Override
268 | public boolean onTouchEvent(MotionEvent ev) {
269 |     switch (ev.getAction()) {
270 |         case MotionEvent.ACTION_MOVE:
271 |             scrollBy((int) (mPointGapF.x - ev.getX()), 0);
272 | 
273 |             mPointGapF.x = ev.getX();
274 |             mPointGapF.y = ev.getY();
275 |             break;
276 |     }
277 |     return super.onTouchEvent(ev);
278 | }
279 | 
280 |

子View : 和子View没有多大关系,只需要处理自身的移动操作即可。

281 |
    public boolean onTouchEvent(MotionEvent ev) {
282 |         switch (ev.getAction()) {
283 |             case MotionEvent.ACTION_DOWN:
284 |                 mPointGapF.x = ev.getX();
285 |                 mPointGapF.y = ev.getY();
286 |                 break;
287 |             case MotionEvent.ACTION_MOVE:
288 |                 scrollBy(0, (int) (mPointGapF.y - ev.getY()));
289 |                 mPointGapF.x = ev.getX();
290 |                 mPointGapF.y = ev.getY();
291 |                 break;
292 |         }
293 |         return true;
294 |     }
295 | 
296 |

内部拦截

297 |

II : 当子View处理了ACTION_DOWN事件(mFirstTouchTarget =该子View),而且设置了FLAG_DISALLOW_INTERCEPT标记位,那么ViewGroup将无法拦截除了ACTION_DOWN以外的其它事件。

298 |

ViewGroup : 只需在onInterceptTouchEventMotionEvent.ACTION_DOWN时候不拦截,其他时候都需要拦截,否则父类的onTouchEvent就不能处理任何事件了。

299 |
public boolean onInterceptTouchEvent(MotionEvent ev) {
300 |     switch (ev.getAction()) {
301 |         case MotionEvent.ACTION_DOWN:
302 |             return false;//down的时候拦截后,就只能交给自己处理了
303 |     }
304 |     return true;//如果不拦截,父类的onTouchEvent方法就无事件可以处理。
305 | }
306 | 
307 | @Override
308 | public boolean onTouchEvent(MotionEvent ev) {
309 |     switch (ev.getAction()) {
310 |         case MotionEvent.ACTION_MOVE:
311 |             scrollBy((int) (mPointGapF.x - ev.getX()), 0);
312 | 
313 |             mPointGapF.x = ev.getX();
314 |             mPointGapF.y = ev.getY();
315 |             break;
316 |     }
317 |     return super.onTouchEvent(ev);
318 | }
319 | 
320 |

子View : 需要在ACTION_DOWN事件设置getParent().requestDisallowInterceptTouchEvent(true),并且在ACTION_MOVE的时候通过判断是否禁止父类的拦截。

321 |
private boolean isSolve;
322 | private boolean isIntercept;
323 | 
324 | @Override
325 | public boolean dispatchTouchEvent(MotionEvent ev) {
326 |     switch (ev.getAction()) {
327 |         case MotionEvent.ACTION_DOWN:
328 |             isIntercept = false;
329 |             isSolve = false;
330 |             mPointGapF.x = ev.getX();
331 |             mPointGapF.y = ev.getY();
332 |             getParent().requestDisallowInterceptTouchEvent(true);
333 |             break;
334 | 
335 |         case MotionEvent.ACTION_MOVE:
336 |             if (!isSolve) {
337 |                 isSolve = true;
338 |                 isIntercept = (Math.abs(ev.getX() - mPointGapF.x) < Math.abs(ev.getY() - mPointGapF.y) * 2);
339 |                 getParent().requestDisallowInterceptTouchEvent(isIntercept);
340 |             }
341 |             break;
342 |     }
343 |     return super.dispatchTouchEvent(ev);
344 | }
345 | 
346 |

最终的效果图和外部拦截的效果一致,这里就不再次贴出来了。

347 |
348 |

Written with StackEdit.

349 |
350 | 351 | -------------------------------------------------------------------------------- /Android stduio instant run install app error.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Android Stduio Instant Run install app 报错

7 |

Android stduio通过instant app 方式,安装app到手机,发生错误,报错信息如下:

8 |
* What went wrong:
 9 | Execution failed for task ':app:transformDexWithInstantRunSlicesApkForChexiaoduoDebug'.
10 | > java.lang.RuntimeException: java.io.FileNotFoundException: /Users/loganzy/AndroidStudioProjects/cdd-last-app/app/build/intermediates/instant_run_split_apk_resources/chexiaoduoDebug/instantRunSplitApkResourcesChexiaoduoDebug/out/slice_3/resources_ap
11 | 
12 |

在build中点击Run with --stacktrace,gradle向下执行,不会报错,执行通过,拿不到错误信息。

13 |
14 |

Bug 出现的条件:安装方式 instant run 勾选中 ,Gradle的版本是5.1.1

15 |
16 |

解决方案:

17 |
18 |

方式一:

19 |
20 | 26 |
27 |

方式二:

28 |
29 | 32 |
33 |

Written with StackEdit.

34 |
35 | 36 | -------------------------------------------------------------------------------- /Android view滑动冲突的处理.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

滑动冲突的常见场景与处理思路

7 |

文章转载自https://www.jianshu.com/p/982a83271327

8 |

当我们内外两层View都可以滑动时候,就会产生滑动冲突!

9 |
10 |

常见的滑动冲突场景:

11 |

12 |

滑动冲突.png

13 |
14 |
15 | 18 |
19 |
20 | 23 |
24 |
25 |

当然还有上面两种组合起来,三层或者多层嵌套产生的冲突,然而不管是多么复杂,解决的思路都是一模一样。所以遇到多层嵌套的小伙伴也不用惊慌,一层一层处理即可。

26 |
27 |

有小伙伴肯定有疑问,ViewPager带ListView并没有出现滑动冲突啊。
28 | 那是因为ViewPager已经为我们处理了滑动冲突!如果我们自己定义一个水平滑动的ViewGroup内部再使用ListView,那么是一定需要处理滑动冲突的。

29 |

30 |

针对上面第一种场景,由于外部与内部的滑动方向不一致,那么**我们可以根据当前滑动方向,水平还是垂直来判断这个事件到底该交给谁来处理。**至于如何获得滑动方向,我们可以得到滑动过程中的两个点的坐标。一般情况下根据水平和竖直方向滑动的距离差就可以判断方向,当然也可以根据滑动路径形成的夹角(或者说是斜率如下图)、水平和竖直方向滑动速度差来判断。

31 |

32 |

ViewPager当斜率小于0.5时判断为横向滑动,拦截事件

33 |

有兴趣的小伙伴可以看ViewPager源码分析(2):滑动及冲突处理

34 |

针对第二种场景,由于外部与内部的滑动方向一致,那么不能根据滑动角度、距离差或者速度差来判断。**这种情况下必需通过业务逻辑来进行判断。**比较常见ScrollView嵌套了ListView。虽然需求不同,业务逻辑自然也不同,但是解决滑动冲突的方式都是一样的。下面为大家截取了微博和天猫当中的同方向滑动冲突场景,方便大家更直观的感受这个场景。

35 |

36 |

同方向,竖向滑动冲突

37 |

微博的这个是同方向,竖向滑动冲突的场景,可以看到发现布局整体是可以滚动的,而且下方的微博列表也是可以滚动的。根据业务逻辑,当热门,榜单…这一行标签栏滑动到顶部的时候微博列表才可以滚动。否则就是发现布局的整体滚动。这个场景是不是在很多app里面都能够见到呢!

38 |

39 |

同方向,横向滑动冲突

40 |

天猫的这个是同方向,横向滑动冲突的场景,内外两层都是可以横向滚动的。它的处理逻辑也很明显,根据用户滑动的位置来判断到底是那个View需要响应滑动。

41 |

上述两种滑动冲突的场景区别只是在于拦截的逻辑处理上。第一种是根据水平还是竖直滑动来判断谁来处理滑动,第二种是根据业务逻辑来判断谁来处理滑动,但是处理的套路都是一样的

42 |

43 |

滑动冲突解决套路

44 |
45 |

套路一 外部拦截法:

46 |

即父View根据需要对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。

47 |
    public boolean onInterceptTouchEvent(MotionEvent event) {
 48 |         boolean intercepted = false;
 49 |         int x = (int) event.getX();
 50 |         int y = (int) event.getY();
 51 |         switch (event.getAction()) {
 52 |             case MotionEvent.ACTION_DOWN: {
 53 |                 intercepted = false;
 54 |                 break;
 55 |             }
 56 |             case MotionEvent.ACTION_MOVE: {
 57 |                 if (满足父容器的拦截要求) {
 58 |                     intercepted = true;
 59 |                 } else {
 60 |                     intercepted = false;
 61 |                 }
 62 |                 break;
 63 |             }
 64 |             case MotionEvent.ACTION_UP: {
 65 |                 intercepted = false;
 66 |                 break;
 67 |             }
 68 |             default:
 69 |                 break;
 70 |         }
 71 |         mLastXIntercept = x;
 72 |         mLastYIntercept = y;
 73 |         return intercepted;
 74 |     }
 75 | 
 76 | 
77 |

上面伪代码表示外部拦截法的处理思路,需要注意下面几点

78 |
79 | 82 |
83 | 87 |

套路二 内部拦截法:

88 |

即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定是自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。下面是子View的dispatchTouchEvent方法的伪代码:

89 |
    public boolean dispatchTouchEvent(MotionEvent event) {
 90 |         int x = (int) event.getX();
 91 |         int y = (int) event.getY();
 92 | 
 93 |         switch (event.getAction()) {
 94 |             case MotionEvent.ACTION_DOWN: {
 95 |                 parent.requestDisallowInterceptTouchEvent(true);
 96 |                 break;
 97 |             }
 98 |             case MotionEvent.ACTION_MOVE: {
 99 |                 int deltaX = x - mLastX;
100 |                 int deltaY = y - mLastY;
101 |                 if (父容器需要此类点击事件) {
102 |                     parent.requestDisallowInterceptTouchEvent(false);
103 |                 }
104 |                 break;
105 |             }
106 |             case MotionEvent.ACTION_UP: {
107 |                 break;
108 |             }
109 |             default:
110 |                 break;
111 |         }
112 | 
113 |         mLastX = x;
114 |         mLastY = y;
115 |         return super.dispatchTouchEvent(event);
116 |     }
117 | 
118 | 
119 |

父View需要重写onInterceptTouchEvent方法:

120 |
    public boolean onInterceptTouchEvent(MotionEvent event) {
121 | 
122 |         int action = event.getAction();
123 |         if (action == MotionEvent.ACTION_DOWN) {
124 |             return false;
125 |         } else {
126 |             return true;
127 |         }
128 |     }
129 | 
130 | 
131 |
132 |

使用内部拦截法需要注意:

133 | 137 |
138 |

滑动冲突解决示例代码

139 |
140 |

理论最终的落脚是在实践,下面我通过一个例子来演示外部解决法和内部解决法解决滑动冲突,大家只要get到了精髓,那么今后遇到滑动冲突问题都将迎刃而解,不再是开发拦路虎!

141 |

我们一开始说过ViewPager已经默认给我们处理了滑动冲突,而它作为ViewGroup使用的是外部拦截法解决的冲突,即在onInterceptTouchEvent方法中进行判断的。为了造成滑动冲突场景,那么我们自定义一个ViewPager,重写onInterceptTouchEvent方法并默认返回false,如下所示:

142 |
public class BadViewPager extends ViewPager {
143 |     public BadViewPager(Context context, AttributeSet attrs) {
144 |         super(context, attrs);
145 |     }
146 | 
147 |     @Override
148 |     public boolean onInterceptTouchEvent(MotionEvent ev) {
149 |         return false;
150 |     }
151 | }
152 | 
153 | 
154 |

这样,一个好好的ViewPager就被我们玩坏了!

155 |

156 |

接下来新建一个ScrollConflicActivity用来测试滑动冲突。

157 |
public class ScrollConflictActivity extends BaseActivity {
158 |     private BadViewPager mViewPager;
159 |     private List<View> mViews;
160 | 
161 |     @Override
162 |     protected void onCreate(Bundle savedInstanceState) {
163 |         super.onCreate(savedInstanceState);
164 |         setContentView(R.layout.activity_scroll_conflict);
165 |         initViews();
166 |         initData(false);
167 |     }
168 | 
169 |     protected void initViews() {
170 |         mViewPager = findAviewById(R.id.viewpager);
171 |         mViews = new ArrayList<>();
172 |     }
173 | 
174 |     protected void initData(final boolean isListView) {
175 |         //初始化mViews列表
176 |         Flowable.just("view1", "view2", "view3", "view4").subscribe(new Consumer<String>() {
177 |             @Override
178 |             public void accept(String s) throws Exception {
179 |                 //当前View
180 |                 View view;
181 |                 if (isListView) {
182 |                     //初始化ListView
183 |                     ListView listView = new ListView(mContext);
184 |                     final ArrayList<String> datas = new ArrayList<>();
185 |                     //初始化数据,datas ,data0 ...data49
186 |                     Flowable.range(0, 50).subscribe(new Consumer<Integer>() {
187 |                         @Override
188 |                         public void accept(Integer integer) throws Exception {
189 |                             datas.add("data" + integer);
190 |                         }
191 |                     });
192 |                     //初始化adapter
193 |                     ArrayAdapter<String> adapter = new ArrayAdapter<>
194 |                             (mContext, android.R.layout.simple_list_item_1, datas);
195 |                     //设置adapter
196 |                     listView.setAdapter(adapter);
197 |                     //将ListView赋值给当前View
198 |                     view = listView;
199 |                 } else {
200 |                     //初始化TextView
201 |                     TextView textView = new TextView(mContext);
202 |                     textView.setGravity(Gravity.CENTER);
203 |                     textView.setText(s);
204 |                     //将TextView赋值给当前View
205 |                     view = textView;
206 |                 }
207 |                 //将当前View添加到ViewPager的ViewList中去
208 |                 mViews.add(view);
209 |             }
210 |         });
211 |         //设置ViewPager的Adapter
212 |         mViewPager.setAdapter(new BasePagerAdapter<>(mViews));
213 |     }
214 | }
215 | 
216 | 
217 |

注:Flowable是RxJava2的方法,这里其实用for循环也是一样的
218 | BasePagerAdapter是BaseProject里的方法

219 |

上面的代码我们使用了BadViewPager,初始化了BadViewPager里面的子View。
220 | initData(false);方法传false表示里面的子View是一个TextView,传true表示里面的子View是ListView。首先我们看BadViewPager里面子View是TextView是否可以滑动。

221 |

222 |

BadViewPager_bad_textview.gif

223 |

似乎对BadViewPager的滑动没有任何影响。

224 |

225 |

大家别急,我们来分析一下,BadViewPager的onInterceptTouchEvent默认返回false则所有事件都会给子View去处理。大家是否还记得上一篇说到的View处理事件的原则?

226 |

View 的onTouchEvent 方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false。

227 |

所以TextView默认并没有消费事件,因为他是不可点击的。事件会交由父View即BadViewPager的onTouchEvent方法去处理。所以它自然是可以滑动的。

228 |

我们将textview的Clickable设置成true,即让它来消费事件。大家再看看呢

229 |

230 |

BadViewPager_bad_textview_clickable.gif

231 |

所以我们不难推测如果将TextView换成Button,将是一样的无法滑动的效果。虽然这并不是常规的滑动冲突(子View不是滑动的),但是造成的原因其实是一样的,没有做滑动判断导致父View不能正确响应滑动事件。

232 |

接下来稍稍修改一下代码 initData(true);传入true,即BadViewPager的子View使用ListView,显然ListView是可以滑动的,BadViewPager是不能滑动的。我们分别通过外部拦截和内部拦截方法来对BadViewPager进行修复。

233 |

234 |

BadViewPager_bad_listview.gif

235 |

1.外部拦截法Fix BadViewPager:

236 |
public class BadViewPager extends ViewPager {
237 |     private static final String TAG = "BadViewPager";
238 | 
239 |     private int mLastXIntercept;
240 |     private int mLastYIntercept;
241 | 
242 |     public BadViewPager(Context context, AttributeSet attrs) {
243 |         super(context, attrs);
244 |     }
245 | 
246 |     @Override
247 |     public boolean onInterceptTouchEvent(MotionEvent ev) {
248 |         boolean intercepted = false;
249 |         int x = (int) ev.getX();
250 |         int y = (int) ev.getY();
251 |         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
252 |         switch (action) {
253 |             case MotionEvent.ACTION_DOWN:
254 |                 intercepted = false;
255 |                 //调用ViewPager的onInterceptTouchEvent方法初始化mActivePointerId
256 |                 super.onInterceptTouchEvent(ev);
257 |                 break;
258 |             case MotionEvent.ACTION_MOVE:
259 |                 //横坐标位移增量
260 |                 int deltaX = x - mLastXIntercept;
261 |                 //纵坐标位移增量
262 |                 int deltaY = y - mLastYIntercept;
263 |                 if (Math.abs(deltaX)>Math.abs(deltaY)){
264 |                     intercepted = true;
265 |                 }else{
266 |                     intercepted = false;
267 |                 }
268 |                 break;
269 |             case MotionEvent.ACTION_UP:
270 |                 intercepted = false;
271 |                 break;
272 |             default:
273 |                 break;
274 |         }
275 | 
276 |         mLastXIntercept = x;
277 |         mLastYIntercept = y;
278 | 
279 |         LogUtil.e(TAG,"intercepted = "+intercepted);
280 |         return intercepted;
281 |     }
282 | }
283 | 
284 | 
285 |

根据我们的外部拦截法的套路,需要重写BadViewPager 的onInterceptTouchEvent方法,并且ACTION_DOWN 和 ACTION_UP返回为false。处理逻辑在ACTION_MOVE中,Math.abs(deltaX)>Math.abs(deltaY)表示横向位移增量大于竖向位移增量,即水平滑动,则BadViewPager 拦截事件。

286 |

这里我们在ACTION_DOWN 当中还调用了super.onInterceptTouchEvent(ev);即ViewPager的onInterceptTouchEvent方法。主要是为了初始化ViewPager的成员变量mActivePointerId。mActivePointerId默认值为-1,在ViewPager的onTouchEvent方法的ACTION_MOVE中有这么一段:

287 |
class ViewPager
288 |     @Override
289 |     public boolean onTouchEvent(MotionEvent ev) {
290 |         ...
291 |         switch (action & MotionEventCompat.ACTION_MASK) {
292 |             case MotionEvent.ACTION_MOVE:
293 |                 if (!mIsBeingDragged) {
294 |                     final int pointerIndex = ev.findPointerIndex(mActivePointerId);
295 |                     if (pointerIndex == -1) {
296 |                         // A child has consumed some touch events and put us into an inconsistent
297 |                         // state.
298 |                         needsInvalidate = resetTouch();
299 |                         break;
300 |                     }
301 |                     //具体的滑动操作...
302 |                 }
303 |                 ...
304 |                 break;
305 |                 ...
306 |         }
307 |         ...
308 |     }
309 | 
310 | 
311 |

假如mActivePointerId不进行初始化,ViewPager会认为这个事件已经被子View给消费了,然后break掉,接下来的滑动操作也就不执行了。

312 |

313 |

BadViewPager_fixed_listview.gif

314 |

2.内部拦截法Fix BadViewPager:

315 |

内部拦截法需要重写ListView的dispatchTouchEvent方法,所以我们自定义一个ListView:

316 |
public class FixListView extends ListView {
317 |     private static final String TAG = "FixListView";
318 | 
319 |     private int mLastX;
320 |     private int mLastY;
321 | 
322 |     public FixListView(Context context) {
323 |         super(context);
324 |     }
325 | 
326 |     public FixListView(Context context, AttributeSet attrs) {
327 |         super(context, attrs);
328 |     }
329 | 
330 |     @Override
331 |     public boolean dispatchTouchEvent(MotionEvent ev) {
332 |         int x = (int) ev.getX();
333 |         int y = (int) ev.getY();
334 | 
335 |         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
336 |         switch (action) {
337 |             case MotionEvent.ACTION_DOWN:
338 |                 getParent().requestDisallowInterceptTouchEvent(true);
339 |                 break;
340 |             case MotionEvent.ACTION_MOVE:
341 |                 //水平移动的增量
342 |                 int deltaX = x - mLastX;
343 |                 //竖直移动的增量
344 |                 int deltaY = y - mLastY;
345 |                 //当水平增量大于竖直增量时,表示水平滑动,此时需要父View去处理事件
346 |                 if (Math.abs(deltaX) > Math.abs(deltaY)){
347 |                     getParent().requestDisallowInterceptTouchEvent(false);
348 |                 }
349 |                 break;
350 |             case MotionEvent.ACTION_UP:
351 |                 break;
352 |             default:
353 |                 break;
354 |         }
355 |         mLastX = x;
356 |         mLastY = y;
357 |         return super.dispatchTouchEvent(ev);
358 |     }
359 | }
360 | 
361 | 
362 |

再看BadViewPager,需要重写拦截方法

363 |
public class BadViewPager extends ViewPager {
364 |     private static final String TAG = "BadViewPager";
365 | 
366 |     public BadViewPager(Context context, AttributeSet attrs) {
367 |         super(context, attrs);
368 |     }
369 | 
370 |     @Override
371 |     public boolean onInterceptTouchEvent(MotionEvent ev) {
372 |         final int action = ev.getAction() & MotionEvent.ACTION_MASK;
373 |         if (action == MotionEvent.ACTION_DOWN){
374 |             super.onInterceptTouchEvent(ev);
375 |             return false;
376 |         }
377 |         return true;
378 |     }
379 | }
380 | 
381 | 
382 |

可以看到和我们的套路代码基本上一样,只是ACTION_MOVE中有我们自己的逻辑处理,处理的方式与外部拦截法也是一致的,并且效果也一样,在此不作赘述。

383 |

大家只用掌握上述滑动冲突的解决套路,不论场景是不同方向,还是同方向,还是乱七八糟的堆加在一起,就用套路去解决,万变不离其宗。根据上述的外部拦截和内部拦截法,可以看出外部拦截法实现起来更加简单,而且也符合View的正常事件分发机制,所以推荐使用外部拦截法(重写父View的onInterceptTouchEvent,父View决定是否拦截)来处理滑动冲突

384 |

-------------------第二篇文章----------------------

385 |
386 |

Written with StackEdit.

387 |
388 | 389 | -------------------------------------------------------------------------------- /Android中Gradle基础介绍.md: -------------------------------------------------------------------------------- 1 | # Android中Gradle基础介绍 2 | Gradle是一个不断迭代的工具,往往在一个新版本的使用上会打破向后兼容问题,这时候使用`Gradle Wrapper`可以避免这个问题,并且能够保证构建是可重复的。 3 | 4 | Grdle Wrapper分别为各个操作系统提供了执行工具,Windows上是一个batch文件,而Linux/Unix是一个shell脚本。当运行这个脚本的时候,所需要的Gradle版本会自动下载到本地,当然如果已经下载过了,他就会直接使用该版本的Gradle进行构建工作。Gradle Wrapper存在的意义就是它可以不借助开发者机器或者自构建系统依然可以运行Wrapper,然后由Wrapper搞定剩余的部分。因此,在开发者的机器上或者构建服务器上没有安装正确的Gradle依然可以通过Wrapper进行构建。此外,Gradle还建议把Wrapper文件添加到你的版本控制系统中。 5 | 6 | **注意:** 运行Gradle Wrapper和直接运行Gradle进行构建是没什么不同的。 7 | 8 | # Gradle Wrapper的获取 9 | 10 | 当使用Android Studio创建一个项目时,IDE会自动为你生成一个Gradle Wrapper的文件夹,如下图所示: 11 | ![](http://p981u1am0.bkt.clouddn.com/18-6-8/88361543.jpg) 12 | 13 | 图中可以看到gradle目录下的wrapper包含两个文件一个是`gradle-wrapper.jar`一个是`gradle-wrapper.properties`,当然在项目的根目录下还包括`gradlew`和`gradlew.bat`两个执行文件,总共这四个文件及其目录结构就是Gradle Wrapper的全部相关内容。 14 | 15 | 如果脱离了Android Studio来进行项目构建怎么办呢?答案当然是选用Gradle Wrapper了。但是首要问题还是如何生成类似于Android Studio那样自动生成的Wrapper文件呢?其实还是要先安装Gradle的,然后才能得到Wrapper。 16 | 17 | > Gradle下载页:[http://gradle.org/download](http://gradle.org/download) 18 | > 安装说明:[http://gradle.org/installation](http://gradle.org/installation) 19 | 20 | 下载安装完Gradle后需要将gradle添加到环境变量中方便在命令行下使用(这里有什么不明白的可以Google之)。 21 | 22 | 为创建一个Wrapper环境首先要定义一个task,task声明在`build.gradle`文件下 23 | 24 | task wrapper(type: Wrapper) { 25 | 26 | gradleVersion = '3.0.0' 27 | 28 | } 29 | 30 | 执行命令: 31 | 32 | > gradle wrapper 33 | 34 | 从而生成wrapper环境: 35 | 36 | 当然在Gradle后续的版本中已经内置了wrapper任务,所以可以不使用`build.gradle`配置文件而是通过如下命令来生成Wrapper环境 37 | 38 | > gradle wrapper --gradle-version XXX 39 | 40 | 从上面Gradle Wrapper结构图中可以发现Wrapper有三部分组成 41 | 42 | - Windows上的batch文件,Linux/Unix上的shell脚本 43 | - batch文件和shell脚本所使用到的jar包 44 | - properties文件 45 | 46 | `gradle-wrapper.properties`文件包含了参数配置以及决定Gradle的使用版本以及它的远程仓库,具体内容如下: 47 | 48 | 49 | #Fri Dec 08 13:36:36 CST 2017 50 | 51 | distributionBase=GRADLE_USER_HOME 52 | 53 | distributionPath=wrapper/dists 54 | 55 | zipStoreBase=GRADLE_USER_HOME 56 | 57 | zipStorePath=wrapper/dists 58 | 59 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 60 | 61 | 如果使用企业内部分发的Gradle版本就可以修改`distributionUrl`的值。所以在运行wrapper之前一定要检查该URL对应的Gradle版本。 62 | 63 | # 运行wrapper 64 | 65 | 在命令行下运行带有`tasks`的命令,如下: 66 | 67 | > gradle tasks 68 | 69 | 这样将会打印出所有可用的任务列表,如果添加了-all 参数,那么将会获得每个任务对应依赖的详细介绍。 70 | 71 | 为了近距离观察Android项目的task,笔者在一个中大型的Android项目下运行了`gradle tasks --all`命令,结果如下: 72 | 73 | Incremental java compilation is an incubating feature. 74 | 75 | :tasks 76 | 77 | ------------------------------------------------------------ 78 | 79 | All tasks runnable from root project 80 | 81 | ------------------------------------------------------------ 82 | 83 | Android tasks 84 | 85 | ------------- 86 | 87 | androidDependencies - Displays the Android dependencies of the project. 88 | 89 | signingReport - Displays the signing info for each variant. 90 | 91 | sourceSets - Prints out all the source sets defined in this project. 92 | 93 | Build tasks 94 | 95 | ----------- 96 | 97 | assemble - Assembles all variants of all applications and secondary packages. 98 | 99 | assembleAndroidTest - Assembles all the Test applications. 100 | 101 | assembleDebug - Assembles all Debug builds. 102 | 103 | assembleRelease - Assembles all Release builds. 104 | 105 | build - Assembles and tests this project. 106 | 107 | buildDependents - Assembles and tests this project and all projects that depend on it. 108 | 109 | buildNeeded - Assembles and tests this project and all projects it depends on. 110 | 111 | clean - Deletes the build directory. 112 | 113 | compileDebugAndroidTestSources 114 | 115 | compileDebugSources 116 | 117 | compileDebugUnitTestSources 118 | 119 | compileReleaseSources 120 | 121 | compileReleaseUnitTestSources 122 | 123 | mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests. 124 | 125 | Build Setup tasks 126 | 127 | ----------------- 128 | 129 | init - Initializes a new Gradle build. [incubating] 130 | 131 | wrapper - Generates Gradle wrapper files. [incubating] 132 | 133 | Help tasks 134 | 135 | ---------- 136 | 137 | buildEnvironment - Displays all buildscript dependencies declared in root project 'emm'. 138 | 139 | components - Displays the components produced by root project 'emm'. [incubating] 140 | 141 | dependencies - Displays all dependencies declared in root project 'emm'. 142 | 143 | dependencyInsight - Displays the insight into a specific dependency in root project 'emm'. 144 | 145 | help - Displays a help message. 146 | 147 | model - Displays the configuration model of root project 'emm'. [incubating] 148 | 149 | projects - Displays the sub-projects of root project 'emm'. 150 | 151 | properties - Displays the properties of root project 'emm'. 152 | 153 | tasks - Displays the tasks runnable from root project 'emm' (some of the displayed tasks may belong to subprojects). 154 | 155 | Install tasks 156 | 157 | ------------- 158 | 159 | installDebug - Installs the Debug build. 160 | 161 | installDebugAndroidTest - Installs the android (on device) tests for the Debug build. 162 | 163 | uninstallAll - Uninstall all applications. 164 | 165 | uninstallDebug - Uninstalls the Debug build. 166 | 167 | uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build. 168 | 169 | uninstallRelease - Uninstalls the Release build. 170 | 171 | Verification tasks 172 | 173 | ------------------ 174 | 175 | check - Runs all checks. 176 | 177 | connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices. 178 | 179 | connectedCheck - Runs all device checks on currently connected devices. 180 | 181 | connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices. 182 | 183 | deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers. 184 | 185 | deviceCheck - Runs all device checks using Device Providers and Test Servers. 186 | 187 | lint - Runs lint on all variants. 188 | 189 | lintDebug - Runs lint on the Debug build. 190 | 191 | lintRelease - Runs lint on the Release build. 192 | 193 | test - Run unit tests for all variants. 194 | 195 | testDebugUnitTest - Run unit tests for the debug build. 196 | 197 | testReleaseUnitTest - Run unit tests for the release build. 198 | 199 | Other tasks 200 | 201 | ----------- 202 | 203 | clean 204 | 205 | extractProguardFiles 206 | 207 | jarDebugClasses 208 | 209 | jarReleaseClasses 210 | 211 | transformResourcesWithMergeJavaResForDebugUnitTest 212 | 213 | transformResourcesWithMergeJavaResForReleaseUnitTest 214 | 215 | To see all tasks and more detail, run gradlew tasks --all 216 | 217 | To see more detail about a task, run gradlew help --task 218 | 219 | BUILD SUCCESSFUL 220 | 221 | Total time: 7.031 secs 222 | 223 | This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.14.1/userguide/gradle_daemon.html 224 | 225 | 可以看到任务项很多大概有四百多行,这里讲几个典型的,剩下的将会在后面的篇章中具体介绍。 226 | 227 | 在开发阶段构建项目时,可以运行带debug参数的assemble任务: 228 | 229 | > gradlew assembleDebug 230 | 231 | 该命令会编译一个debug版本的apk,默认情况下Gradle的Android插件会把apk保存在`project/app/build/outputs/apk`下。 232 | 233 | 在上述列出的任务中发现好多任务名称都很长不利于命令的书写,所以Gradle也支持tasks命令的缩写,通常使用驼峰式缩写法。例如 `assembleDebug`可以使用`assDeb`或者`aD`。笔者通常在快速构建apk时使用`gradlew iD`命令,其实它是`gradlew installDebug`的缩写,该命令也在上述tasks中列了出来,当然在使用`gradlew task --all`命令时可以看出`gradlew installDebug`是依赖于`gradle assemble`的,它先构建一个debug版的apk然后执行pm install 命令将apk安装在手机上。 234 | 235 | 除了assemble外,还有其他三个基本任务 236 | 237 | - Check:运行所有的检查,这通常意味着在一个连接的设备或模拟器上运行测试 238 | - Build:触发assemble和check两个任务 239 | - Clean:清除项目输出 240 | 241 | -------------------------------------------------------------------------------- /Android中Gradle的基础构建.md: -------------------------------------------------------------------------------- 1 | # Android中Gradle的基础构建 2 | # Gradle构建基础 3 | 4 | 在AndroidStudio创建一个安卓项目时会自动生成三个Gradle文件,其中两个build.gradle和一个settings.gradle文件。他们的后缀都是.gradle,并且如果在项目中创建一个module也会随之生成一个build.gradle文件。初始化后的这三个gradle文件结构如下所示: 5 | 6 | ![](http://p981u1am0.bkt.clouddn.com/18-6-8/98261915.jpg) 7 | 8 | - 根目录下的settings.build文件在初始化阶段被执行,并且定义了那些模块应该包含在构建内 9 | - 本例中只有一个app模块,故只有一行`include ':app'` 10 | - 若还有其他一些模块,比如mylibrary库,应该为`include ':app', ':mylibrary'` 11 | - 根目录下的build.gradle文件默认会包含两个代码块,分别如下: 12 | 13 | buildscript { 14 | 15 | repositories { 16 | 17 | jcenter() 18 | 19 | } 20 | 21 | dependencies { 22 | 23 | classpath 'com.android.tools.build:gradle:2.2.2' 24 | 25 | // NOTE: Do not place your application dependencies here; they belong 26 | 27 | // in the individual module build.gradle files 28 | 29 | } 30 | 31 | } 32 | 33 | allprojects { 34 | 35 | repositories { 36 | 37 | jcenter() 38 | 39 | } 40 | 41 | } 42 | 43 | 实际构建配置在`buildscript`代码块中,`repositories`将jcenter配置为一个仓库,和编译有关的依赖库都是从jcenter仓库中拉取;`dependencies`将配置构建过程中的gradle插件的依赖包,默认情况下只定义Android插件(有了`classpath 'com.android.tools.build:gradle:2.2.2'`这句话后随后的app中的build.gradle文件才可以使用android代码块变量以及一些有关Android的属性)。 44 | 45 | `allprojects`代码块可用来声明那些需要用于所有模块的属性,甚至可以在该代码块下创建任务,这些任务最终会被运用到所有模块中。 46 | 47 | - app模块下的build.gradle文件所定义的属性或者任务只能应用在该app模块下,它可以覆盖根下的build.gradle文件所声明属性。 48 | 默认的AndroidStudio会生成如下代码: 49 | 50 | #Android应用插件,根下的build.gradle文件要配置安卓构建工具`classpath 'com.android.tools.build:gradle:2.2.2'`的依赖才能使用 51 | apply plugin: 'com.android.application' 52 | 53 | android { 54 | 55 | compileSdkVersion 25 #用来编译应用的Android API版本 56 | 57 | buildToolsVersion "25.0.0" #构建工具和编译器使用的版本号 58 | 59 | #改代码块下的属性会覆盖AndroidManifest中声明的相关属性 60 | 61 | defaultConfig { 62 | 63 | #覆盖manifest中的packagename,但是它和package name还有不同,它应用在 64 | 65 | #variants多版本构建时至关重要(多版本要有不同的包名才能安装到同一设备上) 66 | 67 | applicationId "com.icedcap.myapplication" 68 | 69 | minSdkVersion 22 #对应manifest 70 | 71 | targetSdkVersion 25 #对应manifest 72 | 73 | versionCode 1 #和manifest的该属性意义相同 74 | 75 | versionName "1.0" #和manifest的该属性意义相同 76 | 77 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 78 | 79 | } 80 | 81 | # 该代码块用来定义如何构建和打包不同类型的应用,这将在后续详细指出 82 | buildTypes { 83 | 84 | release { 85 | 86 | minifyEnabled false 87 | 88 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 89 | 90 | } 91 | 92 | } 93 | 94 | } 95 | 96 | 97 | # app项目所要依赖的库要在该出声明 98 | 99 | dependencies { 100 | 101 | compile fileTree(dir: 'libs', include: ['*.jar']) 102 | 103 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 104 | 105 | exclude group: 'com.android.support', module: 'support-annotations'}) 106 | 107 | compile 'com.android.support:appcompat-v7:25.0.1' 108 | 109 | testCompile 'junit:junit:4.12' 110 | 111 | } 112 | 113 | # Gradle任务和简单的自定义构建 114 | 115 | 谷歌的工程师在开发Android的Gradle插件时也是利用依赖进行开发的。首先Gradle的Android插件使用了Java的基础插件,而Java基础插件又使用了Gradle基础插件,基础插件中定义了任务的标准生命周期和一些共同约定的属性。基础插件中定义了`assemble`和`clean`任务,Java插件定义了`check`和`build`任务。在基础插件中`assemble`和`clean`任务既不被实现,也不执行任何操作,它类似于编程语言中的接口或者是抽象类由依赖该任务的其他插件来具体实现,例如在[Gradle 基础介绍](https://github.com/zylaoshi/AndroidTotal/blob/master/Android%E4%B8%ADGradle%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D.md)列出的所有Gradle任务以及它的依赖,其中`app:assemble`要依赖`app:assembleDebug`和`app:assembleRelease`而`app:installDebug`要依赖`app:assembleDebug`。 116 | 117 | 对于底层插件定义的任务约定如下: 118 | 119 | - assemble:集合项目的输出 120 | - clean:清理项目的输出 121 | - check:运行所有的检查,通常是单元测试和集成测试 122 | - build:同时运行assemble和check 123 | 124 | 而对于安卓Gradle插件来说是在上述约定的基础扩展而来,具体任务约定如下: 125 | 126 | - assemble:为每一个构建版本创建一个APK文件 127 | - clean:删除所有的构建内容,例如APK文件以及编译时产生的资源R文件等 128 | - check:运行Lint检查,如果Lint发现问题则停止构建 129 | - build:同时运行assemble和check任务 130 | 131 | Android的Gradle插件除了扩展了这四个基础任务之外,还自定义了很多有用的任务,在[Gradle 基础介绍](https://github.com/zylaoshi/AndroidTotal/blob/master/Android%E4%B8%ADGradle%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D.md)中也列出了一个Android项目所有的任务以及详细描述和依赖情况。 132 | 133 | 这里列出几个平时用的比较多的任务: 134 | 135 | - connectedCheck:在连接设备或者模拟器上运行测试 136 | - deviceCheck:一个占位任务,专为其他插件在远端设备上运行测试 137 | - installDebug和installRelease:在连接设备上或模拟器上安装特定版本 138 | - uninstallDebug和uninstallRelease:卸载相应的版本 139 | 140 | 好了,说完了基本的构建任务外再来谈谈简单的自定义构建。 141 | 142 | 简单的自定义构建也是平时项目中常常用到的比较有灵气的Gradle小技巧,主要包括以下几块内容: 143 | 144 | - BuildConfig设置 145 | - 项目范围设置 146 | - 项目属性设置 147 | - 默认任务设置 148 | 149 | ## BuildConfig设置 150 | 151 | 从SDK工具升级到17以后,构建工具会在项目中生成一个叫`BuildConfig`的类,默认的使用`generateDebugSources`(点击AndroidStudio的Gradle同步按钮也会执行该任务)构建时,会生成如下信息: 152 | 153 | /** 154 | 155 | * Automatically generated file. DO NOT MODIFY 156 | 157 | */ 158 | 159 | package com.icedcap.myapplication; 160 | 161 | public final class BuildConfig { 162 | 163 | public static final boolean DEBUG = Boolean.parseBoolean("true"); 164 | 165 | public static final String APPLICATION_ID = "com.icedcap.myapplication"; 166 | 167 | public static final String BUILD_TYPE = "debug"; 168 | 169 | public static final String FLAVOR = ""; 170 | 171 | public static final int VERSION_CODE = 1; 172 | 173 | public static final String VERSION_NAME = "1.0"; 174 | 175 | } 176 | 177 | 包括`DEBUG` `APPLICATION_ID` `BUILD_TYPE` `FLAVOR` `VERSION_CODE` `VERSION_NAME`所以我们在使用Debug版本时可以引用到该类中的属性。应用在实际场景中,比如构建debug版应用时项目内访问测试服务器,当发布正式版本时要访问正式的服务器,这时候就可以定义两个版本的服务器URL从而在不同版本上使用不同的URL,同理,对于http请求的日志只想在debug版本中看到。 178 | 179 | buildTypes { 180 | 181 | debug { 182 | 183 | buildConfigField "String", "APP_URL", "\"http://debug.test.com\"" 184 | 185 | buildConfigField "boolean", "LOG_HTTP_CALLS", "true" 186 | 187 | ... 188 | 189 | } 190 | 191 | release { 192 | 193 | buildConfigField "String", "APP_URL", "\"http://release.test.com\"" 194 | 195 | buildConfigField "boolean", "LOG_HTTP_CALLS", "false" 196 | 197 | ... 198 | 199 | } 200 | 201 | } 202 | 203 | **注意:** 这里的url地址要使用转移符号将双引号连带地址一并传入BuildConfig类中。 204 | 205 | 可以看到分别使用assembleDebug和assembleRelease构架时生成的BuildConfig类内容分别如下: 206 | 207 | /** 208 | 209 | * Automatically generated file. DO NOT MODIFY 210 | 211 | */ 212 | 213 | package com.icedcap.myapplication; 214 | 215 | public final class BuildConfig { 216 | 217 | public static final boolean DEBUG = Boolean.parseBoolean("true"); 218 | 219 | public static final String APPLICATION_ID = "com.icedcap.myapplication"; 220 | 221 | public static final String BUILD_TYPE = "debug"; 222 | 223 | public static final String FLAVOR = ""; 224 | 225 | public static final int VERSION_CODE = 1; 226 | 227 | public static final String VERSION_NAME = "1.0"; 228 | 229 | // Fields from build type: debug 230 | 231 | public static final String APP_URL = "http://debug.test.com"; 232 | 233 | public static final boolean LOG_HTTP_CALLS = true; 234 | 235 | } 236 | 237 | 238 | 239 | /** 240 | 241 | * Automatically generated file. DO NOT MODIFY 242 | 243 | */ 244 | 245 | package com.icedcap.myapplication; 246 | 247 | public final class BuildConfig { 248 | 249 | public static final boolean DEBUG = false; 250 | 251 | public static final String APPLICATION_ID = "com.icedcap.myapplication"; 252 | 253 | public static final String BUILD_TYPE = "release"; 254 | 255 | public static final String FLAVOR = ""; 256 | 257 | public static final int VERSION_CODE = 1; 258 | 259 | public static final String VERSION_NAME = "1.0"; 260 | 261 | // Fields from build type: release 262 | 263 | public static final String APP_URL = "http://release.test.com"; 264 | 265 | public static final boolean LOG_HTTP_CALLS = false; 266 | 267 | } 268 | 269 | ## 项目范围设置 270 | 271 | 针对多模块的Android项目,通常在根目录下的build.gradle文件的`allprojects`代码块进行配置时,它将应用在各个子模块中。 272 | 273 | allprojects { 274 | 275 | apply plugin: 'com.android.application' 276 | 277 | android { 278 | 279 | compileSdkVersion 23 280 | 281 | buildToolsVersion "25.0.0" 282 | 283 | } 284 | 285 | } 286 | 287 | 因为Gradle允许在Project对象上添加额外的属性,这就意味着任何gradle.build文件都能定义额外的属性,添加额外的属性需要使用`ext`代码块 288 | 289 | ext { 290 | 291 | local = "hello world, from buid.gradle" 292 | 293 | compileSdkVersion = 22 294 | 295 | buildToolsVersion = "22.0.1" 296 | 297 | } 298 | 299 | 在各个模块的build.gradle文件中可以引用根下build.gradle使用ext声明的变量: 300 | 301 | android { 302 | 303 | compileSdkVersion rootProject.ext.compileSdkVersion 304 | 305 | buildToolsVersion rootProject.ext.buildToolsVersion 306 | 307 | } 308 | 309 | ## 项目属性 310 | 311 | 当然上述在根下的build.gradle文件下使用`ext`声明变量外,还有一些其他方法声明属性变量为模块中的build.gradle来引用: 312 | 313 | - ext代码块 314 | - gradle.properties文件 315 | - -P命令行参数 316 | 317 | ext在前文中声明了一个local变量,同理在`gradle.properties`文件下也声明一个`propertiesFile`变量并赋值,`propertiesFile=hello world from properties`然后我们在根下的build.gradle文件中定义一个打印任务来把这些变量输出出来,具体如下代码 318 | 319 | task printProperties << { 320 | 321 | println local 322 | 323 | println propertiesFile 324 | 325 | if (project.hasProperty('cmd')) { 326 | 327 | println cmd 328 | 329 | } 330 | 331 | } 332 | 333 | 使用命令`gradle printProperties -P cmd="hello world from cmd"`输出如下: 334 | 335 | :printProperties 336 | 337 | hello world, from buid.gradle 338 | 339 | hello world from properties 340 | 341 | hello world from cmd 342 | 343 | ## 默认任务配置 344 | 345 | 通常在根下的build.gradle文件中使用`defaultTask`来定义默认任务: 346 | 347 | defaultTasks 'clean', 'iD' 348 | 349 | 这里我配置了`clean`和`installDebug`为默认的任务,每次命令行输入gradle或者./gradlew不加任何任务名时会默认构建`clean`和`installDebug`两个任务。 350 | 351 | # 依赖管理 352 | 353 | 依赖通常指的是外部依赖,例如其他开发者提供的依赖库,谷歌官方提供的support库等等。当不使用依赖仓库的时候,通常是先下载jar或者aar包放到项目中手动去引用它。而加入了依赖管理机制会很方便的使用到这些依赖库包括该依赖库的名称、版本号等信息。 354 | 355 | 在[Gradle 基础介绍](https://github.com/zylaoshi/AndroidTotal/blob/master/Android%E4%B8%ADGradle%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D.md)已经介绍了`repositories`代码块,在该代码块下添加仓库,默认的只有jcenter仓库: 356 | 357 | repositories { 358 | 359 | jcenter() 360 | 361 | } 362 | 363 | Gradle支持三种不同的依赖仓库`Maven` `Ivy`以及静态文件或文件夹(JCenter是Maven的超集)。而通常一个依赖由三种元素定义它们分别是`group` `name` 和`version`,`group`指的是创建该依赖库的组织通常是反向域名例如io.reactivex,`name`是该依赖的唯一标示例如rxjava,`version`则为依赖库的版本号,使用这三个元素就可以在`dependencies`中声明一个依赖了: 364 | 365 | dependencies { 366 | 367 | compile fileTree(dir: 'libs', include: ['*.jar']) 368 | 369 | testCompile 'junit:junit:4.12' 370 | 371 | compile 'com.android.support:appcompat-v7:24.1.0' 372 | 373 | compile 'com.android.support:cardview-v7:24.1.0' 374 | 375 | compile 'com.android.support:design:24.1.0' 376 | 377 | compile 'com.android.support:support-v13:24.1.0' 378 | 379 | compile 'com.android.support:recyclerview-v7:24.1.0' 380 | 381 | compile 'com.jakewharton:butterknife:7.0.1' 382 | 383 | compile 'com.squareup.okhttp3:okhttp:3.3.1' 384 | 385 | compile 'com.squareup.okhttp3:okhttp-urlconnection:3.3.1' 386 | 387 | compile 'io.reactivex:rxandroid:1.2.1' 388 | 389 | compile 'io.reactivex:rxjava:1.1.6' 390 | 391 | compile 'com.google.code.gson:gson:2.7' 392 | 393 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 394 | 395 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 396 | 397 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 398 | 399 | compile 'org.apache.httpcomponents:httpcore:4.3.2' 400 | 401 | compile 'com.android.support:support-v4:24.1.0' 402 | 403 | compile 'com.github.bumptech.glide:glide:3.7.0' 404 | 405 | } 406 | 407 | ## 远程仓库依赖 408 | 409 | 远程仓库依赖就是指在指定的仓库中拉取要使用的依赖库,通常Gradle预定义了三个Maven仓库:`JCenter` `Maven Central`和本地`Maven Local`仓库,为了在构建脚本中使用他们所以要在`repositories`代码块中包含它们: 410 | 411 | repositories { 412 | 413 | jcenter() 414 | 415 | mavenCentral() 416 | 417 | mavenLocal() 418 | 419 | } 420 | 421 | `jcenter`和`mavenCentral`是两个有名的远程仓库,一般不同时使用他们,通常推荐使用`jcenter`。`jcenter`是`Maven Central`的超集,而且它支持HTTPS传输。`mavenLocal`是已经使用过的所有依赖的本地缓存,当然也可以自己向本地添加依赖。默认的它会以隐藏文件存储在home目录下的.m2文件夹下。 422 | 423 | 很多大厂或者有实力的开发者开发的插件或者依赖库,并且被其很多开发者使用的时候。他更喜欢把该库放在自己的Maven或者Ivy服务器上,而不是统一发布在Jcenter或者Maven Central,为了在自己的项目中使用这个依赖库,这时要在maven代码块中添加url 424 | 425 | repositories { 426 | 427 | maven { 428 | 429 | url "http://repo.example.com/maven2" 430 | 431 | } 432 | 433 | ivy { 434 | 435 | url "http://repo.example.com/repo" 436 | 437 | } 438 | 439 | //自己团队使用的自己的远程仓库 440 | 441 | maven { 442 | 443 | url "http://repo.mycompany.com/maven2" 444 | 445 | credentials { 446 | 447 | username 'user' 448 | 449 | password 'password' 450 | 451 | } 452 | 453 | } 454 | 455 | //本机仓库,把路径作为url 456 | 457 | maven { 458 | 459 | url "../repo" 460 | 461 | } 462 | 463 | //除了把路径作为url还可以使用flatDir代码块 464 | 465 | flatDir { 466 | 467 | dirs 'aars' 468 | 469 | } 470 | 471 | } 472 | 473 | **注意:**不建议在配置文件中存储任何密码或者凭证,因为构建配置文件是纯文本它会被迁入代码控制系统中,从而导致密码泄露。最好的办法是使用前文提到的单独的属性文件定义这些变量并且赋值。 474 | 475 | ## 本地文件(夹)依赖 476 | 477 | 把单独文件作为依赖库可以使用`files`: 478 | 479 | dependencies { 480 | 481 | compile files('libs/xxx.jar') 482 | 483 | ... 484 | 485 | } 486 | 487 | 把一个目录下单所以jar文件添加到依赖: 488 | 489 | dependencies { 490 | 491 | compile fileTree(dir: 'libs', include: ['*.jar']) 492 | 493 | ... 494 | 495 | } 496 | 497 | 对于原生依赖库可以使用`sourceSet`资源集来定义它,默认的原生库文件夹命名为jniLibs,并且在该文件夹下创建每一个平台如`armeabi` `armeabi-v7a` `mips`以及`x86`,将每一个平台下的so文件放在相应的文件夹下,在gradle配置如下: 498 | 499 | android { 500 | 501 | sourceSets.main { 502 | 503 | jniLibs.srcDir `src/main/libs` 504 | 505 | } 506 | 507 | } 508 | 509 | ## 项目依赖以及依赖类型 510 | 511 | 当我们在项目中新建一个模块用于库项目时,我们要在该模块的gradle配置一句`apply plugin: 'com.android.library'`这句话的意思是为该模块加入library插件以至于该模块被构建为共享库并且生成jar或者aar包。当然除了定义该插件外还要记得在[Gradle 基础介绍](https://github.com/zylaoshi/AndroidTotal/blob/master/Android%E4%B8%ADGradle%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D.md)经过的在setting.gradle文件中加入该模块`include ':app', ':library'`。 512 | 513 | 当我们在app下使用该库的话可以在`dependencies`代码块下定义它的依赖: 514 | 515 | dependencies { 516 | 517 | compile(name:'libraryname', ext:'aar') 518 | 519 | } 520 | 521 | 上述代码定义的依赖,构建app时会去`libraryname`项目下找aar包。 522 | 523 | 当我们使用AndroidStudio创建一个新的安卓项目时在`dependencies`代码块下包括以下几个库 524 | 525 | dependencies { 526 | 527 | compile fileTree(dir: 'libs', include: ['*.jar']) 528 | 529 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 530 | 531 | exclude group: 'com.android.support', module: 'support-annotations'}) 532 | 533 | compile 'com.android.support:appcompat-v7:25.0.1' 534 | 535 | testCompile 'junit:junit:4.12' 536 | 537 | } 538 | 539 | 我们可以看到除了`compile`命令外还有`testCompile`和`androidTestCompile`,这些命令统称为scope也可以通过IDE的Project Structure可以看到。 540 | 除了上述AndroidStudio创建项目时默认添加的三个外完整的scope依赖类型包括如下: 541 | 542 | - compile 默认的配置,在编译主应用时包含所有的依赖。该配置不仅会将依赖添加到类路径,还会生成对应的apk 543 | - apk 该依赖只会被打包进apk,而不会添加到编译类路径 544 | - provided 该类型的依赖刚好相反,它不会被打包进apk,该类型与apk类型的依赖只适用于JAR依赖,如果试图在依赖项目中添加他们将会出错。 545 | - testCompile和androidTestCompile类型的依赖会添加用于测试的额外依赖库,在运行测试相关的任务时,这些配置会被使用。 546 | 547 | # 多版本构建 548 | 549 | 当开发一个应用时,通常会有几个不同的版本。最常见的情况是,你有一个手动测试用于保证质量的测试版本和一个生产版本。这些版本通常有不同的配置,例如**BuildConfig设置**小节讲到的几个变量在debug和release两个版本的不同。除此之外,你的应用有可能还有一个免费版和一个额外功能付费版。在这种情况下,你需要处理四中不同的版本,它们分别是:免费测试版、付费测试版、免费正式版、付费正式版。所以涉及到这种多版本的构建也是很伤脑筋的,幸好Gradle支持多版本构建。 550 | 551 | 在这里我们先把debug和release两个版本称为**构建类型**把类似于测试版和付费版命名为**product flavor(产品定制)**。Gradle在构建含有product flavor的项目时会结合构建类型(debug和release)一起生产每个product flavor 的debug和release版本。 552 | 553 | 所以下面我们就来探讨下构建类型和product flavor。 554 | 555 | ## 构建类型 556 | 557 | 在Gradle的Android插件中,构建类型通常被定义为如何构建一个应用或者依赖库。你可以在`buildTypes`代码块中来定义构建类型: 558 | 559 | buildTypes { 560 | 561 | release { 562 | 563 | minifyEnabled false //清除无用的资源 564 | 565 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 566 | 567 | } 568 | 569 | } 570 | 571 | 使用AndroidStudio时会自动生成上述的代码,你可以在`release`代码块中定义其他有关release版本的信息。除此之外,Gradle的Android插件其实是默认已经配置好了`debug`的构建类型而且它是默认的构建类型,当然你也可以在上述的代码块中加入`debug`的配置来覆盖默认的配置。 572 | 573 | 除了`debug`和`release`两种我们常用的构建类型外其实我们可以自定义其他的构建类型。比如: 574 | 575 | buildTypes { 576 | 577 | ... 578 | 579 | myBuildType { 580 | 581 | applicationIdSuffix ".custom" 582 | 583 | versionNameSuffix "-custom" 584 | 585 | buildConfigField "String", "APP_URL","\"http://custom.test.com\"" 586 | 587 | } 588 | 589 | } 590 | 591 | `myBuildType`构建类型针对`applicationID`定义了一个新的后缀,使其和`debug`和`release`两个版本的`applicationID`不一样从而可以安装在同一台设备上。就像下面三个构建类型的包名一样: 592 | 593 | - **Debug :** com.package 594 | - **Release :** com.package 595 | - **myBuildType :** com.package.custom 596 | 597 | 在定义新的构建类型或者修改已有的构建类型时可以使用已经声明好的构建类型的配置进行初始化,从而减少配置代码的书写: 598 | 599 | buildTypes { 600 | 601 | ... 602 | 603 | staging.initWith(buildTypes.debug) 604 | 605 | staging { 606 | 607 | applicationIdSuffix ".staging" 608 | 609 | versionNameSuffix "-staging" 610 | 611 | debuggable false 612 | 613 | } 614 | 615 | } 616 | 617 | `initWith()`方法创建一个新的构建类型,并且复制了一个已经存在的构建类型的所有属性到新的构建类型中。 618 | 619 | 无论是多构建类型还是多product flavor的定义都会产生多个sourceSet来存放这些不同构建类型和不同产品定制之间的不同资源文件,例如,一个_Constants.java_类文件在不同构建类型下肯定是不同的或者每个构建类型或定制产品的首页布局有所不同等等。 620 | ![](http://p981u1am0.bkt.clouddn.com/18-6-8/2949562.jpg) 621 | 622 | 这里提出一个问题当我在定义product flavor时也会定义不同的sourceSets,当在构建不同的的版本时比如付费版release版时这些源集是如何合并的呢?哈哈,先挖个坑,之后再填! 623 | 624 | 对于依赖来说,比如我只想在debug版本中添加logging框架怎么办?很简单上小节**依赖管理**我们也讲到了使用debugCompile的scope。 625 | 626 | ## product flavor 627 | 628 | 与构建类型不同product flavor是用来创建不同的版本,并且使用`productFlavor`代码块来创建不同版本: 629 | 630 | productFlavors { 631 | 632 | red { 633 | 634 | applicationId 'com.example.red' 635 | 636 | versionCode 3 637 | 638 | } 639 | 640 | blue { 641 | 642 | applicationId 'com.example.blue' 643 | 644 | versionCode 4 645 | 646 | minSdkVersion 14 647 | 648 | } 649 | 650 | } 651 | 652 | 对于product flavor的源集,源集文件夹要以product flavor的名字命名。甚至可以为一个构建类型和一个productflavor的结合创建一个文件夹。比如,blue版本的release构建类型可以创建blueRelease名字的文件夹存放该版本所要使用到不同其他版本的资源文件。 653 | 654 | 在很多情况下productflavor可以按照维度来划分又可以拆成不同的版本,比如说,可以按照颜色和是否收费又可以划分为红色免费版、红色付费版、蓝色免费版、蓝色付费版等等。对于这种需求的版本划分就要使用`flavorDimensions`:flavorDimensions "color", "price" 655 | 656 | productFlavors { 657 | 658 | red { 659 | 660 | flavorDimension "color" 661 | 662 | applicationId 'com.example.red' 663 | 664 | versionCode 3 665 | 666 | } 667 | 668 | blue { 669 | 670 | flavorDimension "color" 671 | 672 | applicationId 'com.example.blue' 673 | 674 | versionCode 4 675 | 676 | minSdkVersion 14 677 | 678 | } 679 | 680 | free { 681 | 682 | flavorDimension "price" 683 | 684 | } 685 | 686 | paid { 687 | 688 | flavorDimension "price" 689 | 690 | } 691 | 692 | } 693 | 694 | 有了维度就意味着构建版本又可以组合为新的构建版本对于不同flavor配置了相同的属性那么就会按照维度设置的顺序进行覆盖。例如上面的color维度的属性将会覆盖price维度的属性。 695 | 696 | ## 构建variant 697 | 698 | 好了,开始填补上面挖的坑了。 699 | 构建variant就是构建这些构建类型和productflavor的结合体,在我机器上配置了两个维度的productflavor和两个构建类型,然后分别生成如下的构建variant: 700 | 701 | ![](http://p981u1am0.bkt.clouddn.com/18-6-8/6152104.jpg) 702 | 703 | 有了构建variant就可以使用gradle命令构建了(IDE也有相应的按钮进行构建,不过笔者更喜欢全程使用命令行)。一个Android项目默认有debug和release两种构建类型所以可以使用`gradle assembleDebug`和`gradle assembleRelease`来分别构建两个版本的apk,单独使用`gradle assemble`命令是构建这两个版本的apk。当又定义了productflavor后呢?我们可以使用`gradle assembleRed`来构建red版的debug和release两个apk。使用`gradle assembleDebug`可以生产red版和blue版的测试版本apk,而使用`gradle assembleBlueDebug`只会生成blue的release版本。 704 | 705 | **多版本结合如何合并sourceSets中的资源呢?** 706 | 多版本结合后的sourceSet资源是按照一定的优先级进行合并覆盖的。首先Gradle的Android插件在打包应用之前将main文件夹下的代码和构建类型sourceSet下的代码合并在一起,此外library项目也可以提供额外的资源,这些也需要合并。这样同样适用于manifest文件。例如在应用的debug variant中可能需要额外的Android权限来存储log文件,而你不想在main里中声明该权限,因为会吓跑用户。相反可以在debug版本中单独定义sourceSet把该权限放到这个sourceSet中。所以资源以及manifest的有限构建顺序如下: 707 | 708 | 709 | **BUILD TYPE**->**FLAVOR**->**MAIN**->**DEPENDENCIES** 710 | 711 | 当然有时候对于不同版本的改动比较小使用sourceSet过于繁重,这时候我们可以直接在构建脚本中声明不同的资源id以及它的值,比如: 712 | 713 | productFlavors { 714 | 715 | red { 716 | 717 | applicationId 'com.example.red' 718 | 719 | versionCode 3 720 | 721 | resValue "color", "flavor_color", "#ff0000" 722 | 723 | } 724 | 725 | } 726 | 727 | 所以在构建red版本时候代码中使用`flavor_color`颜色的资源引用是`#ff0000`。 728 | 729 | 最后,所有的variant可以通过过滤来构建。比如如下配置: 730 | 731 | android.variantFilter { variant -> 732 | 733 | if (variant.buildType.name.equals('release')) { 734 | 735 | variant.getFlavors().each() { flavor -> 736 | 737 | if (flavor.name.equals('blue')) { 738 | 739 | variant.setIgnore(true); 740 | 741 | } 742 | 743 | } 744 | 745 | } 746 | 747 | } 748 | 749 | 这时候我在查看我的所有variant如下: 750 | 751 | 752 | ![](http://p981u1am0.bkt.clouddn.com/18-6-8/80994048.jpg) 753 | 754 | 很显然过滤掉了release和blue的结合variant。 755 | 756 | # 签名配置 757 | 758 | 对于签名配置来说比较简单,代码如下: 759 | 760 | signingConfigs{ 761 | 762 | release { 763 | 764 | keyAlias 'myapp.keystore' 765 | 766 | keyPassword 'xxx' 767 | 768 | storePassword 'xxx' 769 | 770 | storeFile file('myapp.keystore') 771 | 772 | } 773 | 774 | } 775 | 776 | buildTypes { 777 | 778 | ... 779 | 780 | release { 781 | 782 | signingConfig signingConfigs.release 783 | 784 | minifyEnabled false 785 | 786 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 787 | 788 | } 789 | 790 | } 791 | -------------------------------------------------------------------------------- /Android中adb命令抓取logcat 日志.md: -------------------------------------------------------------------------------- 1 | ## Android 中通过adb命令专区logcat日志 2 | 3 | > Android开发中,我们需要打印、抓取log,来判断、定位问题,使用Android Studio开发工具,在debug模式下我们可以很容易的抓取、捕获日志信息,快速的定位为题;但是当我们打Release包的时候,没有办法在编译工具上直观的查看log,所以就需要通过abd命令的形式实现。 4 | 5 | 1. 首先,需要`abd.exe`、`AbdWinApi.dll`、`AbdWinUsbApi.dll`,三个文件,这个在Android的sdk的platform_tools文件夹下面是会有的;若是没有,自行下载拷贝到相应目录即可。 6 | 2. 在Android Studio中,打开terminal窗口,或是在 abd.exe所在的文件夹中,打开cmd窗口。 7 | 3. 在terminal窗口中,输入`abd devices`命令,查看手机是否连接电脑。 8 | 9 | E:\kotlin_demo>adb devices 10 | List of devices attached 11 | 988b5a333851304d34 device 12 | 13 | **List of devices attached** -->表示设备已经连接。 14 | 15 | 4. 继续在terminal中,输入`abd shell`命令。 16 | 17 | E:\kotlin_demo>adb shell 18 | dreamqltechn:/ $ 19 | 20 | 5. 继续输入`logcat -f /mnt/sdcard/demo_log.log`,`-f`是指写入文件到路径。 21 | 22 | E:\kotlin_demo>adb shell 23 | dreamqltechn:/ $ logcat -f /mnt/sdcard/demo_log.log 24 | 25 | 6. 复现问题,出现问题后按下Ctrl+C来取消抓取日志,再按下 Ctrl+d 退出adb shell模式 26 | 7. 输入指令 `adb pull /mnt/sdcard/demo_log.log E:\kotlin_demo` ,开始把手机根目录下的demo_log.log文件复制到计算机上的`E:\kotlin_demo`文件路径下。 27 | 28 | E:\kotlin_demo>adb pull /mnt/sdcard/demo_log.log E:\kotlin_demo 29 | 8. 或是在terminal中输入命令直接将log打印出来,命令如下,logcat | grep `TAG` 30 | 31 | E:\kotlin_demo>adb shell 32 | dreamqltechn:/ $ logcat | grep kotlin_demo 33 | 34 | -------------------------------------------------------------------------------- /Android权限之通知、自启动跳转.md: -------------------------------------------------------------------------------- 1 | # Android权限之通知、自启动跳转 2 | >关于极光推送,由于Android手机的特性,用户在关闭app的情境下,无法继续再接收到极光推送的消息;对于我们开发人员来说,需要解决的问题就是怎么样最大化的保证,能接收到推送通知的用户足够的多。 3 | 4 | ## 解决方案 5 | 解决方案分为三种情况,下面给大家分享下,一些解决的心得体会: 6 | 7 | - 通过监听系统广播+Service的形式,拉起app程序保活,这里需要保证Service的活性,不容易被杀掉:1、将service优先级调到最大;2、在onDestroy()中自启。3.将优先级设置最大;在AndroidManifest.xml文件里将persistent设置为true。**适用于放在/system/app下的app** 8 | ``` 9 | 10 | ``` 11 | - 从推送的SDK寻找解决的方案,可以尝试的接入不同厂商的系统通道的推送(小米、华为),或是在接入极光推送的基础上,再接入它们的vip通道(目前有小米、华为、魅族的FCM),不过石收费的。详细的可以联系它们的客服。 12 | - 引导用户手动开启通知、将app加入开启自启动白名单、或是将app加到多任务锁。具体引导流程,不妨参考企业微信的消息、通知开启的引导方式。 13 | 14 | ## 开启通知- - -跳转app权限设置界面 15 | 开启通知权限,区别于存储、拍照、电话、定位的权限。这些权限需要动态获取的,而且调用后,就会有交互的弹窗用于拒绝或是授权,通知权限需要先判断,若没有授权,再跳转到系统界面授权。判断及跳转的代码如下。 16 | ``` 17 | /** 18 | * 判断允许通知,是否已经授权 19 | * 返回值为true时,通知栏打开,false未打开。 20 | *@param context 上下文 21 | */ 22 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 23 | private boolean isNotificationEnabled(Context context) { 24 | 25 | String CHECK_OP_NO_THROW = "checkOpNoThrow"; 26 | String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; 27 | 28 | AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 29 | ApplicationInfo appInfo = context.getApplicationInfo(); 30 | String pkg = context.getApplicationContext().getPackageName(); 31 | int uid = appInfo.uid; 32 | 33 | Class appOpsClass = null; 34 | /* Context.APP_OPS_MANAGER */ 35 | try { 36 | appOpsClass = Class.forName(AppOpsManager.class.getName()); 37 | Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, 38 | String.class); 39 | Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION); 40 | 41 | int value = (Integer) opPostNotificationValue.get(Integer.class); 42 | return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED); 43 | 44 | } catch (ClassNotFoundException e) { 45 | e.printStackTrace(); 46 | } catch (NoSuchMethodException e) { 47 | e.printStackTrace(); 48 | } catch (NoSuchFieldException e) { 49 | e.printStackTrace(); 50 | } catch (InvocationTargetException e) { 51 | e.printStackTrace(); 52 | } catch (IllegalAccessException e) { 53 | e.printStackTrace(); 54 | } 55 | return false; 56 | } 57 | 58 | /** 59 | * 跳转到app的设置界面--开启通知 60 | * @param context 61 | */ 62 | private void goToNotificationSetting(Context context) { 63 | Intent intent = new Intent(); 64 | if (Build.VERSION.SDK_INT >= 26) { 65 | // android 8.0引导 66 | intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); 67 | intent.putExtra("android.provider.extra.APP_PACKAGE", context.getPackageName()); 68 | } else if (Build.VERSION.SDK_INT >= 21) { 69 | // android 5.0-7.0 70 | intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); 71 | intent.putExtra("app_package", context.getPackageName()); 72 | intent.putExtra("app_uid", context.getApplicationInfo().uid); 73 | } else { 74 | // 其他 75 | intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 76 | intent.setData(Uri.fromParts("package", context.getPackageName(), null)); 77 | } 78 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 79 | context.startActivity(intent); 80 | } 81 | 82 | ``` 83 | ## 自启动- - -跳转系统自启动界面 84 | 跳转到手机系统的自启动界面,自行勾选自启动功能。这里不同手机厂商的rom不同,这个功能页面也不是一致的、这个功能界面在有的系统中石不允许外部Intent直接跳转的,所以我们只能跳转到该子页面的前一级界面;我们尽可能多的做主流界面的兼容。 85 | >首先,自启动界面,在不同手机上的UI入口是不同的,有的是在系统设置中,有的是在手机自带的安全管家中设置的,需要我们自行的获取当前activity的名称,之后指定ComponentName来跳转。 86 | - 打开手机相应的自启动界面,在terminal中运行,adb名称获取当前activity的全称。 87 | ``` 88 | adb shell dumpsys activity top 89 | ``` 90 | Samsung手机的结果如下,以供参考: 91 | 92 | ![](http://p981u1am0.bkt.clouddn.com/18-5-24/92317295.jpg) 93 | - 将上面的目标Activity 的路径copy,通过Intent 的ComponentName形式来跳转打开,并进行下一步的自启动操作的设置。因为手机厂商目标Activity的路径需要相应的适配。附上我适配的代码: 94 | ``` 95 | /** 96 | * 跳转到手机系统的自启动界面或是手机管家界面 97 | * adb shell dumpsys activity top 98 | * @param context 99 | */ 100 | public static void jumpStartManager(Context context) { 101 | Intent intent = new Intent(); 102 | try { 103 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 104 | Log.e("StartUtils", "当前手机型号为:" + getMobileType()); 105 | ComponentName componentName = null; 106 | if (getMobileType().equals("Xiaomi")) { 107 | // 小米 5s、红米note 4s 108 | componentName = new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"); 109 | } else if (getMobileType().equals("Letv")) { 110 | // 乐视2 111 | intent.setAction("com.letv.android.permissionautoboot"); 112 | } else if (getMobileType().equals("samsung")) { 113 | //samsung Galaxy s7 114 | componentName = ComponentName.unflattenFromString("com.samsung.android.sm_cn/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity"); 115 | //componentName = new ComponentName("com.samsung.android.sm_cn", "com.samsung.android.sm.ui.ram.AutoRunActivity"); 116 | } else if (getMobileType().equals("HUAWEI")) { 117 | //华为 p10 android 8.0 118 | //componentName = ComponentName.unflattenFromString("com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity"); 119 | //荣耀 H60-l03 android 4.4.2 120 | componentName = ComponentName.unflattenFromString("com.huawei.systemmanager/.optimize.bootstart.BootStartActivity"); 121 | //componentName = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"); 122 | } else if (getMobileType().equals("vivo")) { 123 | // vivo x7只能进入安全管家的界面 124 | componentName = ComponentName.unflattenFromString("com.iqoo.secure/.MainActivity"); 125 | //componentName = ComponentName.unflattenFromString("com.iqoo.secure/.safeguard.PurviewTabActivity"); 126 | // vivo x7 进入手机白名单界面 127 | //componentName = ComponentName.unflattenFromString("com.iqoo.secure/.ui.phoneoptimize.AddWhiteListActivity"); 128 | } else if (getMobileType().equals("Meizu")) { 129 | // 魅族 130 | componentName = ComponentName.unflattenFromString("com.meizu.safe/.permission.SmartBGActivity"); 131 | //componentName = ComponentName.unflattenFromString("com.meizu.safe/.permission.PermissionMainActivity"); 132 | } else if (getMobileType().equals("OPPO")) { 133 | // OPPO R9m 可以 134 | componentName = ComponentName.unflattenFromString("com.coloros.safecenter/.startupapp.StartupAppListActivity"); 135 | //componentName = ComponentName.unflattenFromString("com.oppo.safe/.permission.startup.StartupAppListActivity"); 136 | //耗电列表 137 | //componentName = ComponentName.unflattenFromString("com.coloros.oppoguardelf/com.coloros.powermanager.fuelgaue.PowerUsageModelActivity"); 138 | } else if (getMobileType().equals("ulong")) { 139 | // 360手机 140 | componentName = new ComponentName("com.yulong.android.coolsafe", ".ui.activity.autorun.AutoRunListActivity"); 141 | } else { 142 | // 将用户引导到系统设置页面 143 | if (Build.VERSION.SDK_INT >= 9) { 144 | intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 145 | intent.setData(Uri.fromParts("package", context.getPackageName(), null)); 146 | } else if (Build.VERSION.SDK_INT <= 8) { 147 | intent.setAction(Intent.ACTION_VIEW); 148 | intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); 149 | intent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName()); 150 | } 151 | } 152 | intent.setComponent(componentName); 153 | context.startActivity(intent); 154 | } catch (Exception e) { 155 | //抛出异常就直接打开设置页面 156 | Log.e("StartUtils", "异常信息:" + e.getMessage().toString()); 157 | intent = new Intent(Settings.ACTION_SETTINGS); 158 | context.startActivity(intent); 159 | } 160 | } 161 | ``` 162 | 163 | 从上面的代码中,我们可以的发现,目标Activity的全称路径。在不同手机厂商中是不同的,在同一手机厂商不同型号、系统版本手机中,也有可能是不一样的,这就需要我们做多类型的兼容。 164 | > 推荐一种实现的思路: 165 | app端获取到手机型号、版本号等信息并调用后台的接口,以参数的形式,将信息传递给后台,后台根据不同的参数,返回网页链接(这里的链接可能是多个不同的url,根据手机型号、系统版本做匹配,可以参考企业微信的网页链接,适配就做的比较全面),app端打开网页链接,在h5界面中点击前往设置的按钮,调用原生的方法,进而跳转到系统的自启动界面。 166 | -------------------------------------------------------------------------------- /Fragment中onHiddenChanged、setUserVisibleHint触发条件.md: -------------------------------------------------------------------------------- 1 | 2 | ## Fragment中onHiddenChanged、setUserVisibleHint触发条件 3 | > 在Fragment使用的过程,根据不能的业务需求,我们使用Fragment的情景。多个fragment+viewpage使用;或是多个fragment的add()、hide()或replace()使用。 4 | 5 | ### Fragment的onHiddenChanged()触发条件 6 | ``` 7 | /** 8 | * Called when the hidden state (as returned by {@link #isHidden()} of 9 | * the fragment has changed. Fragments start out not hidden; this will 10 | * be called whenever the fragment changes state from that. 11 | * @param hidden True if the fragment is now hidden, false otherwise. 12 | */ 13 | public void onHiddenChanged(boolean hidden) { 14 | } 15 | ``` 16 | 查看onHidden Change()源码,可以看出,触发执行条件是fragment状态改变,是否可见。由isHidden()方法决定。 17 | 18 | ``` 19 | /** 20 | * Return true if the fragment has been hidden. By default fragments 21 | * are shown. You can find out about changes to this state with 22 | * {@link #onHiddenChanged}. Note that the hidden state is orthogonal 23 | * to other states -- that is, to be visible to the user, a fragment 24 | * must be both started and not hidden. 25 | */ 26 | final public boolean isHidden() { 27 | return mHidden; 28 | } 29 | ``` 30 | 如果该Fragment对象已经被隐藏,那么它返回true。默认情况下,Fragment是被显示的。能够用onHiddenChanged(boolean)回调方法获取该Fragment对象状态的改变,要注意的是隐藏状态与其他状态是正交的---也就是说,要把该Fragment对象显示给用户,Fragment对象必须是被启动并不被隐藏(显示)。 31 | > 值得我们注意的是 这里的隐藏或者显示是指Fragment调用show或者hider的时候才会改变mHidden的值得。 32 | * 当点击Fragment A的内容跳转到Activity B;包括从Activity B再次返回到Fragment A 。这些情况下Fragment A的onHiddenChange()是不会执行的。 33 | * 当前界面为Fragment B,点击Home键回到主屏幕,这个时候Fragment B在手机屏幕上是不可见的,这种情况下监听onHiddenChange()方法来判断fragment的显隐状态是不可行。这种情况下,会响应fragmnet 的onPause()方法。 34 | * 将onHiddenChange()与onPause()、onResume()搭配使用,才能更完美的解决问题。 35 | #### 情景一: 36 | 从其他 Activity 返回这个 Activity 时,当前 Fragment 不会调用 onHiddenChanged() 方法,需要在 onResume() 方法中请求服务器 37 | 解决方案:为了实现切换刷新操作,必须在 onResume() 和 onHiddenChanged() 方法中请求服务器。但是还得避免重复多次请求服务器操作,必须在两个方法中添加状态判断,只有对用户可见时,才刷新界面。 38 | ``` 39 | @Override 40 | public void onResume() { 41 | super.onResume(); 42 | if (isVisible()){ 43 | // 发起网络请求, 刷新界面数据 44 | requestData(); 45 | } 46 | } 47 | @Override 48 | public void onHiddenChanged(boolean hidden) { 49 | super.onHiddenChanged(hidden); 50 | // 这里的 isResumed() 判断就是为了避免与 onResume() 方法重复发起网络请求 51 | if (isVisible() && isResumed()){ 52 | requestData(); 53 | } 54 | } 55 | ``` 56 | 还有一种特殊情况需要处理,就是系统由于内存不足时杀掉 App 的情况。如果当前显示的不是第一个 Fragment,App 被杀掉再次重启时,显示这个 Fragment 时,isVisible() 的判断始终为 false,这种情况下刷新数据的操作,还要额外处理。 57 | ``` 58 | @Override 59 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 60 | super.onActivityCreated(savedInstanceState); 61 | if (savedInstanceState!=null){ 62 | requestData(); 63 | } 64 | } 65 | ``` 66 | 67 | ### Fragment的setUserVisibleHint()触发条件 68 | 多数UI界面和业务,需要我们使用ViewPager+TabLayout+Fragment来实现。我们需要借助 PagerAdapter(FragmentPagerAdapter和FragmentStatePagerAdapter)来实现多个fragment与viewpager的绑定。这种情况下,fragment的setUserVisableHint()方法才会被触发响应。 69 | 70 | #### FragmentPagerAdapter 71 | ![FragmentPagerAdapter部分代码截图](http://p981u1am0.bkt.clouddn.com/18-6-5/43162487.jpg) 72 | > FragmentPagerAdapter实际上是使用add(),attach()和detach()来管理Fragment的。 73 | > 需要注意的是所有实例化过的Fragment实例都会保存在内存中,所以适合页面数量不多且固定的app首页等情况。 74 | 75 | #### FragmentStatePagerAdapter 76 | ![FragmentStatePagerAdapter部分代码截图](http://p981u1am0.bkt.clouddn.com/18-6-5/27859897.jpg) 77 | > FragmentStatePagerAdapter使用add()和remove()管理Fragment,所以缓存外的Fragment的实例不会保存在内存中,适合分页多,数据动态的情况 78 | 79 | #### 显示和隐藏 80 | 对干缓存数量外的Fragment会被detach或remove,我们可以根据其常规生命周期进行开发,但是缓存数量内的显隐并不会影响生命周期,我们可以通过setUserVisableHint()方法来拍段某个Fragment是否显示。 81 | 82 | ![Fragment+PagerAdapter,生命周期变化](http://p981u1am0.bkt.clouddn.com/18-6-5/42571706.jpg) 83 | 84 | 从日志中我们可以看出,缓存数量内的Fragment0和Fragment1的setUserVisableHint()方法的isVisibleToUser首先会被设置成false,然后分别进行onAttach() - onResume()的生命周期,其中需要显示的Fragment在onCreate()之后,会将isVisibleToUser置为true,然后显示出来。 85 | 注意:**由此可见setUserVisibleHint()有可能在onAttach()之前调用,并且到显示前可能调用2次。** 86 | 87 | 再之后的显隐就是设置isVisibleToUser并回调通知了。和上文中的onHiddenChanged()一样,显隐状态没有变化时,也是不会回调的。 88 | -------------------------------------------------------------------------------- /HttpClient与HttpURLConnection的区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

HttpClient与HttpURLConnection的区别

7 |

文章转载自https://www.jianshu.com/p/a32d6980227b

8 |

很多Android初学者在接触到Http协议的时候,估计都会在选择HttpClient还是HttpURLConnection之间,这两个都是Android中包含的Http客户端类。那么,这两个有什么区别呢?
9 |   咳,其实在之前对我而言并没有区别,因为我平时都是用的开源框架比如Async-http-client。
10 |   虽然Android 6.0中已经抛弃了HttpClient转而使用OkHttp,不过既然很多面试都喜欢问这个问题,那么还是了解一下吧。

11 |
12 |

补充

13 |

TCP/IP、Socket、Http简要介绍

14 |

1、TCP/IP中文名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。

15 |

2、Socket是支持TCP/IP协议的网络通信基本操作单元,许多操作系统为应用程序提供了一套调用接口(API),方便开发者开发网络程序。注意,socket本身并不是协议,只是提供一个针对TCP或UDP的编程接口。

16 |

3、HTTP协议是一个web服务器和客户端通信的超文本传送协议,是建立在TCP协议上的一个应用层协议。HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

17 |

Http 1.0与1.1的区别

18 |

最主要的区别就是连接时,在1.0中只能1个单独的连接只能处理1次请求,1.1中可以执行多个请求,并且多个请求可以叠加而不用等待一个请求结束再发送下一个请求。

19 |
20 |

背景

21 |

Http Client

22 |

Apache公司提供的库,提供高效的、最新的、功能丰富的支持HTTP协议工具包,支持HTTP协议最新的版本和建议,是个很不错的开源框架,封装了Http的请求,参数,内容体,响应等,拥有众多API。

23 |

HttpURLConnection

24 |

Sun公司提供的库,也是Java的标准类库java.net中的一员,但这个类什么都没封装,用起来很原始,若需要高级功能,则会显得不太方便,比如重访问的自定义,会话和cookie等一些高级功能。

25 |
26 |

区别

27 |

功能上

28 |

Http Client适用于web浏览器,拥有大量灵活的API,实现起来比较稳定,且其功能比较丰富,提供了很多工具,封装了http的请求头,参数,内容体,响应,还有一些高级功能,代理、COOKIE、鉴权、压缩、连接池的处理。
29 |   但是,正因此,在不破坏兼容性的前提下,其庞大的API也使人难以改进,因此Android团队对于修改优化Apache Http Client并不积极。(并在Android 6.0中抛弃了Http Client,替换成OkHttp)

30 |

HttpURLConnection对于大部分功能都进行了包装,Http Client的高级功能代码会较复杂,另外,HttpURLConnection在Android 2.3中增加了一些Https方面的改进(包括Http Client,两者都支持https)。且在Android 4.0中增加了response cache。

31 |

性能上

32 |

HttpURLConnection直接支持GZIP压缩,默认添加"Accept-Encoding: gzip"头字段到请求中,并处理相应的回应,而Http Client虽然支持,但需要自己写代码处理。
33 |   注意:但在2.3中,由于Http的Content-Length头字段返回的是压缩后的大小,直接使用getContentLength()方法得到的数据大小是错误的,应该使用InputStream.read()直到返回值是-1为止。

34 |

HttpURLConnection直接在系统层面做了缓存策略处理(Android 4.0以上),Http请求将在以下三种中选择:
35 |   1、完全的cache的response将直接从本地存储中获取。因为不需要网络连接,此类response可以立即得到。
36 |   2、有条件cache的response必须在Web服务器验证一下cache的有效性。客户端发送一个请求,比如“如果/foo.png从昨天起有变化则给我新的图片” , 服务端的response要么是更新后的内容,要么是304 没有修改状态码。如果内容是没有改变的,就不需要下载了。
37 |   3、没有cache的response将从服务器上获取。得到这些response之后会存储到cache以便将来使用。

38 |

这篇文章中对两者的速度做了一个对比,做法是两个类都使用默认的方法去请求百度的网页内容,测试结果是使用httpurlconnection耗时47ms,使用httpclient耗时641ms。httpURLConnection在速度有比较明显的优势,当然这跟压缩内容和缓存都有直接关系。

39 |
40 |

选用

41 |

HttpURLConnect是一个通用的、适合大多数应用的轻量级组件。这个类起步比较晚,很容易在主要API上做稳步的改善。但是HttpURLConnection在在Android 2.2及以下版本上存在一些令人厌烦的bug,尤其是在读取 InputStream时调用 close()方法,就有可能会导致连接池失效了。

42 |

因此,在2.2之前的版本,Http Client会是一个比较好的选择,而自2.3起,HttpURLConnection将是最佳选择,其API简单,小巧,非常适合于Android。透明的压缩及response cache减少了网络流量,改进了网络速度,也就更省电。

43 |

在Volley框架的源码中,会发现它在Http请求的使用上也是如此,在Android 2.3及以上的版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。

44 |
45 |

Written with StackEdit.

46 |
47 | 48 | -------------------------------------------------------------------------------- /ListView、RecycleView的item点击事件无效.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

ListView、RecycleView的item点击事件无效
7 | 产生的原因是应为item的布局中又有CheckBox、Button、RadioButton、EditText等控件的存在,优先获取焦点,导致父布局的点击事件失效。
8 | 解决方案:
9 | 1.将Button、CheckBox的实现替换为TextView、ImageView。
10 | 2.若是不能替换,就讲item布局中这些view的focusable属性设置为false。
11 | 3.设置ListView的item的根布局android:descendantFocusability=“blocksDescendants”,一般推荐第三种,意思是ListView的item下边所有的子控件都不能获取焦点。

12 |

android:descendantFocusability的值有3种,其中ViewGroup指的是设置这个属性的View,在这里就指的是ListView的item的根布局:

13 |
beforeDescendants:ViewGroup会优先其子类控件而获取到焦点
14 | afterDescendants:ViewGroup只有当其子类控件不需要获取焦点时才获取焦点
15 | blocksDescendants:ViewGroup会覆盖子类控件而直接获得焦点
16 | 
17 |
18 |

Written with StackEdit.

19 |
20 | 21 | -------------------------------------------------------------------------------- /Mac os 配置gradle环境变量.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Mac OS 配置 Android stduio 中的gradle 环境变量

7 |

1.找到已经安装的gradle的文件路径

8 |
    9 |
  • 打开访达–>在左侧任务栏中选择 应用程序–>找到Android stduio的图标并右键打开显示包内容–>Contents–>gradle–>gradle-xxx版本–>bin 路径。
  • 10 |
  • 复制bin下gradle文件的路径为/Applications/Android Studio.app/Contents/gradle/gradle-5.4/bin
    11 | 其中/Applications/Android后面手动输入\,后面留有空格,最终如下:
    12 | /Applications/Android\ Studio.app/Contents/gradle/gradle-5.1.1/bin
    13 | 2.配置gradle路径
  • 14 |
  • 打开terminal,执行以下命令。
    1. cd ~
    15 | //到home目录下
    16 | 2. touch .bash_profile
    17 | //在没有.bash_profile时会重新创建
    18 | 3. open -e .bash_profile
    19 | //会以文本的形式打开文件
    20 | 
    21 | 在打开的文件中添加如下:
    export GRADLE_HOME=/Applications/Android\ Studio.app/Contents/gradle/gradle-5.1.1
    22 | export PATH=${PATH}:${GRADLE_HOME}/bin
    23 | 
    24 | 注:因为链接中Android Studio.app中间有空格,路径中不能带有空格之类的特殊字符,所以需要在空格前加\进行转义。
    4. source .bash_profile
    25 | //使修改生效
    26 | 
    27 | 在terminal中输入gradle -v查看是否出现版本号及其他信息,出现如下信息,则成功:
    ------------------------------------------------------------
    28 | Gradle 5.1.1
    29 | ------------------------------------------------------------
    30 | Build time:   2019-01-10 23:05:02 UTC
    31 | Revision:     3c9abb645fb83932c44e8610642393ad62116807
    32 | 
    33 | Kotlin DSL:   1.1.1
    34 | Kotlin:       1.3.11
    35 | Groovy:       2.5.4
    36 | Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
    37 | JVM:          1.8.0_212 (Oracle Corporation 25.212-b10)
    38 | OS:           Mac OS X 10.14.2 x86_64
    39 | 
    40 |
  • 41 |
  • 如果没有出现上图这种情况,是因为gradlegradle.bat执行权限不够,需进行权限修改,执行如下命令5,进入到刚才的bin目录下,并使用命令6查看权限,如下:
    5. cd /Applications/Android\ Studio.app/Contents/gradle/gradle-5.1.1/bin
    42 | 6. ls -l
    43 | 7. -rwxr-xr-x  1 loganzy  admin  5297  6  3 11:27 gradle
    44 | 8. -rwxr-xr-x  1 loganzy  admin  2261  6  3 11:27 gradle.bat
    45 | 
    46 | -rwxr-xr-x若是没有x,说明没有可执行权限,因为我已经添加过权限,所以有可执行权限。依次执行如下命令,增加执行权限。
    9. chmod +x gradle
    47 | 10. chmod +x gradle.bat
    48 | 
    49 | 重新打开Mac终端或者在Android StudioTerminal中输入gradle -v,有输出说明配置成功。若是弹窗提示需要安装java的jdk才能使用此功能,那就需要安装并配置java环境变量。java环境变量配置完成后,运行gadle -v就能看到成功的配置信息了。
  • 50 |
51 |
52 |

Written with StackEdit.

53 |
54 | 55 | -------------------------------------------------------------------------------- /Mac os 配置java环境变量.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Mac OS 配置java的环境变量

7 |
    8 |
  1. 下载java的jdk安装包,前往Oracle下载
  2. 9 |
  3. 下载完成后进行安装,Mac会默认安装到:资料库/Java/JavaVirtualMachines/jdk1.8.0_212.jdk,找打jdk的根目录,并复制路径/Library/Java/JavaVirtualMachines/jdk1.8.0_212.jdk/Contents/Home
  4. 10 |
  5. 打开terminal,运行如下命令
    //切换到home顶级目录
    11 | 1.cd ~
    12 | //如果你是第一次配置环境变量,创建一个.bash_profile的隐藏配置文件
    13 | 2.touch .bash_profile
    14 | //如果是已经创建过了,直接打开并编辑
    15 | 3.open .bash_profile
    16 | 
    17 |
    18 |

    在新打开的界面中添加配置信息如下

    19 |
    20 |
    JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_212.jdk/Contents/Home
    21 | PATH=$JAVA_HOME/bin:$PATH:.
    22 | CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.
    23 | export JAVA_HOME
    24 | export PATH
    25 | export CLASSPATH
    26 | 
    27 |
    28 |

    保存编辑完的文件,在terminal中输入命令,使配置生效

    29 |
    30 |
    4. source .bash_profile
    31 | 
    32 |
    33 |

    之后运行java -verison 查看版本号和其他信息,成功信息如下:

    34 |
    35 |
    java version "1.8.0_212"
    36 | Java(TM) SE Runtime Environment (build 1.8.0_212-b10)
    37 | Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)
    38 | 
    39 |
  6. 40 |
41 |
42 |

Written with StackEdit.

43 |
44 | 45 | -------------------------------------------------------------------------------- /README.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSIsInRlbXBsYXRlSWQiOiJqZWt5bGxTaXRlIn0.publish: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoganZy/AndroidTotal/08f8ddbb142da71719af8503bc040b26e21d4b40/README.eyJicmFuY2giOiJtYXN0ZXIiLCJvd25lciI6IkxvZ2FuWnkiLCJwYXRoIjoiUkVBRE1FLm1kIiwicHJvdmlkZXJJZCI6ImdpdGh1YiIsInJlcG8iOiJBbmRyb2lkVG90YWwiLCJzdWIiOiIxNjQ1ODQwOSIsInRlbXBsYXRlSWQiOiJqZWt5bGxTaXRlIn0.publish -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |
7 |
8 |

AndroidTotal

9 |

一些Android项目开发中的知识累积

10 |
11 |

目录

12 |
13 | 37 | 38 | -------------------------------------------------------------------------------- /activity的生命周期再理解.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

Activity生命周期再学习

7 |

activity生命周期活动图

8 |

activity生命周期.jpg

9 |

生命周期中各个方法的含义和作用

10 |
    11 |
  • 12 |

    onCreate: create表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时Activity还在后台,不可见。所以动画不应该在这里初始化,因为看不到……

    13 |
  • 14 |
  • 15 |

    onStart: start表示启动,这是Activity生命周期的第二个方法。此时Activity已经可见了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。

    16 |
  • 17 |
  • 18 |

    onResume: resume表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且可见了。这个阶段可以打开独占设备,Activity已经是处在栈顶了。

    19 |
  • 20 |
  • 21 |

    onPause: pause表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并可见,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。

    22 |
  • 23 |
  • 24 |

    onStop: stop表示停止,此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。

    25 |
  • 26 |
  • 27 |

    onDestroy: destroy表示毁灭,这个阶段Activity被销毁,不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。

    28 |
  • 29 |
  • 30 |

    onRestart: restart表示重新开始,Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。

    31 |
  • 32 |
33 |

activity各个生命周期中方法的对比

34 |

onCreate和onStart之间有什么区别?

35 |
    36 |
  • (1) 可见与不可见的区别。前者不可见,后者可见。
  • 37 |
  • (2) 执行次数的区别。onCreate方法只在Activity创建时执行一次,而onStart方法在Activity的切换以及按Home键返回桌面再切回应用的过程中被多次调用。因此Bundle数据的恢复在onStart中进行比onCreate中执行更合适。
  • 38 |
  • (3) onCreate能做的事onStart其实都能做,但是onstart能做的事onCreate却未必适合做。如前文所说的,setContentView和资源初始化在两者都能做,然而想动画的初始化在onStart中做比较好。
  • 39 |
40 |

onCreate方法和onRestoreInstanceState方法都有Bundle参数,可以用来恢复数据,有什么区别?

41 |

因为onSaveInstanceState 不一定会被调用,所以onCreate()里的Bundle参数可能为空,如果使用onCreate()来恢复数据,一定要做非空判断。

42 |

而onRestoreInstanceState的Bundle参数一定不会是空值,因为它只有在上次activity被回收了才会调用。

43 |

而且onRestoreInstanceState是在onStart()之后被调用的。有时候我们需要onCreate()中做的一些初始化完成之后再恢复数据,用onRestoreInstanceState会比较方便。下面是官方文档对onRestoreInstanceState的说明:

44 |

This method is called after onStart() when the activity is being re-initialized from a previously saved state, given here in savedInstanceState. Most implementations will simply use onCreate(Bundle) to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation.

45 |

注意这个说明的最后一句是什么意思?
46 | to allow subclasses to decide whether to use your default implementation.

47 |

它是说,用onRestoreInstanceState方法恢复数据,你可以决定是否在方法里调用父类的onRestoreInstanceState方法,即是否调用super.onRestoreInstanceState(savedInstanceState);
48 | 而用onCreate()恢复数据,你必须调用super.onCreate(savedInstanceState); 否则运行会报错误。

49 |

onStart方法和onResume方法有什么区别?

50 |
    51 |
  • (1) 是否在前台。onStart方法中Activity可见但不在前台,不可交互,而在onResume中在前台。
  • 52 |
  • (2) 职责不同,onStart方法中主要还是进行初始化工作,而onResume方法,根据官方的建议,可以做开启动画和独占设备的操作。
  • 53 |
54 |

onPause方法和onStop方法有什么区别?

55 |
    56 |
  • (1) 是否可见。onPause时Activity可见,onStop时Activity不可见,但Activity对象还在内存中。
  • 57 |
  • (2) 在系统内存不足的时候可能不会执行onStop方法,因此程序状态的保存、独占设备和动画的关闭、以及一些数据的保存最好在onPause中进行,但要注意不能太耗时。
  • 58 |
59 |

onStop方法和onDestroy方法有什么区别?

60 |
    61 |
  • onStop阶段Activity还没有被销毁,对象还在内存中,此时可以通过切换Activity再次回到该Activity,而onDestroy阶段Acivity被销毁
  • 62 |
63 |

activity一些情景下生命周期的变化

64 |
65 |

情景一: FirstActivity(简称F)启动了SecondActivity(简称S),之后按back键回退到FirstActivity,整个流程是怎样的?

66 |
67 |

这里要分两种情况,第一种情况为,SecondActivity为透明状态的;第二种情况是正常的Activity。

68 |
    69 |
  • 70 |

    SecondActivity主题为透明或是非全屏的,从FirstActivity跳转到SecondActivity,因为SecondActivity为透明的,即FirstActivity在下层,仍然是可见,所以FirstActivity不会调用onStop()方法,只会调用onPause()方法, 按back退出SecondActivity,回到FirstActivity,整个流程生命周期变化如下:

    71 |
    72 |

    onCreate(F)–>onStart(F)–>onResume(F)–>跳转到SecondActivity(S)–>onPause(F)–>onCreate(S)–>onStart(S)–>onResume(S)–>B显示为透明,同时A在屏幕试可见的–>back返回F–>onPause(S)–>onResume(F)–>onStop(S)–>onDestroy(S)

    73 |
    74 |
  • 75 |
  • 76 |

    SecondActivity主题为非透明且全屏的,即我们一般使用的默认主题,从FirstActivity跳转到SecondActivity,SecondActivity显示可见,再按back键返回FirstActivity,整个流程生命周期变化如下:

    77 |
    78 |

    正确的流程: onCreate(F)–>onStart(F)–>onResume(F)–>跳转到SecondActivity(S)–>onPause(F)–>onWindowFocusChanged()–>onCreate(S)–>onStart(S)–>onResume(S)–>onWindowFocusChanged()–>B显示,同时A在屏幕上不可见–>onStop(F)–>back返回F–>onPause(S)–>onRestart(F)–>onStart(F)–>onResume(F)–>onStop(S)–>onDestroy(S)

    79 |
    80 |
  • 81 |
  • 82 |

    (1) 一个Activity或多或少会占有系统资源,而在官方的建议中,onPause方法将会释放掉很多系统资源,为切换Activity提供流畅性的保障,而不需要再等多两个阶段,这样做切换更快。不要在onPause()中做CPU密集型操作(如文件读写),否则可能会影响下一步的操作(Activity B的创建和显示)。

    83 |
  • 84 |
  • 85 |

    (2) 按照生命周期图的表示,如果用户在切换Activity的过程中再次切回原Activity,是在onPause方法后直接调用onResume方法的,这样比onPause→onStop→onRestart→onStart→onResume要快得多。

    86 |
  • 87 |
88 |
89 |

情景二: 横竖屏切换时Activity的生命周期变化?

90 |
91 |
    92 |
  • 如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍。整个过程生命周期的变化为:onCreate()—>onStart()—>onResume()—>旋转屏幕—>onPause()—>onSaveInstanceState()—>onStop()—>onDestory()—>onCreate()—>onStart()—>onRestoreInstanceState()—>onResume()
  • 93 |
  • 如果配置android:configChanges="orientation|keyboardHidden|screenSize">,此时activity的生命周期不会重建,会调用onConfigurationChanged(),生命周期变化如下:onCreate()—>onStart()—>onResume()—>旋转屏幕—>onConfigurationChanged()
  • 94 |
95 |
96 |

情景三: 一个Activity从创建到销毁,是不是一定会得到这些全部生命周期的回调?

97 |
98 |

并不一定,在两种情况下,Activity就不会得到完整的生命周期回调:

99 |
    100 |
  • 在onCreate()中调用Activity的finish()方法,这时候会直接进入onDestory(),而不会产生其他中间过程的回调,即onCreate()->onDestory()。这个我们可以应用到类似跳转功能的Activity中,在onCreate()进行逻辑处理后,打开目标Activity,然后finish()。这种情况下,用户不会看到这个Activity,这个Activity充当一个分发者的角色。
  • 101 |
  • 某个生命周期发生了crash,如在onCreate()中就发生了crash,就不会有下面生命周期的回调了。
  • 102 |
103 |
104 |

情景四: 弹窗、toast、键盘、下拉任务栏等情况,生命周期的变化。

105 |
106 |
    107 |
  • 弹窗:Activity在前台状态,弹出一个dialog,activity生命周期的状态无影响;activity的样式设置为dialog,弹窗弹出,activity调用onPause(),弹窗消失,activity调用onResume()。
  • 108 |
  • 键盘: Android下拉通知栏不会影响Activity的生命周期方法。
  • 109 |
  • 下拉任务栏:Android下拉通知栏不会影响Activity的生命周期方法。
  • 110 |
  • Toast:Android下拉通知栏不会影响Activity的生命周期方法。
  • 111 |
112 |

activity一些值得注意的方法的生命周期

113 |

onSaveInstanceState()和onRestoreInstanceState()
114 | onRestoreInstanceState()调用的时机

115 |

只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。这个方法的主要作用,是恢复在onSaveInstanceState()中保存的状态。它的调用时机在onStart()和onPostCreate()之间。

116 |

onSaveInstanceState()调用的时机

117 |

当activity有可能被系统回收的情况下,注意是有可能,如果是已经确定会被销毁,比如用户按下了返回键,或者调用了finish()方法销毁activity,则onSaveInstanceState不会被调用。总结下,onSaveInstanceState(Bundle outState)会在以下情况被调用:

118 |
119 |

1、当用户按下HOME键时。
120 | 2、从最近应用中选择运行其他的程序时。
121 | 3、按下电源按键(关闭屏幕显示)时。
122 | 4、从当前activity启动一个新的activity时。
123 | 5、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。

124 |
125 |

官方文档中介绍–这个方法的调用时机,在不同的系统中不同,从Android P开始,它是在onStop之后调用的,在之前的系统中,则是在onStop之前调用的。但是是否发生在onPause前后,则看具体情况。(目前为止,我遇到的都是在onPause()之后调用的)。

126 |
127 |

事例一:activity的android:configChanges属性没有配置,发生屏幕方向切换时,activity生命周期如下:
128 | onPause()-> onSaveInstanceState() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()
129 | 事例二:按HOME键返回桌面,又马上点击应用图标回到原来页面时,activity生命周期如下:
130 | onPause() -> onSaveInstanceState() -> onStop() -> onRestart() -> onStart()-> onResume()

131 |
132 |

从上面的两个事例中,我们可以看出onSaveInstanceState()和onRestoreInstanceState()不一定是成对被调用的,即onRestoreInstanceState()被调用了,则onSaveInstanceState()一定被调用过;反之则不然。

133 |

这两个生命周期只是在Activity被异常终止时成对出现(6.0以上在动态权限申请时也会出现)。Activity的异常终止大概有以下两种情况:

134 |
    135 |
  • 系统配置发生改变
  • 136 |
  • 内存不足,优先级低的Activity资源被回收
  • 137 |
138 |

其中系统配置改变是指系统需要重新加载一些资源以适应某些配置的改变,如旋转屏幕、语言或地区改变、键盘改变等。当发生这种改变的时候,系统会重新创建这个Activity,在销毁和重新创建的过程中,这两个生命周期将会被回调。在这种情况下,系统会自动保存当前view的一些状态,然后在重新创建时恢复数据。当然,我们也可以通过onSaveInstanceState()做一些自定义的保存。过滤不想监听的配置变化

139 |

onSaveInstanceState()方法的默认实现

140 |

如果我们没有覆写onSaveInstanceState()方法, 此方法的默认实现会自动保存activity中的某些状态数据, 比如activity中各种UI控件的状态.。android应用框架中定义的几乎所有UI控件都恰当的实现了onSaveInstanceState()方法,因此当activity被摧毁和重建时, 这些UI控件会自动保存和恢复状态数据. 比如EditText控件会自动保存和恢复输入的数据,而CheckBox控件会自动保存和恢复选中状态.开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可), 剩余的事情就可以自动完成了.如果没有为控件指定ID, 则这个控件就不会进行自动的数据保存和恢复操作。

141 |

由上所述, 如果我们需要覆写onSaveInstanceState()方法, 一般会在第一行代码中调用该方法的默认实现:super.onSaveInstanceState(outState)。

142 |

是否需要重写onSaveInstanceState()方法

143 |

既然该方法的默认实现可以自动的保存UI控件的状态数据, 那什么时候需要覆写该方法呢?

144 |

如果需要保存额外的数据时, 就需要覆写onSaveInstanceState()方法。大家需要注意的是:onSaveInstanceState()方法只适合保存瞬态数据, 比如UI控件的状态, 成员变量的值等,而不应该用来保存持久化数据,持久化数据应该当用户离开当前的 activity时,在 onPause() 中保存(比如将数据保存到数据库或文件中)。说到这里,还要说一点的就是在onPause()中不适合用来保存比较费时的数据,所以这点要理解。

145 |

由于onSaveInstanceState()方法方法不一定会被调用, 因此不适合在该方法中保存持久化数据, 例如向数据库中插入记录等. 保存持久化数据的操作应该放在onPause()中。若是永久性值,则在onPause()中保存;若大量,则另开线程吧,别阻塞UI线程。

146 |

onPostCreate()
147 | 这个回调发生在onStart()之后,onResume()之前,此时onCreate()已经执行完了,因此我们可以在其中做一些依赖于onCreate()中状态的操作;有时还会在这个函数里面做一些优先级稍低的事情,比如侧边栏的初始化,因为侧边栏的特性是需要进一步的用户交互来展现,因此我们可以减缓它初始化的优先级。对onPostCreate()的正确使用,可以使代码层次化更清晰,结构更合理。

148 |

onLowMemory()
149 | 该生命周期出现在当系统内存不足的,低优先级的Activity的资源即将被回收时收到的回调,在这个回调中,我们可以做一些数据持久化工作、保存一些关键状态。在执行完这个生命周期后,系统将立即执行gc。

150 |

activity生命周期中的一些问题

151 |
152 |

setContentView如果放在onStart或者onResume中,会有什么问题吗?

153 |
154 |

从官方文档来看,也只是说应该把setContentView放在onCreate中,并没有说必须放在这里,所以应该也不会有显示以及调用的问题吧。还是跑一下程序看看,分别将setContentView以及设置点击事件放在onStart以及onResume中,跑了下程序,没有出现显示问题。但是这个仅仅是显示上的问题,会不会存在效率的问题呢?我们来打印一下时间,以调用onCreate到onWindowFocusChanged之间的时间,作为Activity加载时间,来进行对比

155 |
SecondActivity: =====SecondActivity=====Load Time:56
156 | SecondActivity: =====SecondActivity=====Load Time:57
157 | SecondActivity: =====SecondActivity=====Load Time:57
158 | 
159 |

三次时间几乎一样的,也就是说,如果只是单丛初次启动的效率来说,在三个地方去进行setContentView是没有任何差别的。但是为甚么官方说应该放在onCreate里面去处理了,这是因为,onCreate在正常状况下,只会被调用一次,而onStart以及onResume都会被调用多次,放在这里面去做的话,在onResume的过程,会增加额外的耗时。

160 |

另外,由于onRestoreInstanceState是在onStart之后才调用的,如果将setContentView放在onResume的话,可能会产生问题。

161 |
162 |

onPause中可以保存状态,为什么还要onSaveInstanceState,onCreate中有恢复机制,为什么还需要onRestoreInstanceState?

163 |
164 |

首先,onSaveInstanceState以及onRestoreInstanceState是Android进行UI状态保存与恢复的一套单独的机制。说是单独的机制,是因为每个view里面,都会有onSaveInstanceState去进行一些默认的状态保存操作,常规状态下不需要用户去干预,比方说编辑框中输入的文本信息,这样做为开发者省了很多事。

165 |

根据官方文档来看,onPause主要的作用是停止动画以及一些耗CPU的操作,可以用于保存状态。onSaveInstanceState主要作用是对UI进行状态保存。两者的侧重点不同,onPause也可以保存UI的状态,但是,onPause设计的主要目的是和onStart配对,停止耗时操作。

166 |

onCreate中也可以恢复状态,但是onRestoreInstanceState的触发时间滞后于onCreate,在onStart之后执行,它设计的目的是用来恢复onSaveInstanceState中保存的状态。

167 |

onPause以及onCreate可以干这些事情,但是它们当初不是设计出来,专门干这个事情的。

168 |
169 |

如何判断一个Activity真正可见?

170 |
171 |

对于这个问题,其实没有严格的说法,onResume以及onWindowFocusChanged中都可以做判断,但是官方文档上说,onWindowFocusChanged是Activity对用户是否可见最好的指示器。

172 |
173 |

Written with StackEdit.

174 |
175 | 176 | -------------------------------------------------------------------------------- /android 进程保活机制.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

保活手段

7 |

当前业界的Android进程保活手段主要分为** 黑、白、灰 **三种,其大致的实现思路如下:

8 |

黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)

9 |

白色保活:启动前台Service

10 |

灰色保活:利用系统的漏洞启动前台Service

11 |

黑色保活

12 |

所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:

13 |

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app

14 |

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3

15 |

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)

16 |

没错,我们的Android手机就是一步一步的被上面这些场景给拖卡机的。

17 |

针对场景1,估计Google已经开始意识到这些问题,所以在最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍视频),CONNECTIVITY_ACTION(网络切换)等三种广播,无疑给了很多app沉重的打击。我猜他们的心情是下面这样的

18 |

19 |

而开机广播的话,记得有一些定制ROM的厂商早已经将其去掉。

20 |

针对场景2场景3,因为调用SDK唤醒app进程属于正常行为,此处不讨论。但是在借助LBE分析app之间的唤醒路径的时候,发现了两个问题:

21 |
    22 |
  1. 很多推送SDK也存在唤醒app的功能
  2. 23 |
  3. app之间的唤醒路径真是多,且错综复杂
  4. 24 |
25 |

我把自己使用的手机测试结果给大家围观一下(我的手机是小米4C,刷了原生的Android5.1系统,且已经获得Root权限才能查看这些唤醒路径

26 |

27 |

15组相互唤醒路径

28 |

29 |

全部唤醒路径

30 |

我们直接点开 简书 的唤醒路径进行查看

31 |

32 |

简书唤醒路径

33 |

可以看到以上3条唤醒路径,但是涵盖的唤醒应用总数却达到了23+43+28款,数目真心惊人。请注意,这只是我手机上一款app的唤醒路径而已,到了这里是不是有点细思极恐。

34 |

当然,这里依然存在一个疑问,就是LBE分析这些唤醒路径和互相唤醒的应用是基于什么思路,我们不得而知。所以我们也无法确定其分析结果是否准确,如果有LBE的童鞋看到此文章,不知可否告知一下思路呢?但是,手机打开一个app就唤醒一大批,我自己可是亲身体验到这种酸爽的…

35 |

36 |

白色保活

37 |

白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:

38 |

39 |

灰色保活

40 |

灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:

41 |
    42 |
  • 思路一:API < 18,启动前台Service时直接传入new Notification();
  • 43 |
  • 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理;
  • 44 |
45 |

 46 | public class GrayService extends Service {
 47 | 
 48 |     private final static int GRAY_SERVICE_ID = 1001;
 49 | 
 50 |     @Override
 51 |     public int onStartCommand(Intent intent, int flags, int startId) {
 52 |         if (Build.VERSION.SDK_INT < 18) {
 53 |             startForeground(GRAY_SERVICE_ID, new Notification());//API < 18 ,此方法能有效隐藏Notification上的图标
 54 |         } else {
 55 |             Intent innerIntent = new Intent(this, GrayInnerService.class);
 56 |             startService(innerIntent);
 57 |             startForeground(GRAY_SERVICE_ID, new Notification());
 58 |         }
 59 | 
 60 |         return super.onStartCommand(intent, flags, startId);
 61 |     }
 62 | 
 63 |     ...
 64 |     ...
 65 | 
 66 |     /**
 67 |      * 给 API >= 18 的平台上用的灰色保活手段
 68 |      */
 69 |     public static class GrayInnerService extends Service {
 70 | 
 71 |         @Override
 72 |         public int onStartCommand(Intent intent, int flags, int startId) {
 73 |             startForeground(GRAY_SERVICE_ID, new Notification());
 74 |             stopForeground(true);
 75 |             stopSelf();
 76 |             return super.onStartCommand(intent, flags, startId);
 77 |         }
 78 | 
 79 |     }
 80 | }
 81 | 
 82 | 
 83 | 
84 |

代码大致就是这样,能让你神不知鬼不觉的启动着一个前台Service。其实市面上很多app都用着这种灰色保活的手段,什么?你不信?好吧,我们来验证一下。流程很简单,打开一个app,看下系统通知栏有没有一个 Notification,如果没有,我们就进入手机的adb shell模式,然后输入下面的shell命令

85 |
dumpsys activity services PackageName
 86 | 
 87 | 
88 |

打印出指定包名的所有进程中的Service信息,看下有没有 isForeground=true 的关键信息。如果通知栏没有看到属于app的 Notification 且又看到 isForeground=true 则说明了,此app利用了这种灰色保活的手段。

89 |

下面分别是我手机上微信、qq、支付宝、陌陌的测试结果,大家有兴趣也可以自己验证一下。

90 |

91 |

微信

92 |

93 |

手Q

94 |

95 |

支付宝

96 |

97 |

陌陌

98 |

其实Google察觉到了此漏洞的存在,并逐步进行封堵。这就是为什么这种保活方式分 API >= 18 和 API < 18 两种情况,从Android5.0的ServiceRecord类的postNotification函数源代码中可以看到这样的一行注释

99 |

100 |

当某一天 API >= 18 的方案也失效的时候,我们就又要另谋出路了。需要注意的是,**使用灰色保活并不代表着你的Service就永生不死了,只能说是提高了进程的优先级。如果你的app进程占用了大量的内存,按照回收进程的策略,同样会干掉你的app。**感兴趣于灰色保活是如何利用系统漏洞不显示 Notification 的童鞋,可以研究一下系统的 ServiceRecord、NotificationManagerService 等相关源代码,因为不是本文的重点,所以不做详述。

101 |

唠叨的分割线

102 |

到这里基本就介绍完了** 黑、白、灰 **三种实现方式,仅仅从代码层面去讲保活是不够的,我希望能够通过系统的进程回收机制来理解保活,这样能够让我们更好的避免踩到进程被杀的坑。

103 |

进程回收机制

104 |

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 **OOM Killer(Out-Of-Memory killer)**机制诞生。

105 |

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:

106 |
    107 |
  • 进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
  • 108 |
  • 普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
  • 109 |
110 |

那么我们如何查看进程的oom_adj值呢,需要用到下面的两个shell命令

111 |
ps | grep PackageName //获取你指定的进程信息
112 | 
113 | 
114 |

115 |

这里是以我写的demo代码为例子,红色圈中部分别为下面三个进程的ID

116 |

UI进程:com.clock.daemon
117 | 普通后台进程:com.clock.daemon:bg
118 | 灰色保活进程:com.clock.daemon:gray

119 |

当然,这些进程的id也可以通过AndroidStudio获得

120 |

121 |

接着我们来再来获取三个进程的oom_adj

122 |
cat /proc/进程ID/oom_adj
123 | 
124 | 
125 |

126 |

从上图可以看到UI进程和灰色保活Service进程的oom_adj=0,而普通后台进程oom_adj=15。到这里估计你也能明白,**为什么普通的后台进程容易被回收,而前台进程则不容易被回收了吧。**但明白这个还不够,接着看下图

127 |

128 |

上面是我把app切换到后台,再进行一次oom_adj的检验,你会发现UI进程的值从0变成了6,而灰色保活的Service进程则从0变成了1。这里可以观察到,app退到后台时,其所有的进程优先级都会降低。但是UI进程是降低最为明显的,因为它占用的内存资源最多,系统内存不足的时候肯定优先杀这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的

129 |

从Android官方文档中,我们也能看到优先级从高到低列出了这些不同类型的进程:Foreground processVisible processService processBackground processEmpty process。而这些进程的oom_adj分别是多少,又是如何挂钩起来的呢?推荐大家阅读下面这篇文章:

130 |

http://www.cnblogs.com/angeldevil/archive/2013/05/21/3090872.html

131 |

总结(文末有福利)

132 |

絮絮叨叨写完了这么多,最后来做个小小的总结。回归到开篇提到QQ进程不死的问题,我也曾认为存在这样一种技术。可惜我把手机root后,杀掉QQ进程之后就再也起不来了。有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。

133 |

所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!

134 |

补充更新 (2016-04-20)

135 |

有童鞋问,在华为的机子上发现微信和手Q的UI进程退到后台,oom_adj的值一点都没有变,是不是有什么黑科技在其中。为此,我稍稍验证了一下,验证方式就是把demo工程的包名改成手机QQ的,编译运行在华为的机子上,发现我的进程怎么杀也都是不死的,退到后台oom_adj的值同样不发生变化,而恢复原来的包名就不行了。所以,你懂的,手Q就在华为机子的白名单中。

136 |
137 |

第二种方案

138 |

目前市面上的应用,貌似除了微信和手Q都会比较担心被用户或者系统(厂商)杀死问题。本文对 Android 进程拉活进行一个总结。

139 |

Android 进程拉活包括两个层面:

140 |
    141 |
  1. 142 |

    提供进程优先级,降低进程被杀死的概率

    143 |
  2. 144 |
  3. 145 |

    在进程被杀死后,进行拉活

    146 |
  4. 147 |
148 |

本文下面就从这两个方面做一下总结。

149 |

1. 进程的优先级

150 |

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要清除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是清除重要性稍低一级的进程,依此类推,以回收系统资源。

151 |

进程的重要性,划分5级:

152 |
    153 |
  1. 154 |

    前台进程 (Foreground process)

    155 |
  2. 156 |
  3. 157 |

    可见进程 (Visible process)

    158 |
  4. 159 |
  5. 160 |

    服务进程 (Service process)

    161 |
  6. 162 |
  7. 163 |

    后台进程 (Background process)

    164 |
  8. 165 |
  9. 166 |

    空进程 (Empty process)

    167 |
  10. 168 |
169 |

170 |

前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来阐述每种级别的进程

171 |

1.1 前台进程 —— Foreground process

172 |

用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

173 |
    174 |
  1. 175 |

    拥有用户正在交互的 Activity(已调用 onResume()

    176 |
  2. 177 |
  3. 178 |

    拥有某个 Service,后者绑定到用户正在交互的 Activity

    179 |
  4. 180 |
  5. 181 |

    拥有正在“前台”运行的 Service(服务已调用 startForeground()

    182 |
  6. 183 |
  7. 184 |

    拥有正执行一个生命周期回调的 Service(onCreate()onStart()onDestroy()

    185 |
  8. 186 |
  9. 187 |

    拥有正执行其 onReceive() 方法的 BroadcastReceiver

    188 |
  10. 189 |
190 |

1.2 可见进程 —— Visible process

191 |

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

192 |
    193 |
  1. 194 |

    拥有不在前台、但仍对用户可见的 Activity(已调用 onPause()

    195 |
  2. 196 |
  3. 197 |

    拥有绑定到可见(或前台)Activity 的 Service

    198 |
  4. 199 |
200 |

1.3 服务进程 —— Service process

201 |

尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

202 |

正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程。

203 |

1.4 后台进程 —— Background process

204 |

后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

205 |

对用户不可见的 Activity 的进程(已调用 Activity的onStop() 方法)

206 |

1.5 空进程 —— Empty process

207 |

保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

208 |

不含任何活动应用组件的进程

209 |
210 |

详情参见:http://developer.android.com/…

211 |
212 |

2. Android 进程回收策略

213 |

Android 中对于内存的回收,主要依靠 Lowmemorykiller 来完成,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制。

214 |

关于 OOM_ADJ 的说明如下:

215 |

216 |

其中红色部分代表比较容易被杀死的 Android 进程(OOM_ADJ>=4),绿色部分表示不容易被杀死的 Android 进程,其他表示非 Android 进程(纯 Linux 进程)。在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。

217 |

Android 手机中进程被杀死可能有如下情况:

218 |

219 |

综上,可以得出减少进程被杀死概率无非就是想办法提高进程优先级,减少进程在内存不足等情况下被杀死的概率。

220 |

3. 提升进程优先级的方案

221 |

3.1 利用 Activity 提升权限

222 |

方案设计思想:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。

223 |

通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。

224 |

方案适用范围:

225 |
    226 |
  • 227 |

    适用场景:本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题。

    228 |
  • 229 |
  • 230 |

    适用版本:适用于所有的 Android 版本。

    231 |
  • 232 |
233 |

方案具体实现:首先定义 Activity,并设置 Activity 的大小为1像素:

234 |

235 |

其次,从 AndroidManifest 中通过如下属性,排除 Activity 在 RecentTask 中的显示:

236 |

237 |

最后,控制 Activity 为透明:

238 |

239 |

Activity 启动与销毁时机的控制:

240 |

241 |

3.2 利用 Notification 提升权限

242 |

方案设计思想:Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。

243 |

方案实现挑战:从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一起的。

244 |

对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。

245 |

方案挑战应对措施:通过实现一个内部 Service,在 LiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。

246 |

方案适用范围:适用于目前已知所有版本。

247 |

方案具体实现:

248 |

249 |

250 |

4. 进程死后拉活的方案

251 |

4.1 利用系统广播拉活

252 |

方案设计思想:在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。

253 |

常用的用于拉活的广播事件包括:

254 |

255 |

方案适用范围:适用于全部 Android 平台。但存在如下几个缺点:

256 |
    257 |
  1. 258 |

    广播接收器被管理软件、系统软件通过“自启管理”等功能禁用的场景无法接收到广播,从而无法自启。

    259 |
  2. 260 |
  3. 261 |

    系统广播事件不可控,只能保证发生事件时拉活进程,但无法保证进程挂掉后立即拉活。

    262 |
  4. 263 |
264 |

因此,该方案主要作为备用手段。

265 |

4.2 利用第三方应用广播拉活

266 |

方案设计思想:该方案总的设计思想与接收系统广播类似,不同的是该方案为接收第三方 Top 应用广播。

267 |

通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。

268 |

方案适用范围:该方案的有效程度除与系统广播一样的因素外,主要受如下因素限制:

269 |
    270 |
  1. 271 |

    反编译分析过的第三方应用的多少

    272 |
  2. 273 |
  3. 274 |

    第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。

    275 |
  4. 276 |
277 |

这些因素都影响了拉活的效果。

278 |

4.3 利用系统Service机制拉活

279 |

方案设计思想:将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:

280 |

281 |

方案适用范围:如下两种情况无法拉活

282 |
    283 |
  1. 284 |

    Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。

    285 |
  2. 286 |
  3. 287 |

    进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。

    288 |
  4. 289 |
290 |

4.4 利用Native进程拉活

291 |

方案设计思想:

292 |
    293 |
  • 294 |

    主要思想:利用 Linux 中的 fork 机制创建 Native 进程,在 Native 进程中监控主进程的存活,当主进程挂掉后,在 Native 进程中立即对主进程进行拉活。

    295 |
  • 296 |
  • 297 |

    主要原理:在 Android 中所有进程和系统组件的生命周期受 ActivityManagerService 的统一管理。而且,通过 Linux 的 fork 机制创建的进程为纯 Linux 进程,其生命周期不受 Android 的管理。

    298 |
  • 299 |
300 |

方案实现挑战:

301 |
    302 |
  • 挑战一:在 Native 进程中如何感知主进程死亡。
  • 303 |
304 |

要在 Native 进程中感知主进程是否存活有两种实现方式:

305 |
    306 |
  1. 307 |

    在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,档主进程不存活时进行拉活。该方案的很大缺点是不停的轮询执行判断逻辑,非常耗电。

    308 |
  2. 309 |
  3. 310 |

    在主进程中创建一个监控文件,并且在主进程中持有文件锁。在拉活进程启动后申请文件锁将会被堵塞,一旦可以成功获取到锁,说明主进程挂掉,即可进行拉活。由于 Android 中的应用都运行于虚拟机之上,Java 层的文件锁与 Linux 层的文件锁是不同的,要实现该功能需要封装 Linux 层的文件锁供上层调用。

    311 |
  4. 312 |
313 |

封装 Linux 文件锁的代码如下:

314 |

315 |

Native 层中堵塞申请文件锁的部分代码:

316 |

317 |
    318 |
  • 挑战二:在 Native 进程中如何拉活主进程。
  • 319 |
320 |

通过 Native 进程拉活主进程的部分代码如下,即通过 am 命令进行拉活。通过指定“–include-stopped-packages”参数来拉活主进程处于 forestop 状态的情况。

321 |

322 |
    323 |
  • 挑战三:如何保证 Native 进程的唯一。
  • 324 |
325 |

从可扩展性和进程唯一等多方面考虑,将 Native 进程设计层 C/S 结构模式,主进程与 Native 进程通过 Localsocket 进行通信。在Native进程中利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题。

326 |

327 |

方案适用范围:该方案主要适用于 Android5.0 以下版本手机。

328 |

该方案不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。

329 |

对于 Android5.0 以上手机,系统虽然会将native进程内的所有进程都杀死,这里其实就是系统“依次”杀死进程时间与拉活逻辑执行时间赛跑的问题,如果可以跑的比系统逻辑快,依然可以有效拉起。记得网上有人做过实验,该结论是成立的,在某些 Android 5.0 以上机型有效。

330 |

4.5 利用 JobScheduler 机制拉活

331 |

方案设计思想:Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。

332 |

在本项目中,我对 JobScheduler 进行了进一步封装,兼容 Android5.0 以下版本。封装后 JobScheduler 接口的使用如下:

333 |

334 |

335 |

方案适用范围:该方案主要适用于 Android5.0 以上版本手机。

336 |

该方案在 Android5.0 以上版本中不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。

337 |

仅在小米手机可能会出现有时无法拉活的问题。

338 |

4.6 利用账号同步机制拉活

339 |

方案设计思想:Android 系统的账号同步机制会定期同步账号进行,该方案目的在于利用同步机制进行进程的拉活。添加账号和设置同步周期的代码如下:

340 |

341 |

该方案需要在 AndroidManifest 中定义账号授权与同步服务。

342 |

343 |

方案适用范围:该方案适用于所有的 Android 版本,包括被 forestop 掉的进程也可以进行拉活。

344 |

最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。

345 |

5. 其他有效拉活方案

346 |

经研究发现还有其他一些系统拉活措施可以使用,但在使用时需要用户授权,用户感知比较强烈。

347 |

这些方案包括:

348 |
    349 |
  1. 350 |

    利用系统通知管理权限进行拉活

    351 |
  2. 352 |
  3. 353 |

    利用辅助功能拉活,将应用加入厂商或管理软件白名单。

    354 |
  4. 355 |
356 |

这些方案需要结合具体产品特性来搞。

357 |

上面所有解释这些方案都是考虑的无 Root 的情况。

358 |

其他还有一些技术之外的措施,比如说应用内 Push 通道的选择:

359 |
    360 |
  1. 361 |

    国外版应用:接入 Google 的 GCM。

    362 |
  2. 363 |
  3. 364 |

    国内版应用:根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。

    365 |
  4. 366 |
367 |

Written with StackEdit.

368 | 369 | -------------------------------------------------------------------------------- /git使用.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

git的使用与错误

7 |
    8 |
  1. 9 |

    使用Git pull文件时,出现"error: RPC failed; curl 18 transfer closed with outstanding read data remaining
    10 | 错误信息如下:

    11 |
    error: RPC failed; curl 18 transfer closed with outstanding read data remaining
    12 | fatal: The remote end hung up unexpectedly
    13 | fatal: early EOF
    14 | fatal: index-pack failed
    15 | 
    16 |

    1.缓存区溢出curl的postBuffer的默认值太小,需要增加缓存

    17 |

    使用git命令增大缓存(单位是b,524288000B也就500M左右)

    18 |

    git config --global http.postBuffer 524288000

    19 |

    使用git config --list查看是否生效

    20 |

    此时重新克隆即可

    21 |

    2.网络下载速度缓慢

    22 |

    修改下载速度

    23 |
    git config --global http.lowSpeedLimit 0 git config --global http.lowSpeedTime 999999
    24 | 
    25 |

    3.以上两种方式依旧无法clone下,尝试以浅层clone,然后更新远程库到本地

    26 |
    git clone --depth=1 http://xxx.git
    27 | git fetch --unshallow
    28 | 
    29 |
  2. 30 |
31 |
32 |

Written with StackEdit.

33 |
34 | 35 | -------------------------------------------------------------------------------- /webview上传文件与h5交互.md: -------------------------------------------------------------------------------- 1 | ## WebChromeClient之onShowFileChooser或openFileChooser使用及注意事项 2 | `Android `开发使用WebView控件加载包含表单的H5网页,点击上传文件按钮,弹出对话框,选择从相册获取照片、拍照或打开手机文件管理器,从Android手机选取一张图片或一个文件,然后通过ValueCallback接口传递,在WebView加载的H5网页显示。这里有一个问题,点击“取消”或返回按钮,无法重复回调onShowFileChooser或openFileChooser方法。 3 | 控制台打印:**Attempted to finish an input event but the input event receiver has already been disposed** 4 | 5 | /** 6 | * Tell the client to show a file chooser. 7 | * This is called to handle HTML forms with 'file' input type, in response to the 8 | * user pressing the "Select File" button. 9 | * To cancel the request, call filePathCallback.onReceiveValue(null) and 10 | * return true. 11 | * @param webView The WebView instance that is initiating the request. 12 | * @param filePathCallback Invoke this callback to supply the list of paths to files to upload, 13 | * or NULL to cancel. Must only be called if the 14 | * showFileChooser implementations returns true. 15 | * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be 16 | * used with it. 17 | * @return true if filePathCallback will be invoked, false to use default handling. 18 | * @see FileChooserParams 19 | */ 20 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, 21 | FileChooserParams fileChooserParams) { 22 | return false; 23 | } 24 | 25 | 该方法的作用,告诉当前APP,打开一个文件选择器,比如:打开相册、启动拍照或打开本地文件管理器,实际上更好的理解,WebView加载包含上传文件的表单按钮,HTML定义了input标签,同时input的type类型为file,手指点击该按钮,回调onShowFileChooser这个方法,在这个重写的方法里面打开相册、启动照片或打开本地文件管理器,甚至做其他任何的逻辑处理,点击一次回调一次的前提是请求被取消,而取消该请求回调的方法:给ValueCallback接口的onReceiveValue抽象方法传入null,同时onShowFileChooser方法返回true; 26 | 27 | ValueCallback的抽象方法被回调onShowFileChooser方法返回true;反之返回false;再来看一下openFileChooser的源码,如下: 28 | 29 | /** 30 | * Tell the client to open a file chooser. 31 | * @param uploadFile A ValueCallback to set the URI of the file to upload. 32 | * onReceiveValue must be called to wake up the thread.a 33 | * @param acceptType The value of the 'accept' attribute of the input tag 34 | * associated with this file picker. 35 | * @param capture The value of the 'capture' attribute of the input tag 36 | * associated with this file picker. 37 | * 38 | * @deprecated Use {@link #showFileChooser} instead. 39 | * @hide This method was not published in any SDK version. 40 | */ 41 | @SystemApi 42 | @Deprecated 43 | public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) { 44 | uploadFile.onReceiveValue(null); 45 | } 46 | 在所有发布的SDK版本中,openFileChooser是一个隐藏的方法,使用onShowFileChooser代替,但是最好同时重写showFileChooser和openFileChooser方法,**Android 4.4.X以上**的系统回调onShowFileChooser方法,**低于或等于Android 4.4.X**的系统回调openFileChooser方法,只重写onShowFileChooser或openFileChooser造成在有的系统可以正常回调,在有的系统点击没有反应。 47 | 48 | 仔细分析onShowFileChooser和openFileChooser回调方法,这两个方法之间的区别, 49 | 50 | 第一个区别:前者ValueCallback接口回传一个Uri数组,后者回传一个Uri对象,在onActivityResult回调方法中调用ValueCallback接口方法onReceiveValue传入参数特别注意; 51 | 52 | /** 53 | *回调onShowFileChooser方法,onReceiveValue传入Uri对象数组 54 | */ 55 | mFilePathCallback.onReceiveValue(new Uri[]{uri}); 56 | 57 | /** 58 | *回调openFileChooser方法,onReceiveValue传入一个Uri对象 59 | */ 60 | mFilePathCallback4.onReceiveValue(uri); 61 | 62 | H5表单写入两个上传文件的按钮,点击其中一个从底部弹出对话框,选择相册文件或拍照,点击“取消”按钮,再次点击“上传文件”按钮能够再次回调onShowFileChooser或openFileChooser方法。 63 | 64 | 在之前的理解中,误解onShowFileChooser或openFileChooser只能打开相册或启动相机拍照,其实不仅仅是这样,onShowFileChooser或openFileChooser既然是一个回调的方法,可以重复执行各种逻辑代码,比如:启动另一个Activity、弹窗对话框、录制视频或录音等 65 | 66 | 在上面的例子中,执行弹窗操作,将弹窗的处理代码放置onShowFileChooser或openFileChooser方法体,如下: 67 | 68 | @Override 69 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { 70 | super.onShowFileChooser(webView, filePathCallback, fileChooserParams); 71 | popupDialog(); 72 | PickPhotoUtil.mFilePathCallback = filePathCallback; 73 | //返回true,如果filePathCallback被调用;返回false,如果忽略处理 74 | return true; 75 | } 76 | 77 | 或 78 | 79 | public void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { 80 | popupDialog(); 81 | String title = acceptType; 82 | PickPhotoUtil.mFilePathCallback4 = filePathCallback; 83 | } 84 | 85 | 点击弹窗取消按钮、点击打开相册取消操作或取消拍照,可能无法再次回调onShowFileChooser或openFileChooser方法,如果你没有在点击弹窗取消方法中或onActivityResult回调方法resultCode==RESULT_CANCELED处理,再次点击上传按钮,打印出log: 86 | 87 | **Attempted to finish an input event but the input event receiver has already been disposed** 88 | 89 | 同时,点击没有效果 90 | 91 | /** 92 | * 弹窗,启动拍照或打开相册 93 | */ 94 | public void popupDialog() { 95 | ActionSheetDialog actionSheetDialog= new ActionSheetDialog(activity).builder() 96 | .setCancelable(false) 97 | .setCanceledOnTouchOutside(false) 98 | .addSheetItem("手机拍照", ActionSheetDialog.SheetItemColor.Blue, 99 | new ActionSheetDialog.OnSheetItemClickListener() { 100 | @Override 101 | public void onClick(int which) { 102 | goToTakePhoto(); 103 | } 104 | }) 105 | .addSheetItem("手机相册", ActionSheetDialog.SheetItemColor.Blue, 106 | new ActionSheetDialog.OnSheetItemClickListener() { 107 | @Override 108 | public void onClick(int which) { 109 | goForPicFile(); 110 | } 111 | }); 112 | actionSheetDialog.show(); 113 | /** 114 | * 设置点击“取消”按钮监听,目的取消mFilePathCallback回调,可以重复调起弹窗 115 | */ 116 | actionSheetDialog.setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | cancelFilePathCallback(); 120 | } 121 | }); 122 | } 123 | 124 | /** 125 | *取消mFilePathCallback回调 126 | */ 127 | private void cancelFilePathCallback() { 128 | if (PickPhotoUtil.mFilePathCallback4 != null) { 129 | PickPhotoUtil.mFilePathCallback4.onReceiveValue(null); 130 | PickPhotoUtil.mFilePathCallback4 = null; 131 | } else if (PickPhotoUtil.mFilePathCallback != null) { 132 | PickPhotoUtil.mFilePathCallback.onReceiveValue(null); 133 | PickPhotoUtil.mFilePathCallback = null; 134 | } 135 | } 136 | 137 | 在不期待回调mFilePathCallback的onReceiveValue方法时,调用**cancelFilePathCallback()**,解决点击上传按钮无法重复回调的问题。 138 | -------------------------------------------------------------------------------- /实际面试的问答补充.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

1.webview的优化
7 | 2.Handler的sendMessage()和post(Runable r)的区别?

8 |
public final boolean sendMessage(Message msg){ 
  9 | 	return sendEmptyMessageDelayed(msg,  0);  
 10 | }
 11 | public final boolean post(Runnable r){
 12 | 	//getPostMessage方法是两种发送消息的不同之处
 13 |     return sendMessageDelayed(getPostMessage(r), 0);
 14 | }
 15 | 
16 |

方法只有一句,内部实现和普通的sendMessage是一样的,但是只有一点不同,那就是 getPostMessage 这个方法:

17 |
private static Message getPostMessage(Runnable r) {
 18 |         Message m = Message.obtain();
 19 |         m.callback = r;
 20 |         return m;
 21 | }
 22 | 
23 |

这里将runable对象传递给message的callback对象了,继续向下看

24 |
public void dispatchMessage(Message msg) {
 25 | 	if (msg.callback != null) {
 26 | 		handleCallback(msg);
 27 | 	} else {
 28 | 		if (mCallback != null) {
 29 | 			if (mCallback.handleMessage(msg)) {
 30 | 				return;
 31 | 			}
 32 | 		}
 33 | 		handleMessage(msg);
 34 | 	}
 35 | }
 36 | 
 37 | private static void handleCallback(Message message) {  
 38 |     message.callback.run();  
 39 | }
 40 | 
41 |

msg的callback应该已经想到是什么了,就是我们通过Handler.post(Runnable r)传入的Runnable的run方法,这里就要提提java基础了,直接调用线程的run方法相当于是在一个普通的类调用方法,还是在当前线程执行,并不会开启新的线程。

42 |
43 |

Handler、Message、MessageQueue、Looper:

44 |
45 |
    46 |
  • 新建Handler,通过sendMessage或者post发送消息,Handler调用sendMessageAtTime将Message交给MessageQueue
  • 47 |
  • MessageQueue.enqueueMessage方法将Message以链表的形式放入队列中
  • 48 |
  • Looper的loop方法循环调用MessageQueue.next()取出消息,并且调用Handler的dispatchMessage来处理消息
  • 49 |
  • 在dispatchMessage中,分别判断msg.callback、mCallback也就是post方法或者构造方法传入的不为空就执行他们的回调,如果都为空就执行我们最常用重写的handleMessage。
  • 50 |
51 |

4.eventBus的实现原理和广播的区别?
52 | 广播与EventBus的区别

53 |
    54 |
  • 55 |

    1)广播相对于其他的方式而言,广播是重量级的,消耗资源较多的方式。
    56 | 广播作为Android组件间的通信方式,可以使用的场景如下:
    57 | 1、同一app内部的同一组件内的消息通信(单个或多个线程之间);
    58 | 2、同一app内部的不同组件之间的消息通信(单个进程);
    59 | 3、同一app具有多个进程的不同组件之间的消息通信;
    60 | 4、不同app之间的组件之间消息通信;
    61 | 5、Android系统在特定情况下与App之间的消息通信。

    62 |
  • 63 |
  • 64 |

    2)EventBus
    65 | EventBus是基于反射的,利用方法名,来定义事件的。
    66 | EventBus作为Android 开发中常用的框架,拥有着许多优点:
    67 | 1.调度灵活。不依赖于 Context,使用时无需像广播一样关注 Context 的注入与传递。父类对于通知的监听和处理可以继承给子类,这对于简化代码至关重要;通知的优先级,能够保证 Subscriber 关注最重要的通知;粘滞事件(sticky events)能够保证通知不会因 Subscriber 的不在场而忽略。可继承、优先级、粘滞,是 EventBus 比之于广播、观察者等方式最大的优点,它们使得创建结构良好组织紧密的通知系统成为可能。
    68 | 2.使用简单。EventBus 的 Subscriber 注册非常简单,调用 eventBus 对象的 register 方法即可,如果不想创建 eventBus 还可以直接调用静态方法 EventBus.getDefault() 获取默认实例,Subscriber 接收到通知之后的操作放在 onEvent 方法里就行了。成为 Publisher 的过程就更简单了,只需要调用合适的 eventBus(自己创建的或是默认的)的 post 方法即可。
    69 | 3.快速且轻量。作为 github 的明星项目之一, EventBus 的源代码中许多技巧来改善性能,

    70 |
  • 71 |
72 |

EventBus主题在要更新数据的时候是通过反射来执行动作的。EventBus的缺点是:代码阅读性降低、不能跨进程、不能混淆,但是优点很多,比观察者模式耦合性更低,比广播更轻量。

73 |

广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。

74 |

5.final修饰的变量必须初始化吗?
75 | 不是,分两种静态变量、非静态变量情况

76 |
    77 |
  • 静态变量: 必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定;
  • 78 |
  • 成员变量:必要要在非静态初始化块声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定。
  • 79 |
  • **局部变量:**如果final变量未进行初始化,可以进行赋值,当且仅有一次赋值,一旦赋值之后再次赋值就会出错
  • 80 |
81 |

6.Android中栈的理解、四种启动模式的应用场景。

82 |

7.Glide的缓存机制
83 | Glide时二级缓存机制:内存缓存、磁盘缓存

84 |
85 |

Glide内存缓存的特点

86 |
87 |
    88 |
  • 内存缓存使用弱引用和LruCache结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。
  • 89 |
90 |
91 |
Glide内存缓存的流程
92 |
93 |
    94 |
  • 读:是先从lruCache取,取不到再从弱引用中取;
  • 95 |
  • 存:从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一; 渲染完图片,图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。
  • 96 |
97 |
98 |

Glide磁盘缓存流程

99 |
100 |
    101 |
  • 读:先找处理后(result)的图片,没有的话再找原图,即读取网络图片。
  • 102 |
  • 存:先存原图,再存处理后的图。
  • 103 |
104 |

注意一点:diskCacheStrategy设置的的缓存模式即影响读取,也影响存储。
105 | - Glide:Glide除了是接口封装类,还负责创建全局使用的工具和组件。

106 |
    107 |
  • GenericRequest:为每个图片加载创建一个Request,初始化这张图片的转码器、图片变换器、图片展示器target等,当然这个过程实在GenericRequestBuilder的实现类里完成的。
  • 108 |
  • Engine:异步处理总调度器。EnginJob负责线程管理,EngineRunnable是一个异步处理线程。DecodeJob是真正线程里获取和处理图片的地方。
  • 109 |
  • HttpUrlFetcher :获取网络流,使用的是HttpURLConnection。
  • 110 |
  • Decoder :读取网络流后,解码得到Bitmap或者gifResource。
    111 | 接下来我们进入主题,缓存的代码在上面流程图里的什么位置?内存缓存的操作应该是在异步处理之前,磁盘缓存是耗时操作应该是在异步处理中完成。
  • 112 |
113 |

8.Android 5.0、6.0、7.0、8.0、9.0各个版本的特性、适配、问题?

114 |
    115 |
  • 116 |

    5.0 :Android虚拟机安从devlike更换为运行时。

    117 |
  • 118 |
  • 119 |

    6.0 :动态权限适配的问题、 移除对Apache HTTP client的支持,建议使用HttpURLConnection。如果还是想用Apache HTTP client,
    120 | 那么需要在build.gradle中添加

    121 |
    android {
    122 |     useLibrary 'org.apache.http.legacy'
    123 | }
    124 | 
    125 |
  • 126 |
  • 127 |

    7.0 :

    128 |
    129 |

    1.安装app遇到的失败的问题。第一种是 因为更新下载的文件没有制定fileprovider的原因,在mianfest文件中需要指定属性;第二种是安装包解析签名文件出错,原因在于7.0增加v2签名的原因。
    130 | 2.部分机型在webview打开会报错。
    131 | 3.PopupWindow位置不正确,PopupWindow高度为MATCH_PARENT,在显示的时候调用showAsLocation方法时,PopupWindow并没有在指定控件的下方显示。如果使用showAsDropDown,会全屏显示。解决方法:
    132 | 1.最简单的解决方法就是指定 PopupWindow 的高度为 WRAP_CONTENT, 调用 showAsDropDown方法。
    133 | 2.或者弹出时做一下判断处理(代码来自PopupWindowCompat)

    134 |
    135 |
  • 136 |
  • 137 |

    8.0 :通知的适配问题、透明主题的Activity只有只有全屏不透明的activity才可以设置方向,解决方案:1.要么去掉对应activity中的 screenOrientation 属性,2.或者对应设置方向的代码。
    138 | 要么舍弃透明效果,在它的Theme中添加:
    139 | <item name="android:windowIsTranslucent">false</item>
    140 | 方案2最好是添加 values-v26目录,单独处理8.0版本。个人推荐方案1,。

    141 |
  • 142 |
  • 143 |

    9.0:暂时没做

    144 |
  • 145 |
  • 146 |
147 |
148 |

Written with StackEdit.

149 |
150 | 151 | -------------------------------------------------------------------------------- /排序算法总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

排序算法总结

7 |

文章转载自 https://www.jianshu.com/p/ae97c3ceea8d

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
排序算法平均时间复杂度
冒泡排序O(n2)
选择排序O(n2)
插入排序O(n2)
希尔排序O(n1.5)
快速排序O(N*logN)
归并排序O(N*logN)
堆排序法O(N*logN)
基数排序O(d(n+r))

一. 冒泡排序(BubbleSort)

51 |
52 |
    53 |
  1. 54 |

    基本思想: 两个数比较大小,较大的数下沉,较小的数冒起来。

    55 |
  2. 56 |
  3. 57 |

    过程:

    58 |
  4. 59 |
60 |
    61 |
  • 62 |

    比较相邻的两个数据,如果第二个数小,就交换位置。

    63 |
  • 64 |
  • 65 |

    从后向前两两比较,一直到比较最前两个数据。最终最小数被交换到起始的位置,这样第一个最小数的位置就排好了。

    66 |
  • 67 |
  • 68 |

    继续重复上述过程,依次将第2.3…n-1个最小数排好位置。

    69 |

    70 |

    冒泡排序

    71 |
  • 72 |
73 |
    74 |
  1. 平均时间复杂度: O(n2)
  2. 75 |
  3. java代码实现:
  4. 76 |
77 |
public static void BubbleSort(int [] arr){
 78 |         
 79 |         int temp;//临时变量
 80 |         for(int i=0; i<arr.length-1; i++){   //表示趟数,一共arr.length-1次。
 81 |             for(int j=arr.length-1; j>i; j--){
 82 |                 
 83 |                 if(arr[j] < arr[j-1]){
 84 |                     temp = arr[j];
 85 |                     arr[j] = arr[j-1];
 86 |                     arr[j-1] = temp;
 87 |                 }
 88 |             }
 89 |         }
 90 |     }
 91 | 
 92 | 
93 |
    94 |
  1. 优化:
  2. 95 |
96 |
    97 |
  • 98 |

    针对问题:
    99 | 数据的顺序排好之后,冒泡算法仍然会继续进行下一轮的比较,直到arr.length-1次,后面的比较没有意义的。

    100 |
  • 101 |
  • 102 |

    方案:
    103 | 设置标志位flag,如果发生了交换flag设置为true;如果没有交换就设置为false。
    104 | 这样当一轮比较结束后如果flag仍为false,即:这一轮没有发生交换,说明数据的顺序已经排好,没有必要继续进行下去。

    105 |
  • 106 |
107 |
public static void BubbleSort1(int [] arr){
108 |         
109 |         int temp;//临时变量
110 |         boolean flag;//是否交换的标志
111 |         for(int i=0; i<arr.length-1; i++){   //表示趟数,一共arr.length-1次。
112 |             
113 |             flag = false;
114 |             for(int j=arr.length-1; j>i; j--){
115 |                 
116 |                 if(arr[j] < arr[j-1]){
117 |                     temp = arr[j];
118 |                     arr[j] = arr[j-1];
119 |                     arr[j-1] = temp;
120 |                     flag = true;
121 |                 }
122 |             }
123 |             if(!flag) break;
124 |         }
125 |     }
126 | 
127 | 
128 |

二. 选择排序(SelctionSort)

129 |
130 |
    131 |
  1. 132 |

    基本思想:
    133 | 在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;
    134 | 第二次遍历n-2个数,找到最小的数值与第二个元素交换;
    135 | 。。。
    136 | 第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。

    137 |
  2. 138 |
  3. 139 |

    过程:

    140 |

    141 |

    选择排序

    142 |
  4. 143 |
  5. 144 |

    平均时间复杂度: O(n2)

    145 |
  6. 146 |
  7. 147 |

    java代码实现:

    148 |
  8. 149 |
150 |
public static void select_sort(int array[],int lenth){
151 |       
152 |       for(int i=0;i<lenth-1;i++){
153 |           
154 |           int minIndex = i;
155 |           for(int j=i+1;j<lenth;j++){
156 |              if(array[j]<array[minIndex]){
157 |                  minIndex = j;
158 |              }
159 |           }
160 |           if(minIndex != i){
161 |               int temp = array[i];
162 |               array[i] = array[minIndex];
163 |               array[minIndex] = temp;
164 |           }
165 |       }
166 |   }
167 | 
168 | 
169 |

三. 插入排序(Insertion Sort)

170 |
171 |
    172 |
  1. 173 |

    基本思想:
    174 | 在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

    175 |
  2. 176 |
  3. 177 |

    过程:

    178 |

    179 |

    插入排序

    180 |

    181 |

    相同的场景

    182 |
  4. 183 |
  5. 184 |

    平均时间复杂度: O(n2)

    185 |
  6. 186 |
  7. 187 |

    java代码实现:

    188 |
  8. 189 |
190 |
public static void  insert_sort(int array[],int lenth){
191 |       
192 |       int temp;
193 |       
194 |       for(int i=0;i<lenth-1;i++){
195 |           for(int j=i+1;j>0;j--){
196 |               if(array[j] < array[j-1]){
197 |                   temp = array[j-1];
198 |                   array[j-1] = array[j];
199 |                   array[j] = temp;
200 |               }else{         //不需要交换
201 |                   break;
202 |               }
203 |           }
204 |       }
205 |   }
206 | 
207 | 
208 |

四. 希尔排序(Shell Sort)

209 |
210 |
    211 |
  1. 212 |

    前言:
    213 | 数据序列1: 13-17-20-42-28 利用插入排序,13-17-20-28-42. Number of swap:1;
    214 | 数据序列2: 13-17-20-42-14 利用插入排序,13-14-17-20-42. Number of swap:3;
    215 | 如果数据序列基本有序,使用插入排序会更加高效。

    216 |
  2. 217 |
  3. 218 |

    基本思想:
    219 | 在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。
    220 | 然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。

    221 |
  4. 222 |
  5. 223 |

    过程:

    224 |

    225 |

    希尔排序

    226 |
  6. 227 |
  7. 228 |

    平均时间复杂度:

    229 |
  8. 230 |
  9. 231 |

    java代码实现:

    232 |
  10. 233 |
234 |
  public static void shell_sort(int array[],int lenth){
235 |       
236 |       int temp = 0;
237 |       int incre = lenth;
238 |       
239 |       while(true){
240 |           incre = incre/2;
241 |           
242 |           for(int k = 0;k<incre;k++){    //根据增量分为若干子序列
243 |               
244 |               for(int i=k+incre;i<lenth;i+=incre){
245 |                   
246 |                   for(int j=i;j>k;j-=incre){
247 |                       if(array[j]<array[j-incre]){
248 |                           temp = array[j-incre];
249 |                           array[j-incre] = array[j];
250 |                           array[j] = temp;
251 |                       }else{
252 |                           break;
253 |                       }
254 |                   }
255 |               }
256 |           }
257 |           
258 |           if(incre == 1){
259 |               break;
260 |           }
261 |       }
262 |   }
263 | 
264 | 
265 |

五. 快速排序(Quicksort)

266 |
267 |
    268 |
  1. 基本思想:(分治)
  2. 269 |
270 |
    271 |
  • 先从数列中取出一个数作为key值;
  • 272 |
  • 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
  • 273 |
  • 对左右两个小数列重复第二步,直至各区间只有1个数。
  • 274 |
275 |
    276 |
  1. 辅助理解:挖坑填数
  2. 277 |
278 |
    279 |
  • 初始时 i = 0; j = 9; key=72
    280 | 由于已经将a[0]中的数保存到key中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
    281 | 从j开始向前找一个比key小的数。当j=8,符合条件,a[0] = a[8] ; i++ ; 将a[8]挖出再填到上一个坑a[0]中。
    282 | 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。
    283 | 这次从i开始向后找一个大于key的数,当i=3,符合条件,a[8] = a[3] ; j-- ; 将a[3]挖出再填到上一个坑中。
  • 284 |
285 |
数组:72 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 48 - 85
286 |       0   1   2    3    4    5    6    7    8    9
287 | 
288 | 
289 |
    290 |
  • 此时 i = 3; j = 7; key=72
    291 | 再重复上面的步骤,先从后向前找,再从前向后找。
    292 | 从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
    293 | 从i开始向后找,当i=5时,由于i==j退出。
    294 | 此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将key填入a[5]。
  • 295 |
296 |
数组:48 - 6 - 57 - 88 - 60 - 42 - 83 - 73 - 88 - 85
297 |       0   1   2    3    4    5    6    7    8    9
298 | 
299 | 
300 |
    301 |
  • 可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
  • 302 |
303 |
数组:48 - 6 - 57 - 42 - 60 - 72 - 83 - 73 - 88 - 85
304 |       0   1   2    3    4    5    6    7    8    9
305 | 
306 | 
307 |
    308 |
  1. 309 |

    平均时间复杂度: O(N*logN)

    310 |
  2. 311 |
  3. 312 |

    代码实现:

    313 |
  4. 314 |
315 |
public static void quickSort(int a[],int l,int r){
316 |         if(l>=r)
317 |           return;
318 |          
319 |         int i = l; int j = r; int key = a[l];//选择第一个数为key
320 |         
321 |         while(i<j){
322 |             
323 |             while(i<j && a[j]>=key)//从右向左找第一个小于key的值
324 |                 j--;
325 |             if(i<j){
326 |                 a[i] = a[j];
327 |                 i++;
328 |             }
329 |             
330 |             while(i<j && a[i]<key)//从左向右找第一个大于key的值
331 |                 i++;
332 |             
333 |             if(i<j){
334 |                 a[j] = a[i];
335 |                 j--;
336 |             }
337 |         }
338 |         //i == j
339 |         a[i] = key;
340 |         quickSort(a, l, i-1);//递归调用
341 |         quickSort(a, i+1, r);//递归调用
342 |     }
343 | 
344 | 
345 |

key值的选取可以有多种形式,例如中间数或者随机数,分别会对算法的复杂度产生不同的影响。

346 |

六. 归并排序(Merge Sort)

347 |
348 |
    349 |
  1. 基本思想:参考
    350 | 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
    351 | 首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
  2. 352 |
353 |
//将有序数组a[]和b[]合并到c[]中
354 | void MemeryArray(int a[], int n, int b[], int m, int c[])
355 | {
356 |     int i, j, k;
357 | 
358 |     i = j = k = 0;
359 |     while (i < n && j < m)
360 |     {
361 |         if (a[i] < b[j])
362 |             c[k++] = a[i++];
363 |         else
364 |             c[k++] = b[j++]; 
365 |     }
366 | 
367 |     while (i < n)
368 |         c[k++] = a[i++];
369 | 
370 |     while (j < m)
371 |         c[k++] = b[j++];
372 | }
373 | 
374 | 
375 |

解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成2组A,B,如果这2组组内的数据都是有序的,那么就可以很方便的将这2组数据进行排序。如何让这2组组内数据有序了?
376 | 可以将A,B组各自再分成2组。依次类推,当分出来的小组只有1个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的2个小组就可以了。这样通过先递归的分解数列再合并数列就完成了归并排序。

377 |
    378 |
  1. 379 |

    过程:

    380 |

    381 |

    归并排序

    382 |
  2. 383 |
  3. 384 |

    平均时间复杂度: O(NlogN)
    385 | 归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。

    386 |
  4. 387 |
  5. 388 |

    代码实现:

    389 |
  6. 390 |
391 |
 public static void merge_sort(int a[],int first,int last,int temp[]){
392 |      
393 |      if(first < last){
394 |          int middle = (first + last)/2;
395 |          merge_sort(a,first,middle,temp);//左半部分排好序
396 |          merge_sort(a,middle+1,last,temp);//右半部分排好序
397 |          mergeArray(a,first,middle,last,temp); //合并左右部分
398 |      }
399 |  }
400 | 
401 | 
402 |
 //合并 :将两个序列a[first-middle],a[middle+1-end]合并
403 |  public static void mergeArray(int a[],int first,int middle,int end,int temp[]){     
404 |      int i = first;
405 |      int m = middle;
406 |      int j = middle+1;
407 |      int n = end;
408 |      int k = 0; 
409 |      while(i<=m && j<=n){
410 |          if(a[i] <= a[j]){
411 |              temp[k] = a[i];
412 |              k++;
413 |              i++;
414 |          }else{
415 |              temp[k] = a[j];
416 |              k++;
417 |              j++;
418 |          }
419 |      }   
420 |      while(i<=m){
421 |          temp[k] = a[i];
422 |          k++;
423 |          i++;
424 |      }   
425 |      while(j<=n){
426 |          temp[k] = a[j];
427 |          k++;
428 |          j++; 
429 |      }
430 |      
431 |      for(int ii=0;ii<k;ii++){
432 |          a[first + ii] = temp[ii];
433 |      }
434 |  }
435 | 
436 | 
437 |

七. 堆排序(HeapSort)

438 |
439 |
    440 |
  1. 441 |

    基本思想:

    442 |

    443 |
  2. 444 |
  3. 445 |

    图示: ( 88,85,83,73,72,60,57,48,42,6)

    446 |

    447 |

    Heap Sort

    448 |
  4. 449 |
  5. 450 |

    平均时间复杂度: O(NlogN)
    451 | 由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。

    452 |
  6. 453 |
  7. 454 |

    java代码实现:

    455 |
  8. 456 |
457 |
//构建最小堆
458 | public static void MakeMinHeap(int a[], int n){
459 |     for(int i=(n-1)/2 ; i>=0 ; i--){
460 |         MinHeapFixdown(a,i,n);
461 |     }
462 | }
463 |   //从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
464 |   public static void MinHeapFixdown(int a[],int i,int n){
465 |       
466 |       int j = 2*i+1; //子节点
467 |       int temp = 0;
468 |       
469 |       while(j<n){
470 |           //在左右子节点中寻找最小的
471 |           if(j+1<n && a[j+1]<a[j]){   
472 |               j++;
473 |           }
474 |           
475 |           if(a[i] <= a[j])
476 |               break;
477 |           
478 |           //较大节点下移
479 |           temp = a[i];
480 |           a[i] = a[j];
481 |           a[j] = temp;
482 |           
483 |           i = j;
484 |           j = 2*i+1;
485 |       }
486 |   }
487 | 
488 | 
489 |
 public static void MinHeap_Sort(int a[],int n){
490 |      int temp = 0;
491 |      MakeMinHeap(a,n);
492 |      
493 |      for(int i=n-1;i>0;i--){
494 |          temp = a[0];
495 |          a[0] = a[i];
496 |          a[i] = temp; 
497 |          MinHeapFixdown(a,0,i);
498 |      }   
499 |  }
500 | 
501 | 
502 |

八. 基数排序(RadixSort)

503 |
504 |
BinSort
505 |
    506 |
  1. 507 |

    基本思想:
    508 | BinSort想法非常简单,首先创建数组A[MaxValue];然后将每个数放到相应的位置上(例如17放在下标17的数组位置);最后遍历数组,即为排序后的结果。

    509 |
  2. 510 |
  3. 511 |

    图示:

    512 |

    513 |

    BinSort

    514 |
  4. 515 |
  5. 516 |

    问题:
    517 | 当序列中存在较大值时,BinSort 的排序方法会浪费大量的空间开销。

    518 |
  6. 519 |
520 |
RadixSort
521 |
    522 |
  1. 523 |

    基本思想:
    524 | 基数排序是在BinSort的基础上,通过基数的限制来减少空间的开销。

    525 |
  2. 526 |
  3. 527 |

    过程:

    528 |

    529 |

    过程1

    530 |

    531 |

    过程2

    532 |

    (1)首先确定基数为10,数组的长度也就是10.每个数34都会在这10个数中寻找自己的位置。
    533 | (2)不同于BinSort会直接将数34放在数组的下标34处,基数排序是将34分开为3和4,第一轮排序根据最末位放在数组的下标4处,第二轮排序根据倒数第二位放在数组的下标3处,然后遍历数组即可。

    534 |
  4. 535 |
  5. 536 |

    java代码实现:

    537 |
  6. 538 |
539 |
public static void RadixSort(int A[],int temp[],int n,int k,int r,int cnt[]){
540 |       
541 |       //A:原数组
542 |       //temp:临时数组
543 |       //n:序列的数字个数
544 |       //k:最大的位数2
545 |       //r:基数10
546 |       //cnt:存储bin[i]的个数
547 |       
548 |       for(int i=0 , rtok=1; i<k ; i++ ,rtok = rtok*r){
549 |           
550 |           //初始化
551 |           for(int j=0;j<r;j++){
552 |               cnt[j] = 0;
553 |           }
554 |           //计算每个箱子的数字个数
555 |           for(int j=0;j<n;j++){
556 |               cnt[(A[j]/rtok)%r]++;
557 |           }
558 |           //cnt[j]的个数修改为前j个箱子一共有几个数字
559 |           for(int j=1;j<r;j++){
560 |               cnt[j] = cnt[j-1] + cnt[j];
561 |           }
562 |           for(int j = n-1;j>=0;j--){      //重点理解
563 |               cnt[(A[j]/rtok)%r]--;
564 |               temp[cnt[(A[j]/rtok)%r]] = A[j];
565 |           }
566 |           for(int j=0;j<n;j++){
567 |               A[j] = temp[j];
568 |           }
569 |       }
570 |   }
571 | 
572 | 
573 |
574 |

Written with StackEdit.

575 |
576 | 577 | -------------------------------------------------------------------------------- /面试知识点 问答.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | 4 | --- 5 | 6 |

面试知识点问答

7 |

1.java数据类型

8 |
9 |

java数据类型分为基本类型、引用类型

10 |
11 |
    12 |
  • 基本数据类型分为:整形(byte、short、int、long)、浮点类型(float、double)、布尔类型(boolean)、字符型(char),共8种。
  • 13 |
  • 引用类型:5种引用类型(对象类型)分别是类、接口、 数组、枚举、 标注
  • 14 |
15 |

2.java种==和equals()的区别?

16 |
17 |

==对于数据类型的比较

18 |
19 |
    20 |
  • 基本数据类型: 他们之间的比较,比较的是他们的值。
  • 21 |
  • 引用数据类型:当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址(确切的说,是堆内存地址)。
  • 22 |
23 |
24 |

注:对于第二种类型,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。因为每new一次,都会重新开辟堆内存空间。

25 |
26 |
27 |

equals()的作用

28 |
29 |
    30 |
  • 对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。
  • 31 |
32 |
33 |

请解释字符串比较之中“==”和equals()的区别?

34 |
35 |
    36 |
  • ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较;
  • 37 |
  • equals():比较的是两个字符串的内容,属于内容比较。
  • 38 |
39 |

3.StringStringBufferStringBuilder区别?

40 |
41 |

三者在执行速度方面的比较:StringBuilder > StringBuffer > String

42 |
43 |

由于StringBuilder、StringBuffer都继承自抽象类AbstractStringBuilder,他们的append、replace、delete、insert、indexOf、reverse方法的实现都是由AbstractStringBuilder实现的。不同的是StringBuffer做了同步处理,是线程安全的,StringBuilder是非线程安全的。

44 |

因此,在下面和String比较的时候就拿StringBuffer做比较。

45 |
46 |

String和StringBuffer主要有2个区别:

47 |
48 |
    49 |
  • 1.String类对象为不可变对象,一旦修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringBuffer类对象为可修改对象,可以通过append()方法来修改值
  • 50 |
  • 2.String类的性能远不如StringBuffer类。
  • 51 |
52 |
53 |

String:

54 |
55 |
    56 |
  • 1.是对象不是原始类型,是引用类型。
  • 57 |
  • 2.String 是final类,不能被继承,一旦被创建,就不能修改它的值.
  • 58 |
  • 3.底层用char[]来实现。
  • 59 |
  • 4.在用”+”进行字符串连接的时候,底层是新建一个String对象,通过新建一个StringBuilder或StringBuffer对象,调用其append方法,然后调用toString方法(在调用toString方法的时候会再创建一个String对象),返回给新建的String对象。其中会频繁的创建新对象,增加了虚拟机GC的工作量,频繁字符串连接的时候不推荐使用。
  • 60 |
61 |
62 |

StringBuffer:

63 |
64 |
    65 |
  • 66 |

    1.是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象。

    67 |
  • 68 |
  • 69 |

    2.底层用char[]来实现。

    70 |
  • 71 |
  • 72 |

    3.它只能通过构造函数来创建:

    73 |
    StringBuffer sb1 = new StringBuffer(); //创建一个长度为16的StringBuffer对象,内容为空。
     74 | StringBuffer sb2 = new StringBuffer(10); //创建一个长度为10+16的StringBuffer对象,内容为空。
     75 | StringBuffer sb3 = new StringBuffer("abc"); //创建一个长度为3+16的StringBuffer对象。
     76 | 
    77 |
  • 78 |
  • 79 |

    4.对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer中赋值的时候可以通过它的append()方法.sb.append(“hello”);在调用append()方法的时候会先判断StringBuffer底层char[]的长度,如果长度不够用,就对char[]进行扩展,新长度为原来长度的2倍+2。

    80 |
  • 81 |
82 |

总结:

83 |
    84 |
  • 85 |

    1.在字符串连接操作中StringBuffer的效率要比String高。

    86 |
  • 87 |
  • 88 |

    2.如果没有频繁的字符串连接,可以用String,如果有频繁的字符串连接,推荐用StringBuffer(线程安全)或者StringBuilder(非线程安全)。

    89 |
  • 90 |
  • 91 |

    3.StringBuffer之间的比较,要先调用toString()方法,再调用equals()方法作比较。

    92 |
    StringBuffer sb1 = new StringBuffer("abc");
     93 | StringBuffer sb2 = new StringBuffer("abc");
     94 | System.out.println(sb1.equals(sb2));//结果false
     95 | System.out.println(sb1.toString().equals(sb2.toString()));//结果true
     96 | 
    97 |
  • 98 |
  • 99 |

    4.是因为声明s1,s2的时候这个”abc”是放在JVM内存中的常量池中,s1和s2都指向这个地址。因此在比较的时候s1,s2指向的为内存中的同一个地址,所以会相等。

    100 |
    String s1 = "abc";
    101 | String s2 = "abc";
    102 | s1==s1;//结果为true
    103 | 
    104 |
  • 105 |
  • 106 |

    5.关于”+“连接String,将class文件反编译后;我们可以看出JVM在进行String连接的时候,实际上是新建了一个StringBuilder,然后进行运算。

    107 |
  • 108 |
109 |

4.什么是内部类?内部类的作用

110 |
    111 |
  • 内部类可直接访问外部类的属性
  • 112 |
  • Java中内部类主要分为成员内部类局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法, 不依赖外围类)
  • 113 |
114 |

5.final**,finallyfinalize的区别**

115 |
    116 |
  • final:修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可重写。
  • 117 |
  • finally:与try…catch…共同使用,确保无论是否出现异常都能被调用到。
  • 118 |
  • finalize:类的方法,垃圾回收之前会调用此方法,子类可以重写finalize()方法实现对资源的回收。
  • 119 |
120 |

6.java中抽象类和接口的区别?

121 |
122 |

抽象类的特点

123 |
124 |
    125 |
  • 抽象类是由子类具有相同的一类特征抽象而来,也可以说是其基类或者父类
  • 126 |
  • 抽象方法必须为 public 或者 protected(因为如果为 private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为 public
  • 127 |
  • 抽象类不能用来创建对象
  • 128 |
  • 抽象方法必须由子类来实现
  • 129 |
  • 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法,如果子类没有实现父类的抽象方法,则必须将子类也定义为抽象类
  • 130 |
  • 抽象类还是很有用的重构工具,因为它们使得我们可以很容易地将公共方法沿着继承层次结构向上移动
  • 131 |
132 |
133 |

接口的特点

134 |
135 |

java 为了保证数据安全性是不能多继承的,也就是一个类只有一个父类。
136 | 但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多继承的缺陷。

137 |

接口是抽象类的延伸,它可以定义没有方法体的方法,要求实现者去实现。

138 |
    139 |
  • 接口的所有方法访问权限自动被声明为 public
  • 140 |
  • 接口中可以定义“成员变量”,会自动变为 public static final 修饰的静态常量 141 |
      142 |
    • 可以通过类命名直接访问:ImplementClass.name
    • 143 |
    • 不推荐使用接口创建常量类
    • 144 |
    145 |
  • 146 |
  • 实现接口的非抽象类必须实现接口中所有方法,抽象类可以不用全部实现
  • 147 |
  • 接口不能创建对象,但可以申明一个接口变量,方便调用
  • 148 |
  • 完全解耦,可以编写可复用性更好的代码
  • 149 |
150 |

区别
151 | 1.语法层面上的区别

152 |

1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

153 |

2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

154 |

3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

155 |

4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

156 |

2.设计层面上的区别
157 | 1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
158 | 2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计

159 |

7.静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

160 |
    161 |
  • 可继承 不可重写 而是被隐藏
  • 162 |
  • 如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成。
  • 163 |
164 |

8.string 转换成 integer****的方式及原理

165 |
166 |

string 转化为integer Integer.parparseInt()

167 |
168 |
    169 |
  1. parseInt(String s)–内部调用parseInt(s,10)(默认为10进制)
  2. 170 |
  3. 正常判断null,进制范围,length等
  4. 171 |
  5. 判断第一个字符是否是符号位
  6. 172 |
  7. 循环遍历确定每个字符的十进制值
  8. 173 |
  9. 通过*= 和-= 进行计算拼接
  10. 174 |
  11. 判断是否为负值 返回结果。
  12. 175 |
176 |
177 |

integer转化为string

178 |
179 |
    180 |
  1. a+"" 效率最差
  2. 181 |
  3. String.valueOf(a) 效率最高
  4. 182 |
  5. Integer.toString(a) 效率其次
  6. 183 |
184 |

9.成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用
185 | java中内部类主要分为成员内部类局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法, 不依赖外围类)

186 |

使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
187 | 因为Java不支持多继承,支持实现多个接口。但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
188 | 10.serializable 和parcelable 的区别、选择?

189 |
190 |

作用

191 |
192 |
    193 |
  • Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的
  • 194 |
  • Parcelable :Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
  • 195 |
196 |
197 |

区别

198 |
199 |
    200 |
  • Serializable Java 序列化接口,使用反射, 在硬盘上读写 读写过程中有大量临时变量的生成,内部执行大量的i/o操作,效率很低。
  • 201 |
  • Parcelable Android 序列化接口 ,实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。效率高 使用麻烦 在内存中读写,对象不能保存到磁盘中。
  • 202 |
203 |
204 |

选择
205 | Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据;而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化

206 |
207 |

11.Java****中实现多态的机制是什么?

208 |
209 |

**多态存在的三个必要条件:

210 |
211 |
    212 |
  • 一、要有继承;
  • 213 |
  • 二、要有重写;
  • 214 |
  • 三、父类引用指向子类对象。**
  • 215 |
216 |
217 |

**Java中多态的实现方式:

218 |
219 |
    220 |
  • 接口实现
  • 221 |
  • 继承父类进行方法重写
  • 222 |
  • 同一个类中进行方法重载**
  • 223 |
224 |

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
225 | 12.在Java中创建对象,通常有四种方式:

226 |
    227 |
  • 1、new关键字;
  • 228 |
229 |
230 |

ClassA a = new ClassA();
231 | 使用new关键字,实际上完成了两个步骤:1、分配内存空间,创建对象;2、调用构造方法初始化对象。

232 |
233 |

注意:任何类都有构造方法,但是new指令只能创建非抽象类的对象。

234 |
    235 |
  • 2、反射机制;
  • 236 |
237 |
238 |

反射机制创建对象分为两种,一种是Class类的newInstance(),另一种是java.lang.reflect.Constructor类的newInstance()。
239 | 两者区别在于:
240 | Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
241 | Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。

242 |
243 |

事实上Class的newInstance方法内部调用Constructor的newInstance方法。
244 | 反射机制创建对象,使用的是类加载机制,newInstance()的特点是在调用时才创建对象,通过类加载机制Class.forName(“xxx”).newInstance()创建对象,xxx可以从配置文件

245 |
    246 |
  • 3、clone()方法;
  • 247 |
  • 4、反序列化。
  • 248 |
249 |

12.java中反射机制,以及在Android中的常见使用情景
250 | JAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性。 从对象出发,通过反射(Class类)可以取得取得类的完整信息(类名 Class类型,所在包、具有的所有方法 Method[]类型、某个方法的完整信息(包括修饰符、返回值类型、异常、参数类型)、所有属性 Field[]、某个属性的完整信息、构造器 Constructors),调用类的属性或方法自己的总结: 在运行过程中获得类、对象、方法的所有信息。
251 | Android使用情景:

252 |
    253 |
  • 1.Activity 的启动过程中 Activity 的对象的创建就用到了反射技术。
  • 254 |
  • 2.Android的热修复技术就是基于这个实现的。
  • 255 |
  • 3.对于一些被@hide隐藏的API的调用。
  • 256 |
257 |

13.java中的异常分类,Android中常见的异常?

258 |
259 |

throwable:异常和错误的基类,提供了错误堆栈实现等一系列方法。 两个直接子类:Error & Exception

260 |
261 |

两个子类区别:

262 |
    263 |
  • 264 |

    Error: 程序不应该捕捉的错误,应该交由JVM来处理。一般可能指非常重大的错误。这个错误我们一般获取不到。

    265 |
  • 266 |
  • 267 |

    Exception:程序中应该要捕获的错误。这个异常类及它的子类是我们需要学习获取的。

    268 |

    (1)RuntimeException:运行期异常,是Exception的子类,但不需捕捉的异常超类,但是实际发生异常时,还是会蹦的,只是编译时没有报错而已。比如除数为零,数组空指针等等,这些都是在运行之后才会报错。

    269 |

    (2)非运行时异常:除了RuntimeException类和它的子类,其他类都被定义为Checked类,是需要显式处理可能出现的异常,否则编译就报错了。Checked类主要包含:IO类和SQL类的异常情况,这些在使用时经常要先显示处理异常(使用throws或try catch捕获)

    270 |
  • 271 |
272 |
273 |

在Java中throw与throws关键字之间的区别?
274 | throws用于在方法签名中声明此方法可能抛出的异常,而throw关键字则是中断程序的执行并移交异常对象到运行时进行处理。

275 |
276 |
277 |

被检查的异常和不受检查的异常有什么区别?
278 | 1.被检查的异常应该用try-catch块代码处理,或者在main方法中用throws关键字让JRE了解程序可能抛出哪些异常。不受检查的异常在程序中不要求被处理或用throws语句告知。
279 | 2.被检查的异常适用于那些不是因程序引起的错误情况,比如:读取文件时文件不存在引发的FileNotFoundException。然而,不被检查的异常通常都是由于糟糕的编程引起的,比如:在对象引用时没有确保对象非空而引起的NullPointerException。

280 |
281 |

常见异常

282 |

283 |

14.创建多线程形式

284 |
285 |

Written with StackEdit.

286 |
287 | 288 | -------------------------------------------------------------------------------- /面试知识点/排序算法总结.md: -------------------------------------------------------------------------------- 1 | ## 排序算法总结 2 | 文章转载自 https://www.jianshu.com/p/ae97c3ceea8d 3 | | 排序算法 | 平均时间复杂度 | 4 | |--------------|-------------| 5 | | 冒泡排序 | O(n2) | 6 | | 选择排序 | O(n2) | 7 | | 插入排序 | O(n2) | 8 | | 希尔排序 | O(n1.5) | 9 | | 快速排序 | O(N*logN) | 10 | | 归并排序 | O(N*logN) | 11 | | 堆排序法 | O(N*logN) | 12 | | 基数排序 | O(d(n+r)) | 13 | 14 | #### 一. 冒泡排序(BubbleSort) 15 | 16 | ---------- 17 | 18 | 1. **基本思想:**两个数比较大小,较大的数下沉,较小的数冒起来。 19 | 20 | 2. **过程:** 21 | 22 | 23 | - 比较相邻的两个数据,如果第二个数小,就交换位置。 24 | - 从后向前两两比较,一直到比较最前两个数据。最终最小数被交换到起始的位置,这样第一个最小数的位置就排好了。 25 | - 继续重复上述过程,依次将第2.3...n-1个最小数排好位置。 26 | 27 | 28 | 29 | ![](//upload-images.jianshu.io/upload_images/650075-4ca0eba08589328b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp) 30 | 31 | 冒泡排序 32 | 33 | 34 | 3. **平均时间复杂度:**O(n2) 35 | 4. **java代码实现:** 36 | 37 | ``` 38 | public static void BubbleSort(int [] arr){ 39 | 40 | int temp;//临时变量 41 | for(int i=0; ii; j--){ 43 | 44 | if(arr[j] < arr[j-1]){ 45 | temp = arr[j]; 46 | arr[j] = arr[j-1]; 47 | arr[j-1] = temp; 48 | } 49 | } 50 | } 51 | } 52 | 53 | ``` 54 | 55 | 5. **优化:** 56 | 57 | - **针对问题:** 58 | 数据的顺序排好之后,冒泡算法仍然会继续进行下一轮的比较,直到arr.length-1次,后面的比较没有意义的。 59 | 60 | - **方案:** 61 | 设置标志位flag,如果发生了交换flag设置为true;如果没有交换就设置为false。 62 | 这样当一轮比较结束后如果flag仍为false,即:这一轮没有发生交换,说明数据的顺序已经排好,没有必要继续进行下去。 63 | 64 | 65 | ``` 66 | public static void BubbleSort1(int [] arr){ 67 | 68 | int temp;//临时变量 69 | boolean flag;//是否交换的标志 70 | for(int i=0; ii; j--){ 74 | 75 | if(arr[j] < arr[j-1]){ 76 | temp = arr[j]; 77 | arr[j] = arr[j-1]; 78 | arr[j-1] = temp; 79 | flag = true; 80 | } 81 | } 82 | if(!flag) break; 83 | } 84 | } 85 | 86 | ``` 87 | 88 | #### 二. 选择排序(SelctionSort) 89 | 90 | ---------- 91 | 92 | 1. **基本思想:** 93 | 在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换; 94 | 第二次遍历n-2个数,找到最小的数值与第二个元素交换; 95 | 。。。 96 | 第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。 97 | 98 | 2. **过程:** 99 | 100 | ![](//upload-images.jianshu.io/upload_images/650075-52b3df95a952ca4f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp) 101 | 102 | 选择排序 103 | 104 | 3. **平均时间复杂度:**O(n2) 105 | 106 | 4. **java代码实现:** 107 | 108 | 109 | ``` 110 | public static void select_sort(int array[],int lenth){ 111 | 112 | for(int i=0;i0;j--){ 161 | if(array[j] < array[j-1]){ 162 | temp = array[j-1]; 163 | array[j-1] = array[j]; 164 | array[j] = temp; 165 | }else{ //不需要交换 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | 172 | ``` 173 | 174 | #### 四. 希尔排序(Shell Sort) 175 | 176 | ---------- 177 | 178 | 1. **前言:** 179 | 数据序列1: 13-17-20-42-28 利用插入排序,13-17-20-28-42. Number of swap:1; 180 | 数据序列2: 13-17-20-42-14 利用插入排序,13-14-17-20-42. Number of swap:3; 181 | 如果数据序列基本有序,使用插入排序会更加高效。 182 | 183 | 2. **基本思想:** 184 | 在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。 185 | 然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。 186 | 187 | 3. **过程:** 188 | 189 | ![](//upload-images.jianshu.io/upload_images/650075-159300f36ea6b0f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/818/format/webp) 190 | 191 | 希尔排序 192 | 193 | 4. **平均时间复杂度:** 194 | 195 | 5. **java代码实现:** 196 | 197 | 198 | ``` 199 | public static void shell_sort(int array[],int lenth){ 200 | 201 | int temp = 0; 202 | int incre = lenth; 203 | 204 | while(true){ 205 | incre = incre/2; 206 | 207 | for(int k = 0;kk;j-=incre){ 212 | if(array[j]=r) 283 | return; 284 | 285 | int i = l; int j = r; int key = a[l];//选择第一个数为key 286 | 287 | while(i=key)//从右向左找第一个小于key的值 290 | j--; 291 | if(i=0 ; i--){ 437 | MinHeapFixdown(a,i,n); 438 | } 439 | } 440 | //从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2 441 | public static void MinHeapFixdown(int a[],int i,int n){ 442 | 443 | int j = 2*i+1; //子节点 444 | int temp = 0; 445 | 446 | while(j0;i--){ 473 | temp = a[0]; 474 | a[0] = a[i]; 475 | a[i] = temp; 476 | MinHeapFixdown(a,0,i); 477 | } 478 | } 479 | 480 | ``` 481 | 482 | #### 八. 基数排序(RadixSort) 483 | 484 | ---------- 485 | 486 | ##### BinSort 487 | 488 | 1. **基本思想:** 489 | BinSort想法非常简单,首先创建数组A[MaxValue];然后将每个数放到相应的位置上(例如17放在下标17的数组位置);最后遍历数组,即为排序后的结果。 490 | 491 | 2. ** 图示:** 492 | 493 | 494 | 495 | ![](//upload-images.jianshu.io/upload_images/650075-1447386fb32c0b52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/754/format/webp) 496 | 497 | BinSort 498 | 499 | 3. ** 问题:** 500 | 当序列中存在较大值时,BinSort 的排序方法会浪费大量的空间开销。 501 | 502 | 503 | ##### RadixSort 504 | 505 | 1. **基本思想:** 506 | 基数排序是在BinSort的基础上,通过基数的限制来减少空间的开销。 507 | 508 | 2. **过程:** 509 | 510 | ![](//upload-images.jianshu.io/upload_images/650075-560569712b8939ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/730/format/webp) 511 | 512 | 过程1 513 | 514 | 515 | 516 | ![](//upload-images.jianshu.io/upload_images/650075-f5c6b8e6d9f081e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/730/format/webp) 517 | 518 | 过程2 519 | 520 | 521 | (1)首先确定基数为10,数组的长度也就是10.每个数34都会在这10个数中寻找自己的位置。 522 | (2)不同于BinSort会直接将数34放在数组的下标34处,基数排序是将34分开为3和4,第一轮排序根据最末位放在数组的下标4处,第二轮排序根据倒数第二位放在数组的下标3处,然后遍历数组即可。 523 | 524 | 3. **java代码实现:** 525 | 526 | 527 | ``` 528 | public static void RadixSort(int A[],int temp[],int n,int k,int r,int cnt[]){ 529 | 530 | //A:原数组 531 | //temp:临时数组 532 | //n:序列的数字个数 533 | //k:最大的位数2 534 | //r:基数10 535 | //cnt:存储bin[i]的个数 536 | 537 | for(int i=0 , rtok=1; i=0;j--){ //重点理解 552 | cnt[(A[j]/rtok)%r]--; 553 | temp[cnt[(A[j]/rtok)%r]] = A[j]; 554 | } 555 | for(int j=0;j Written with [StackEdit](https://stackedit.io/). 565 | --------------------------------------------------------------------------------