-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
499 lines (499 loc) · 448 KB
/
search.xml
1
2
3
4
5
6
7
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Git-subtree&submodule拆分与管理]]></title>
<url>%2FGit-subtree%26submodule%E6%8B%86%E5%88%86%E4%B8%8E%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[随着业务需求越来越庞大,Git仓库包含很多可以独立拆分的Module,因此拆分出独立的模块出来,将子目录作为一个新的仓库,并且保留Log记录,同时方便其他工程调用 仓库拆分手动强拆直接copy一份目标目录代码,非常简单,但是会导致Log记录缺失。 git filter-branch clone一份原仓库,并且删除remote 123> git clone <big-repo> <new-repo>> cd <new-repo>> git remote rm origin 拆分 1234// 会带着tag记录> git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter <name-of-folder> -- --all// 没有带tag> git filter-branch -f --prune-empty --subdirectory-filter <name-of-folder> 参数说明:12345这条命令同样会过滤所有历史提交,只保留所有对指定子目录有影响的提交,并将该子目录设为该仓库的根目录。这里说明各下个参数的作用:--tag-name-filter 该参数控制我们要如何处理旧的 tag,cat 即表示原样输出;--prune-empty 删除空的(对子目录没有影响的)提交;--subdirectory-filter 指定子目录路径;-- --all 该参数必须跟在 -- 后面,表示对所有分支进行操作。如果你只想保存当前分支,也可以不添加此参数。 清理.git当上述命令执行完毕后,就可以看到本地的新仓库已经是原仓库子目录中的内容了,且保留了关于该子目录所有的提交历史。不过只是这样的话新仓库中的.git 目录里还是保存有不少无用的东西,我们需要将其清除掉以减小新仓库的体积(如果你用subtree 的方法的话是不需要执行这一步的)。依次执行以下命令: 1234> git reset --hard> git for-each-ref --format="%(refname)" refs/original/ |xargs -n 1 git update-ref -d> git reflog expire --expire=now --all> git gc --aggressive --prune=now 推送新仓库到远端 12345cd到<new-repo>// 添加远端地址:> git remote add origin <new-git-url>// 推送到远端:> git push -u origin master git subtreegit subtree比上面的方法都简单,需要高版本的git支持,1.8 进入 所在的目录,创建一个的临时分支1> git subtree split -P <name-of-folder> -b <name-of-new-branch> 说明:分离目录,把它作为一个名字是的branch注意:有时候偶尔会没有拆分,会拉取原仓库所有的log,这个时候就要注意一下,拆分的目录的Log会少一些;不成功多尝试几次 创建一个新的 git 仓库,用于分离的目录 123> mkdir <new-repo>> cd ../<new-repo>> git init 拉取原仓库</path/to/big-repo>的临时分支到新的仓库中 master 分支 1> git pull </path/to/big-repo> <name-of-new-branch> 推送到远程 12345cd到<new-repo>// 添加远端地址:> git remote add origin <new-git-url>// 推送到远端:> git push -u origin master document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Gradle多渠道代码]]></title>
<url>%2FGradle%E5%A4%9A%E6%B8%A0%E9%81%93%E4%BB%A3%E7%A0%81%2F</url>
<content type="text"><![CDATA[一套代码衍生多套代码,打包不同的APK 当遇到需求,一套代码根据不同的客户打包成不同的logo或者不同的UI风格时,Android Studio中搭配gradle,使用productFlavors可以很好解决。buildType也可以,但是一般处理的是编译配置。 常见的几种情况: 资源文件assets、res,替换logo、图片、strings AndroidManifest文件 java类文件 注意:资源文件、AndroidManifest文件在编译都可以使用main目录里的资源,因为他们是合并、替换或者覆盖;但是java文件必须在每个flavor中存在,main目录则不能存有,会报错,因为java类只能是唯一。 异常123451.All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html解决方法:defaultConfig{}加入flavorDimensions "default"2.Duplicate class解决方法:去掉main中重复的java类 示例123456789101112131415161718// 多渠道productFlavors { system { } normal { }}// 搭配productFlavors,指定资源sourceSets { system { manifest.srcFile 'src/main/AndroidManifest_system.xml' } normal { manifest.srcFile 'src/main/AndroidManifest.xml' }} 资源简单的资源替代替换字符串,在生成的BuildConfig类中可以找到。这种方法只适用少数的替换。12345678910111213141516productFlavors { A { // 替换字符串变量 buildConfigField("String", "name", "A") // 替换资源字符串:R.string.type resValue("string", "type", "A") } B { buildConfigField("String", "name", "B") resValue("string", "type", "B") } C { buildConfigField("String", "name", "C") resValue("string", "type", "C") }} 多渠道资源替换创建多个flavor时,指向对应的资源目录:该资源目录在编译时,会与main里面的资源目录进行合并或者替换。 合并如strings.xml,先合并main和当前flavor文件里资源;遇到id名相同时,则会用flavor的资源替换带main里面的资源。 替换如assets、图片名字相同时,会被flavor的资源文件替换。 123456789101112131415161718192021222324productFlavors { A { } B { }}// 两种写法,可以指定main里面的资源路径sourceSets{ A.res.srcDirs=['src/main/res-a'] B.res.srcDirs=['src/main/res-b'] C.res.srcDirs=['src/main/res-c']}sourceSets { A { res.srcDirs = ['src/main/res-a'] } B { res.srcDirs = ['src/main/res-b'] } C { manifest.srcFile 'src/main/AndroidManifest_c.xml' res.srcDirs = ['src/main/res-c'] }} 或者创建与main同级的目录,表示一个flavor AndroidManifest该文件多渠道与资源文件多渠道有些类似,但是它是合并节点的形式。 默认的main里面有一个AndroidManifest文件,编译时的flavor也存在另外一个AndroidManifest,则会合并,根据节点tools:node配置,判断是否覆盖(replace)或者是合并(merge)等等,默认是merge。12345678<activity android:name=".MainActivity" tools:node="merge"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter></activity> 有时多个AndroidManifest时,修改flavor,但是没有达到效果,则要查看是否是tools:node配置的原因。 java文件java文件比较特殊,如果想对文件A.java进行多渠道,需要去掉main里面的文件的A.java,然后在其他所有的flavor里加入A.java。这样flavor多的时候,修改A中公共的代码部分比较麻烦,目前还没好的办法。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Gradle</tag>
<tag>多渠道</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Retrofit封装]]></title>
<url>%2FRetrofit%E5%B0%81%E8%A3%85%2F</url>
<content type="text"><![CDATA[封装Retrofit,统一管理网络请求 传送门 实现功能 get请求封装 请求头和参数统一配置,分开配置 异步统一回调接口 单个请求、单个界面请求、所有请求取消 缓存策略:在线缓存、离线缓存 下载 上传 使用引用1compile 'com.excellence:retrofit:_latestVersion' 权限123<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 网络请求 初始化可以在Application中初始化 1234567// 默认支持:ScalarsConverterFactory、RxJavaCallAdapterFactory// addLog : 是否开启Log打印// cacheEnable : 是否开启缓存,默认不开启:开启后,默认每个请求都缓存// 单个请求的缓存控制,可设置HttpRequest.Builder#cacheEnable// cache : 自定义缓存目录,设置缓存目录后,缓存自动开启,无需设置cacheEnable···new RetrofitUtils.Builder(this).baseUrl(BASE_URL).addLog(true).cacheEnable(true).build(); 创建请求 GET 1new HttpRequest.Builder().tag(this).url(REQUEST_URL).build().get(); POST 1new HttpRequest.Builder().tag(this).url(REQUEST_URL).build().postForm(); 下载 1new HttpRequest.Builder().tag(this).url(REQUEST_URL).build().download(); 上传 1new HttpRequest.Builder().tag(this).url(REQUEST_URL).build().uploadFile(); 使用注意 缓存默认位置/sdcard/Android/data/YourPackageName/cache/,可以初始化时自定义 如果想针对单个请求,不使用缓存,可以考虑添加头信息mHeaders.put(DOWNLOAD, DOWNLOAD),当做下载请求,则不会使用缓存机制 混淆123456789101112###-----------保持 retrofit client 不被混淆-------------keep class com.excellence.retrofit.RetrofitHttpService { *; }###-----------保持 retrofit 不被混淆-------------dontwarn retrofit2.**-keep class retrofit2.** { *; }-dontwarn javax.annotation.**###-----------保持 okhttp 不被混淆-------------dontwarn com.squareup.okhttp3.**-keep class com.squareup.okhttp3.** { *;}-dontwarn okio.** 版本更新 版本 描述 1.0.5 判断请求队列是否存在 2018-10-30 1.0.4 可自定义OkHttp,开放请求队列的增加与删除 2018-10-25 1.0.3 开放retrofit对象,可自定义创建请求Service 2018-8-1 1.0.2 分离下载封装,优化请求接口 2018-7-5 1.0.1 优化请求和新增异常处理 2018-3-13 1.0.0 创建网络请求:GET、POST、下载、上传 2017-11-14 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Retrofit</tag>
<tag>网络请求</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android图片加载策略-ImageLoader]]></title>
<url>%2FAndroid%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E7%AD%96%E7%95%A5-ImageLoader%2F</url>
<content type="text"><![CDATA[封装图片加载,任意切换图片库 传送门 统一管理图片加载库,随意切换图片加载框架 Fresco Picasso Glide Universal-ImageLoader Volley 封装 多个图片加载库切换 图片加载进度回调 自定义配置(如占位图片、错误占位图片、缓存目录、大小等) 清除缓存 使用 独立依赖库 12345implementation 'com.excellence:imageloader:_latestVersion'// 下面图库三选一,减小安装包大小implementation 'com.excellence:imageloader-fresco:_latestVersion'implementation 'com.excellence:imageloader-picasso:_latestVersion'implementation 'com.excellence:imageloader-glide:_latestVersion' 权限 1234<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> API 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778// 初始化,不同的加载器,有部分独立的方法// 可以自定义实现ImageLoader接口,创建新的图库加载器ImageLoaderOptions options = new ImageLoaderOptions.Builder().isLogEnable(true).isCache(false).build();mImageLoader = new FrescoImageLoader(this, options);mImageLoader = new PicassoImageLoader(this, options);mImageLoader = new GlideImageLoader(this, options);// 统一的接口public interface ImageLoader{ /** * 加载资源图片 * * @param view * @param resId */ void loadImage(@NonNull ImageView view, @DrawableRes int resId); void loadImage(@NonNull ImageView view, @DrawableRes int resId, IListener listener); /** * 加载资源图片,占位图片,错误图片 * * @param view * @param resId * @param placeholderResId * @param errorResId */ void loadImage(@NonNull ImageView view, @DrawableRes int resId, @DrawableRes int placeholderResId, @DrawableRes int errorResId); void loadImage(@NonNull ImageView view, @DrawableRes int resId, @DrawableRes int placeholderResId, @DrawableRes int errorResId, IListener listener); /** * 加载本地图片 * * @param view * @param file */ void loadImage(@NonNull ImageView view, @NonNull File file); void loadImage(@NonNull ImageView view, @NonNull File file, IListener listener); /** * 加载本地图片,占位图片,错误图片 * * @param view * @param file * @param placeholderResId * @param errorResId */ void loadImage(@NonNull ImageView view, @NonNull File file, @DrawableRes int placeholderResId, @DrawableRes int errorResId); void loadImage(@NonNull ImageView view, @NonNull File file, @DrawableRes int placeholderResId, @DrawableRes int errorResId, IListener listener); /** * 加载网络图片 * * @param view * @param url */ void loadImage(@NonNull ImageView view, @NonNull String url); void loadImage(@NonNull ImageView view, @NonNull String url, IListener listener); /** * 加载网络图片,占位图片,错误图片 * * @param view * @param url * @param placeholderResId * @param errorResId */ void loadImage(@NonNull ImageView view, @NonNull String url, @DrawableRes int placeholderResId, @DrawableRes int errorResId); void loadImage(@NonNull ImageView view, @NonNull String url, @DrawableRes int placeholderResId, @DrawableRes int errorResId, IListener listener); void clearCache();} Fresco1234567891011121314implementation 'com.facebook.fresco:fresco:1.9.0'// 在 API < 14 上的机器支持 WebP 时,需要添加compile 'com.facebook.fresco:animated-base-support:0.12.0'// 支持 GIF 动图,需要添加compile 'com.facebook.fresco:animated-gif:0.12.0'// 支持 WebP (静态图+动图),需要添加compile 'com.facebook.fresco:animated-webp:0.12.0'compile 'com.facebook.fresco:webpsupport:0.12.0'// 仅支持 WebP 静态图,需要添加compile 'com.facebook.fresco:webpsupport:0.12.0' 123Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);draweeView.setImageURI(uri); Picasso1implementation 'com.squareup.picasso:picasso:2.71828' 12345Picasso.get() .load(url) .resize(50, 50) .centerCrop() .into(imageView) 缓存路径:data/data/your package name/cache/picasso-cache/(默认路径) Glide12implementation 'com.github.bumptech.glide:glide:4.8.0'annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 1234567Glide.with(getContext()) .load(url) .skipMemoryCache(true) .placeholder(drawable) .centerCrop() .animate(animator) .into(img); Universal-ImageLoaderVolley版本更新 版本 描述 1.0.0 封装Fresco、Picasso、Glide图库,简单加载图片 2018-10-11 感谢 ladingwu hpdx peng8350 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>ImageLoader</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RxJava+Retrofit常用的使用场景]]></title>
<url>%2FRxJava-Retrofit%E5%B8%B8%E7%94%A8%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%2F</url>
<content type="text"><![CDATA[记录RxJava + Retrofit一些常用的使用场景 简介RxJava是非常强大的函数响应式编程库,Retrofit是非常强大的网络请求框架。强强结合,最佳结合体验,让代码更丝滑。 RxJava的强大,不仅仅是结合网络请求,在其他的场景,如按钮防抖动、定时器、数据库异步操作等等,应用也非常广泛,强烈建议学习使用。 使用场景轮询请求 定时轮询 123456789// 每隔5分钟请求TokenObservable.interval(5, TimeUnit.MINUTES).flatMap(new Func1<Object, Observable<Object>>() { @Override public Observable<Object> call(Object obj) { return mService.getToken(); }}).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(); 条件轮询 1234567891011121314// 结果不为空时,则停止,否则继续请求Observable.from(list).flatMap(new Func1<Object, Observable<Object>>() { @Override public Observable<Object> call(Object obj) { return mService.getToken(); }}).takeUntil(new Func1<Response<String>, Boolean>() { @Override public Boolean call(Response<String> result) { return result.code() == HTTP_OK && isNotEmpty(result); }}).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(); 链式请求RxJava配合链式请求,就不需要使用嵌套的形势,一层嵌一层在回调里去请求了。12345678910111213141516171819// 登陆 -> 请求Token -> 请求分类 -> 请求列表mService.login(mUrl).flatMap(new Func1<Object, Observable<Object>>() { @Override public Observable<Object> call(Object obj) { return mService.getToken(); }}).flatMap(new Func1<Object, Observable<Object>>() { @Override public Observable<Object> call(Object obj) { return mService.getCategory(); }}).flatMap(new Func1<Object, Observable<Object>>() { @Override public Observable<Object> call(Object obj) { return mService.getList(); }}).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(); 多个请求结果合并1234Observable<Object> observable1 = mServer.getCategory();Observable<Object> observable2 = mServer.getChannel();Observable.zip(observable1, observable2).subscribe(); 多个请求结果不合并该场景会存在一个问题,如果中途某个请求出现异常,则会中断,后续不再请求。暂时没有想到什么办法避免或者是出现异常继续请求。 1234567Observable.from(appInfoList).flatMap(new Func1<AppLog, Observable<LogInfo>>() { @Override public Observable<LogInfo> call(AppLog appLog) { Map<String, String> params = new HashMap<>(); return mInstance.mHttpService.registerLog(REGISTER_APP_LOG, params); } }).subscribe(); document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>RxJava</tag>
<tag>Retrofit</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android功守道-so库混淆加密]]></title>
<url>%2FAndroid%E5%8A%9F%E5%AE%88%E9%81%93-so%E5%BA%93%E6%B7%B7%E6%B7%86%E5%8A%A0%E5%AF%86%2F</url>
<content type="text"><![CDATA[Android NDK开发中加密so库,旨为保护自己的开发成果。 so反编译使用IDA工具反编译so文件,破解分析时,很容易查看到so文件里,被Java层调用的函数,以及一些加密算法等等信息,进一步破解应用代码。但是这种是我们不想见到的,我们希望把一些核心功能或者数据加密,如key,这就引入了so文件加固。 so加固原理:JNI_OnLoad方法,当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库,然后首先试图找到”JNI_OnLoad”函数,如果该函数存在,则调用它。JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。 CMakeList开启配置,隐藏符号表 编辑Android Studio工具的CMakeList文件,添加下面两个语句: 12set(CMAKE_C_VISIBILITY_PRESET hidden) # C语言写法set(CMAKE_CXX_VISIBILITY_PRESET hidden) # C++写法 注册JNI_OnLoad方法,函数对照表 记录调用C代码的Java文件路径 1static const char *JNI_REG_CLASS = "com/excellence/test/tool/KeyUtils"; gMethods方法替换将native方法getKey,指向gK,这样反编译就查不到getKey方法。 123static JNINativeMethod gMethods[] = { {"getKey", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void *) gK},}; JNINativemethod中结构体的定义: 12345678typedef struct { // Java中函数的名字 const char* name; // 描述了Java中函数的参数和返回值 const char* signature; // fnPtr是函数指针,指向native函数。前面都要接 (void *) void* fnPtr; } JNINativeMethod; 在JNI调用的C文件里注册 123456789101112131415161718192021222324252627282930313233343536static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE;}static int registerNatives(JNIEnv *env) { if (!registerNativeMethods(env, JNI_REG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) return JNI_FALSE; return JNI_TRUE;}jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); //注册 if (!registerNatives(env)) { return -1; } return JNI_VERSION_1_6;} 这样加固了so文件,就可以做到一定的保护作用,使用IDA工具查看,会起到混淆的作用。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>混淆</tag>
</tags>
</entry>
<entry>
<title><![CDATA[AndroidFFmpeg命令使用]]></title>
<url>%2FAndroidFFmpeg%E5%91%BD%E4%BB%A4%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[FFmpeg命令在Android中的使用 项目传送门:FFmpeg for Android 1implementation 'com.excellence:ffmpeg:_latestVersion' AndroidFFmpeg使用12345678910111213141516171819202122232425262728293031323334353637// 初始化,默认:不限制并发线程数;指令超时10s终止FFmpeg.init(context);// 自定义初始化参数:超时1s终止FFmpeg.init(context, new CommanderOptions.Builder().setTimeOut(1000).build())// 获取FFmpeg工具路径FFmpeg.checkFFmpeg()// 创建执行命令FFmpeg.addTask(cmd, new IListener() { @Override public void onPre(String command) { Log.i(TAG, "onPre: " + command); } @Override public void onProgress(String message) { Log.i(TAG, "onProgress: " + message); } @Override public void onError(Throwable t) { t.printStackTrace(); } @Override public void onSuccess(String message) { Log.i(TAG, "onSuccess: " + message); }});// 终止命令CommandTask.discard()// 终止所有命令FFmpeg.destroy() FFmpeg命令FFMpeg官网 Windows工具下载,解压后,把FFmpeg加入到环境变量中,可在Windows上使用FFmpeg 命令选项 选项参数 基本语法格式: 1ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}... 常用选项 1234567891011-version: 显示版本信息。-formats: 显示所有有效的格式。-decoders: 显示所有有效解码器。-encoders: 显示所有有效的编码器。-bsfs: 显示有效的数据流滤镜。-pix_fmts: 显示有效的像素格式。 主要选项 1234567891011121314151617181920-f fmt(input|output): 指定输入或者输出文件格式,可依据扩展名自动指定。-i filename(input): 指定输入文件。-y (global): 默认自动覆盖输出文件。-n (global): 不覆盖输出文件,如果输出文件已存在则立即退出。-t duration(input|output): 限制输入/输出的时间。 如果用于输入选项,就是限定从输入中读取多少时间的数据; 如果用于输出选项,则表示写入多少时间数据后就停止。 duration 可以是以秒为单位的数值活着 hh:mm:ss[.xxx] 格式的时间值。 -to 和 -t 是互斥的,-t 有更高的优先级。-to time_stop(output): 写入 time_stop 时间后就停止。 time_stop 可以是以秒为单位的数值或者 hh:mm:ss[.xxx] 格式的时间值。-fs limit_size(output): 设置输出文件大小限制,单位是字节(bytes)。-ss time_off(input|output): 指定输入文件或输出文件的开始位置。 视频选项 12345678910-vframes number(output): 设置输出文件的帧数。-r rate(input|output): 设置帧率(Hz 值)。-s size(input|output): 设置帧的尺寸。数据格式是 WxH,即宽度值x高度值。-aspect aspect(output): 指定视频的长宽显示比例。格式为浮点数字符串或者 num:den 格式字符串。 如"4:3","16:9","1.333"等。-vcodec codec(output): 设置视频编码器。 音频选项 123456789-aframes number(output): 设置输出文件的帧数。-ar rate(input|output): 设置音频的采样率,单位为 Hz。-aq quality(output): 设置音频品质(编码指定为 VBR)。-ac channels(input|output): 设置音频通道数。-af filtergraph(output): 对音频使用 filtergraph 滤镜效果。 常用命令 获取视频信息 1ffprobe -v quiet -print_format json -show_format -show_streams inputfile 视频截图 12345678ffmpeg -i input_file -y -f mjpeg -ss 1 -t 0.001 -s widthxheight output_filei: 源文件y: 覆盖输出文件f: 截图格式ss: 起始位置,单位秒t: 截图时间,单位秒s: 图片宽x高 每隔 1 秒截一张图 1ffmpeg -i input.mp4 -f image2 -vf fps=fps=1 out%d.jpg 每隔 20 秒截一张图 1ffmpeg -i input.mp4 -f image2 -vf fps=fps=1/20 out%d.jpg 将视频的前 30 帧转换成一个 Gif 1ffmpeg -i input.mp4 -vframes 30 -y -f gif output.gif 从视频中生成 Gif 1ffmpeg -i input.mp4 -t 10 -pix_fmt rgb24 output.gif 转换视频为图片(每帧一张图) 1ffmpeg -i input.mp4 out%d.jpg 图片转换为视频 1ffmpeg -f image2 -i out%d.jpg -r 25 video.mp4 提取视频的关键帧 1ffmpeg -i input.mp4 -vf select='eq(pict_type\,I)' -vsync 2 -s 160x90 -f image2 out-%02d.jpeg 分解视频音频流 12345// 分离视频流ffmpeg -i input_file -vcodec copy -an output_file_video// 分离音频流ffmpeg -i input_file -vcodec copy -vn output_file_audio 视频转码 12// 转码为码流原始文件ffmpeg -i input.mp4 -vcodec h264 -an -f m4v test.264 视频封装 1ffmpeg -i video_file -i audio_file -vcodec copy -acodec copy output_file 视频录制 12345// 录制视频流ffmpeg -i rtsp://hostname/stream -vcodec copy output.avi// 通过电脑摄像头录制ffmpeg -f avfoundation -framerate 30 -i "0" -f mpeg1video -b 500k -r 20 -vf scale=640:360 output.avi 版本更新 版本 描述 1.0.0 集成FFmpeg命令行执行 2017-8-17 感谢 WritingMinds, WritingMinds hiliving c060604 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>FFmpeg</tag>
</tags>
</entry>
<entry>
<title><![CDATA[基于Android的HTTP下载速度提升]]></title>
<url>%2F%E5%9F%BA%E4%BA%8EAndroid%E7%9A%84HTTP%E4%B8%8B%E8%BD%BD%E9%80%9F%E5%BA%A6%E6%8F%90%E5%8D%87%2F</url>
<content type="text"><![CDATA[只为极速下载 因素HTTP下载速度受限于两个因素,这里不讨论服务器限制以及多线程 带宽网速 文件写入速度 速度提升由于带宽是固定的,因此,文件读写速度是下载速度的关键。 BufferedRandomAccessFile普遍的,我们使用RandomAccessFile进行断点下载,对文件读写操作,线程对磁盘的读写非常频繁,导致写入文件非常慢,从而导致下载速度慢。因此,采用具有缓冲的RandomAccessFile,能快速降低磁盘的IO。 以下是测试速度对比,转载自https://blog.csdn.net/hpb21/article/details/51270873 读 写 耗时(s) RandomAccessFile RandomAccessFile 95.848 BufferedInputStream + DataInputStream BufferedOutputStream + DataOutputStream 2.935 BufferedRandomAccessFile BufferedRandomAccessFile 0.401 块传输通过对比,FileChannel写文件速度优于普通的复制文件方法 写法12345678910111213141516171819202122232425262728293031/** * 使用块传输,直接通过追加的形式,写入到文件里 * * @param inputStream */private void dynamicTransmission(InputStream inputStream) throws Exception{ FileOutputStream outputStream = new FileOutputStream(mTempFile, true); FileChannel channel = outputStream.getChannel(); ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream); ByteBuffer buffer = ByteBuffer.allocate(STREAM_LEN); int read; while ((read = readableByteChannel.read(buffer)) != -1) { buffer.flip(); channel.write(buffer); buffer.compact(); mTaskEntity.downloadLen += read; onProgressChange(mTaskEntity.fileSize, mTaskEntity.downloadLen); if (mTaskEntity.isCancel) { onCancel(); break; } } outputStream.close(); channel.close(); readableByteChannel.close();} 示例Downloader,欢迎star! 使用上述两种方式,下载速度明显提高了。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>HTTP</tag>
<tag>Downloader</tag>
</tags>
</entry>
<entry>
<title><![CDATA[AndroidSDK-原生Settings添加菜单项]]></title>
<url>%2FAndroidSDK-%E5%8E%9F%E7%94%9FSettings%E6%B7%BB%E5%8A%A0%E8%8F%9C%E5%8D%95%E9%A1%B9%2F</url>
<content type="text"><![CDATA[Android N原生Settings应用添加自定义的一级菜单项 被动方式 被动方式:修改被调用应用的AndroidManifest.xml 根据菜单项Google的启发,因此猜想是否可以通过这种方式添加选项。 源码追踪 追踪:Settings\src\com\android\settings\SettingsActivity.java 追踪父类:SettingsLib\src\com\android\settingslib\drawer\SettingsDrawerActivity.java 最终定位:SettingsLib\src\com\android\settingslib\drawer\TileUtils.java,因此根据源码,反推出原生Settings通过遍历AndroidManifest.xml文件,添加符合要求的菜单项 修改AndroidManifest.xml 12345678910111213141516<activity android:name=".app.AppsActivity" android:launchMode="singleTask"> <!-- 核心,特别重要,必须添加:用于Settings过滤出菜单项Category --> <intent-filter> <action android:name="com.android.settings.MANUFACTURER_APPLICATION_SETTING" /> </intent-filter> <!-- 设置显示的title --> <meta-data android:name="com.android.settings.title" android:resource="@string/app_name" /> <!-- 设置显示的小标题 --> <meta-data android:name="com.android.settings.summary" android:resource="@string/app_name" /> <!-- 设置显示的图标 --> <meta-data android:name="com.android.settings.icon" android:resource="@drawable/load_err" /> <!-- 核心,特别重要,必须添加:表示添加一个菜单选项,用于Settings分类,归属于“个人”的选项下,也可以是其他的:系统等等 --> <meta-data android:name="com.android.settings.category" android:value="com.android.settings.category.personal"/></activity> 验证猜想 通过反编译Google的apk发现,里面的AndroidManifest里面行数L2536-L2543的代码与上面修改的代码是一样的,因此,猜想是正确的。 拓展 菜单项的所属分类修改value值,可以选择分类:personal、system,其他的分类未验证 123<meta-data android:name="com.android.settings.category"// android:value="com.android.settings.category.systemandroid:value="com.android.settings.category.personal"/> 菜单项的顺序修改优先级,可以调整顺序,等级越高的,在上面 12<intent-filter android:priority="4"></intent-filter> 注意 通过验证:需要预置apk才有效,安装的方式是没有作用 主动方式 主动的方式:通过修改原生Settings的代码,也可以添加一级、二级菜单项,但是这种方式比较繁琐复杂。如果只是简单在原生Settings上添加一级菜单项,建议使用被动方式。 感谢非常感谢 迷途羔羊 的帮助😁😁 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>AndroidSDK</tag>
<tag>Settings</tag>
</tags>
</entry>
<entry>
<title><![CDATA[AndroidStudio统一管理gradle脚本]]></title>
<url>%2FAndroidStudio%E7%BB%9F%E4%B8%80%E7%AE%A1%E7%90%86gradle%E8%84%9A%E6%9C%AC%2F</url>
<content type="text"><![CDATA[AndroidStudio统一管理gradle编译脚本 目的当工程集成很多Modules时,每个Module都有一个build.gradle,并且带有如下重复的代码;对每个build.gradle修改很麻烦,因此统一管理build.gradle文件是必要的 123456789101112131415161718// 重复代码android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName "1.0" }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.2.1'} 优化代码 使用相同的编译配置 统一管理远程依赖 减少sync project次数 本地配置 在项目根目录下创建config.gradle文件,作为管理配置文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236import java.text.SimpleDateFormatimport java.util.regex.Matcherimport java.util.regex.Pattern ext { // 插件 plugins = [ application : "com.android.application", library : "com.android.library", maven : "com.github.dcendents.android-maven", bintray : "com.jfrog.bintray", novoda : "com.novoda.bintray-release", greendao : "org.greenrobot.greendao", "greendao-gradle" : "org.greenrobot:greendao-gradle-plugin:3.2.2" ] // 配置 android = [ /*************************原生配置*************************/ compileSdkVersion : 25, buildToolsVersion : "25.0.0", minSdkVersion : 17, targetSdkVersion : 23, versionCode : getVersionCode(), versionName : getVersionName(), /*************************自定义配置*************************/ androidSupportSdkVersion: "23.0.0" ] // 依赖 dependencies = [ /*************************原生依赖*************************/ "appcompat-v7" : "com.android.support:appcompat-v7:${android["androidSupportSdkVersion"]}", "support-v4" : "com.android.support:support-v4:${android["androidSupportSdkVersion"]}", "cardview-v7" : "com.android.support:cardview-v7:${android["androidSupportSdkVersion"]}", "recyclerview-v7" : "com.android.support:recyclerview-v7:${android["androidSupportSdkVersion"]}", "design" : "com.android.support:design:${android["androidSupportSdkVersion"]}", "annotations" : "com.android.support:support-annotations:${android["androidSupportSdkVersion"]}", "gridlayout-v7" : "com.android.support:gridlayout-v7:${android["androidSupportSdkVersion"]}", "constraint-layout" : "com.android.support.constraint:constraint-layout:1.0.2", /*************************第三方依赖*************************/ // https://github.com/square/retrofit "retrofit2" : "com.squareup.retrofit2:retrofit:2.4.0", "converter-scalars" : "com.squareup.retrofit2:converter-scalars:2.4.0", "converter-gson" : "com.squareup.retrofit2:converter-gson:2.4.0", "adapter-rxjava" : "com.squareup.retrofit2:adapter-rxjava:2.4.0", "adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:2.4.0", // https://github.com/square/okhttp "okhttp" : "com.squareup.okhttp3:okhttp:3.11.0", // https://github.com/greenrobot/greenDAO "greendao" : "org.greenrobot:greendao:3.2.2", // https://github.com/yuweiguocn/GreenDaoUpgradeHelper "greendao-helper" : "com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.1.0", // https://github.com/bumptech/glide "glide" : "com.github.bumptech.glide:glide:4.8.0", // https://github.com/square/picasso "picasso" : "com.squareup.picasso:picasso:2.71828", // https://github.com/facebook/fresco "fresco" : "com.facebook.fresco:fresco:1.10.0", // https://github.com/greenrobot/EventBus "eventbus" : "org.greenrobot:eventbus:3.1.1", // https://github.com/BuglyDevTeam/Bugly-Android "bugly" : "com.tencent.bugly:crashreport:2.6.6.1", "bugly-native" : "com.tencent.bugly:nativecrashreport:3.3.1", // https://bintray.com/android/android-utils/com.android.volley.volley "volley" : "com.android.volley:volley:1.1.1", // https://github.com/ReactiveX/RxJava "rxjava" : "io.reactivex:rxjava:1.3.8", "rxjava2" : "io.reactivex.rxjava2:rxjava:2.2.2", "rxandroid" : "io.reactivex:rxandroid:2.1.0", "rxandroid2" : 'io.reactivex.rxjava2:rxandroid:2.0.2', // https://github.com/JakeWharton/RxBinding "rxbinding" : 'com.jakewharton.rxbinding2:rxbinding:2.2.0', // https://github.com/google/gson "gson" : "com.google.code.gson:gson:2.8.5", // https://github.com/apache/commons-lang "commons-lang3" : "org.apache.commons:commons-lang3:3.8", // https://github.com/square/leakcanary "leakcanary" : "com.squareup.leakcanary:leakcanary-android:1.6.2", "leakcanary-release" : "com.squareup.leakcanary:leakcanary-android-no-op:1.6.2", "leakcanary-fragment" : "com.squareup.leakcanary:leakcanary-support-fragment:1.6.2", // https://github.com/YoKeyword/Fragmentation "fragmentation" : "me.yokeyword:fragmentation:1.3.6", /*************************个人依赖*************************/ // https://github.com/VeiZhang/BaseToolsLibrary "basetools" : "com.excellence:basetools:1.2.6", // https://github.com/VeiZhang/Permission "permission" : "com.excellence:permission:1.0.1", // https://github.com/VeiZhang/RetrofitClient "retrofit-client" : "com.excellence:retrofit:1.0.5", // https://github.com/VeiZhang/QSkinLoader "skinloader" : "com.excellence:skinloader:1.2.2", // https://github.com/VeiZhang/ToastKit "toast" : "com.excellence:toast:1.1.0", // https://github.com/VeiZhang/MailSender "mailsender" : "com.excellence:mailsender:1.0.0", // https://github.com/VeiZhang/Downloader "downloader" : "com.excellence:downloader:1.2.0", // https://github.com/VeiZhang/AppStatistics "app-statistics" : "com.excellence:app-statistics:1.0.1", // https://github.com/VeiZhang/AndroidExec "exec" : "com.excellence:exec:1.1.0", // https://github.com/VeiZhang/AndroidFFmpeg "ffmpeg" : "com.excellence:ffmpeg:1.1.0", // https://github.com/VeiZhang/ImageLoader "imageloader" : "com.excellence:imageloader:1.0.0", "imageloader-fresco" : "com.excellence:imageloader-fresco:1.0.0", "imageloader-picasso" : "com.excellence:imageloader-picasso:1.0.0", "imageloader-glide" : "com.excellence:imageloader-glide:1.0.0" ] }/***********************APP版本控制的通用方法***********************//** * svn * 直接读取svn版本号作为版本控制 *//** * git * * git tag作为版本名称 * git 版本号有两种方法 * ①版本号作为我们内部开发的标识,一般它是+1递增的,每一次发版我们就会打一个tag,tag的数量也会增加1个,和我们版本号的递增逻辑是符合的,tag数量+1,版本号也会跟着+1 * ②还有一种是把提交次数作为versionCode,不推荐。相比较①,②比较难找,因为tag的数量不会很多 *//** * 获取版本 * @return */def getVersionName() { def date = getDate() def version = getSvnVersionCode() if (version != 0) { return "1.0.${version} [${date}]" } version = getGitTag() if (version != 0) { return "${version} [${date}]" } if (version == 0) { version = 1 } /** * 错误的版本信息,请检查 */ return "0.${version} [${date}]"}/** * 获取版本号 * @return */def getVersionCode() { def versionCode = getSvnVersionCode() if (versionCode == 0) { versionCode = getGitVersionCode() } if (versionCode == 0) { versionCode = 1 } return versionCode}/***********************读取Git信息***********************//** * 读取git tag * @return tag */def getGitTag() { try { def stdout = new ByteArrayOutputStream() exec { commandLine 'git', 'describe', '--abbrev=0', '--tags' standardOutput = stdout } return stdout.toString().split("\n") } catch (e) { println e.getMessage() } return 0}/** * 以git tag的数量作为其版本号 * @return tag的数量 */def getGitVersionCode() { try { def stdout = new ByteArrayOutputStream() exec { commandLine 'git', 'tag', '--list' standardOutput = stdout } return stdout.toString().split("\n").size() } catch (e) { println e.getMessage() } return 0}/***********************读取SVN信息***********************//** * 根据svn提交版本生成版本号 * @return */def getSvnVersionCode() { try { def process = ("svnversion -c " + getBuildDir().parent).execute() process.waitFor() def version = process.in.text Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)\\D") Matcher matcher = pattern.matcher(version) if (matcher.find()) { version = matcher.group(matcher.groupCount()) } return Integer.parseInt(version) } catch (e) { println e.getMessage() } return 0}/** * 获取日期 * @return */def getDate() { String date = new SimpleDateFormat("MMddyyyy").format(new Date()) return date} 在项目根目录的build.gradle中引用,供其他的Module使用 注意: 如果想使用ext的值,则只能在项目根目录的build.gradle中引用 想让单独的Module使用,则在该Module的build.gradle里引入,但是此时不能使用ext的值,否则会提示无法找到”Error:Cannot get property ‘xxx’ on extra properties extension as it does not exist” 1apply from: "config.gradle" 在Module目录的build.gradle中使用变量 远程配置远程配置配置其他步骤与本地配置是一样,不同的是引用的方式,导入的不是路径里的文件,而是一个文件链接 1apply from: "https://github.com/VeiZhang/build.gradle/blob/master/config.gradle?raw=true" 继承方式本地、远程的配置都只是保存了变量,但是如果想引用gradle文件里面的函数,其实很简单,与上面是相同的做法,必须注意的是,apply from: 放置的顺序很重要! 注意:引入位置在文件的开头、中间、结尾等处使用。因为gradle脚本编译是顺序执行的,如果父脚本与子脚本有相同的方法,此时父脚本引入的顺序就非常重要,不同的位置,执行先后不一样。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>AndroidStudio</tag>
<tag>gradle</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android二维码生成与扫描]]></title>
<url>%2FAndroid%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%94%9F%E6%88%90%E4%B8%8E%E6%89%AB%E6%8F%8F%2F</url>
<content type="text"><![CDATA[老板,请尽情用红包来蹂躏我吧!!!😘😘😘 扫码场景现在很普遍,支付宝付款、微信付款、扫码等,两种扫码方式:zxing、zbar。了解与使用,方便集成到项目中。 介绍 zxing google推出用于识别多种格式条形码的开源项目,维护中 支持更多的码制:datamatix、PDF417 zbar 主要用C来写,速度极快,推出iPhone的SDK和Android的调用方法JNI,不在维护 不能很好支持PDF417,但是在源码中有对PDF417码的处理 比较两者的扫码速度,实践证明,zbar的扫码速度优于zxing。 感谢、集成由于源代码并不是所有的模块都是需要的,因此裁剪,优化是非常必要的。感谢bingoogolapple,裁剪了源代码,并且打包成依赖库:生成二维码、扫描二维码。两种扫码方式之间切换非常方便。 zxing123dependencies { implementation 'cn.bingoogolapple:bga-qrcode-zxing:latestVersion'} zbar123dependencies { implementation 'cn.bingoogolapple:bga-qrcode-zbar:latestVersion'} document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>二维码</tag>
<tag>zxing</tag>
<tag>zbar</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Merry Christmas-2017]]></title>
<url>%2FMerry-Christmas%2F</url>
<content type="text"><![CDATA[123456789101112131415161718192021222324 __________________________________________________| _ || /|,/ _ _ _ / ` /_ _ . _ _/_ _ _ _ _||/ / /_' / / /_/ /_, / / / / _\ / / / / /_| _\ || _/ || ~~** Tiimor **~~ ||__________________________________________________| ___ /` `'. / _..---; | /__..._/ .--.-. |.' e e | ___\_|/____ (_)'--.o.--| | | | .-( `-' = `-|____| |____| / ( |____ ____| | ( |_ | | __| | '-.--';/'/__ | | ( `| | '. \ )"";--`\ / \ ; |--' `;.-' |`-.__ ..-'--'`;..--'` :*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~* document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
</entry>
<entry>
<title><![CDATA[Android功守道-反编译VS混淆与加壳]]></title>
<url>%2FAndroid%E5%8A%9F%E5%AE%88%E9%81%93-%E5%8F%8D%E7%BC%96%E8%AF%91VS%E6%B7%B7%E6%B7%86%E4%B8%8E%E5%8A%A0%E5%A3%B3%2F</url>
<content type="text"><![CDATA[反编译并不是为了去破解人家辛辛苦苦开发的app;混淆与加壳也是为了在一定程度上保护自己的开发成果。反编译、混淆、加壳是非常有用的技能。 记录一下个人日常使用 反编译反编译工具 apktool资源文件获取,可以提取出图片文件和布局文件进行使用查看:apktool.bat d -f [apk文件] [输出文件夹] dex2jar将apk反编译成Java源码(classes.dex转化成jar文件):dex2jar.bat [apk文件] jd-gui查看APK中classes.dex转化成出的jar文件,即源码文件:用jd-gui.exe打开上面生成的classes.dex即可 注:AndroidKiller是一个反编译工具,一步到位,看Java源码的时候,需要借助smail转java的工具。另外将test.apk的apk文件更换后缀名为test.zip,可以提取出资源图片文件等等。 未混淆未混淆的app,反编译可以看到源码: 混淆混淆app之后,反编译得到的源码: 混淆开启App的混淆,在一定程度上保护自己辛苦开发的成果,即反编译出来的源码是a b c...这样的,但是仍然可以被反编译看到AndroidManifest和资源文件。 开启混淆在Android Studio的app工程目录里,release版本开启代码混淆,然后混淆的配置写在该目录里的proguard-rules.pro文件里 1234567release { // proguard files minifyEnabled true // add proguard cfg proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'} 配置proguard-rules配置该文件是为了防止某些功能的正常使用,代码过于混淆,会导致功能无法使用,这个时候就要保持不被混淆。比较全面的通用配置(常用的第三方框架)如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188# Add project specific ProGuard rules here.# By default, the flags in this file are appended to flags specified# in E:\AndroidTools\AndroidStudio\sdk/tools/proguard/proguard-android.txt# You can edit the include path and order by changing the proguardFiles# directive in build.gradle.## For more details, see# http://developer.android.com/guide/developing/tools/proguard.html# Add any project specific keep options here:###-----------指定代码的压缩级别-------------optimizationpasses 5###-----------是否使用大小写混合-------------dontusemixedcaseclassnames###-----------混淆时是否做预校验-------------dontpreverify###-----------混淆时是否记录日志-------------verbose###-----------忽略警告-------------ignorewarnings-keepattributes EnclosingMethod###-----------保证异常时显示行号-------------renamesourcefileattribute SourceFile-keepattributes SourceFile,LineNumberTable###-----------注解-------------keepattributes *Annotation*###-----------泛型-------------keepattributes Signature###-----------异常-------------keepattributes Exceptions###-----------去掉代码里的Log-------------assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String,int); public static *** d(...); public static *** v(...); public static *** i(...); public static *** w(...); public static *** e(...);}###-----------混淆时所采用的算法-------------optimizations !code/simplification/arithmetic,!field/*,!class/merging/*###-----------保持Activity类不被混淆-------------keep public class * extends android.app.Activity###-----------保持AppCompatActivity类不被混淆-------------keep public class * extends android.support.v7.app.AppCompatActivity###-----------保持DialogFragment类不被混淆-------------keep public class * extends android.app.DialogFragment###-----------保持Application类不被混淆-------------keep public class * extends android.app.Application###-----------保持Service类不被混淆-------------keep public class * extends android.app.Service###-----------保持BroadcastReceiver类不被混淆-------------keep public class * extends android.content.BroadcastReceiver###-----------保持ContentProvider类不被混淆-------------keep public class * extends android.content.ContentProvider###-----------保持BackupAgentHelper类不被混淆-------------keep public class * extends android.app.backup.BackupAgentHelper###-----------保持Preference类不被混淆-------------keep public class * extends android.preference.Preference###-----------保持ILicensingService类不被混淆-------------keep public class com.android.vending.licensing.ILicensingService###-----------保持 native 方法不被混淆-------------keepclasseswithmembernames class * { native <methods>;}###-----------保持自定义控件类不被混淆-------------keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet);}###-----------保持自定义控件类不被混淆-------------keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet, int);}###-----------保持自定义控件类不被混淆-------------keepclassmembers class * extends android.app.Activity { public void *(android.view.View);}###-----------保持枚举 enum 类不被混淆-------------keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String);}###-----------# 保持 Parcelable 不被混淆-------------keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *;}###-----------保持 retrofit 不被混淆-------------dontwarn retrofit2.**-keep class retrofit2.** { *; }-dontwarn javax.annotation.**###-----------保持 okhttp 不被混淆-------------dontwarn com.squareup.okhttp3.**-keep class com.squareup.okhttp3.** { *;}-dontwarn okio.**###-----------保持 GreenDao 不被混淆-------------keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {public static java.lang.String TABLENAME;}-keep class **$Properties###-----------保持 Zbar 不被混淆-------------keep class net.sourceforge.zbar.** { *; }-keep interface net.sourceforge.zbar.** { *; }-dontwarn net.sourceforge.zbar.**###-----------保持 Glide 不被混淆-------------keep public class * implements com.bumptech.glide.module.GlideModule-keep public class * extends com.bumptech.glide.module.AppGlideModule-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *;}###-----------保持 eventbus 不被混淆-------------keepattributes *Annotation*-keepclassmembers class ** { @org.greenrobot.eventbus.Subscribe <methods>;}-keep enum org.greenrobot.eventbus.ThreadMode { *; }# Only required if you use AsyncExecutor-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { <init>(java.lang.Throwable);}###-----------保持 MPAndroidChart 不被混淆-------------keep class com.github.mikephil.charting.** { *; }###-----------保持 bugly 不被混淆-------------dontwarn com.tencent.bugly.**-keep public class com.tencent.bugly.**{*;}###-----------保持 gson 不被混淆-------------keep class sun.misc.Unsafe { *; }-keep class com.google.gson.stream.** { *; }###-----------保持 Rxjava RxAndroid 不被混淆-------------dontwarn sun.misc.**-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { long producerIndex; long consumerIndex;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode;}-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode;}###-----------保持 volley 不被混淆-------------keep class com.android.volley.** { *; }-keep class com.android.volley.toolbox.** { *; }###-----------保持 本项目的gson实体类 不被混淆------------###-----------网络请求解析的实体类一定不要混淆-------------keep class your-package.entity.** { *; }# If your project uses WebView with JS, uncomment the following# and specify the fully qualified class name to the JavaScript interface# class:#-keepclassmembers class fqcn.of.javascript.interface.for.webview {# public *;#}# Uncomment this to preserve the line number information for# debugging stack traces.#-keepattributes SourceFile,LineNumberTable# If you keep the line number information, uncomment this to# hide the original source file name.#-renamesourcefileattribute SourceFile Proguard常用语法 关键字 关键字 描述 keep 保留类和类中的成员,防止它们被移除或被重命名 keepnames 保留类和类中的成员,防止它们被重命名,但当成员没有被引用时会被移除 keepclassmembers 只保留类中的成员,防止它们被移除或者被重命名 keepclassmembernames 只保留类中的成员,防止它们被重命名,但当成员没有被引用时会被移除 keepclasseswithmembers 防止拥有该成员的类和成员被移除或被重命名,前提是指明的类中的成员必须存在,如果不存在则还是会混淆 keepclasseswithmembernames 防止拥有该成员的类和成员被重命名,但当成员没有被引用时会被移除,前提是指明的类中的成员必须存在,如果不存在则还是会混淆 通配符 通配符 描述 <field> 匹配类中的所有字段 <method> 匹配类中的所有方法 <init> 匹配类中的所有构造函数 * 匹配任意长度字符,但不含包名分隔符(.);如完整包名:com.example.test.util,使用com.*、com.example.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.example.*.*、com.example.test.*;如果你不写任何其他内容,只有一个*,则表示匹配所有的字符 ** 匹配任意长度字符,并且包含包名分隔符(.);如混淆文件里的:dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包 *** 匹配任意参数类型;如void set(***)匹配任意传入的参数类型,*** get()就能匹配任意返回值的类型 ... 匹配任意长度的任意类型参数;如void test(…)匹配void test(String a)或void test(int a, String b)等等 常见规则 形如:123[关键字][类]{ [成员]} 包:com.example.test类:A 不混淆某个类 1-keep public class com.example.test.A { *; } 不混淆某个包下所有的类 1-keep class com.example.test.**{ *; } 不混淆某个类的子类 1-keep public class * extends com.example.test.A { *; } 不混淆所有类名中包含了”关键字”的类及成员 1-keep public class **.*model*.** { *; } 不混淆某个接口 1-keep class * implements com.example.test.Interface { *; } 不混淆某个类的构造方法 123-keepclassmembers class com.example.test.A { public <init>();} 不混淆某个类的特定的方法 123-keepclassmembers class com.example.test.A { public void test(java.lang.String);} 不混淆某个类的内部类 123-keep class com.example.test.A$* { *;} 混淆注意代码开启混淆之后,调试app或者遇到app异常时,打印里面显示的则是a b c...这种的异常,定位不到异常的位置,这个时候在目录build\outputs\mapping\release里,使用mapping.txt,结合SDK里的工具sdk\tools\proguard\bin\proguardgui.bat->ReTrace,进行定位异常,基本上可以定位,然后解决异常错误。 加壳加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。是应用加固的一种手法对原始二进制原文进行加密/隐藏/混淆。加壳的程序可以有效阻止对程序的反汇编分析,常用来保护软件版权,防止被软件破解。 Android Dex文件加壳原理Android Dex文件大量使用引用给加壳带来了一定的难度,但是从理论上讲,Android APK加壳也是可行的。 加壳程序:加密源程序为解壳数据、组装解壳程序和解壳数据 解壳程序:解密解壳数据,并运行时通过DexClassLoader动态加载 源程序:需要加壳处理的被保护代码 加壳的利与弊优势 保护自己核心代码算法,提高破解/盗版/二次打包的难度 还可以缓解代码注入/动态调试/内存注入攻击 劣势 影响兼容性 影响程序运行效率 常用的加壳方式 腾讯乐固 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>混淆</tag>
<tag>反编译</tag>
<tag>加壳</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android图表库-MPAndroidChart]]></title>
<url>%2FAndroid%E5%9B%BE%E8%A1%A8%E5%BA%93-MPAndroidChart%2F</url>
<content type="text"><![CDATA[MPAndroidChart图表库的使用 MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,应用起来非常灵活。MPAndroidChart显得更为轻巧和简单,拥有常用的图表类型:线型图、饼图、柱状图和散点图。 核心功能 支持x,y轴缩放 支持拖拽 支持手指滑动 支持高亮显示 支持保存图表到文件中 支持从文件(txt)中读取数据 预先定义颜色模板 自动生成标注 支持自定义x,y轴的显示标签 支持x,y轴动画 支持x,y轴设置最大值和附加信息 支持自定义字体,颜色,背景,手势,虚线等 图表类型 LineChart (with legend, simple design) LineChart (with legend, simple design) LineChart (cubic lines) LineChart (gradient fill) Combined-Chart (bar- and linechart in this case) BarChart (with legend, simple design) BarChart (grouped DataSets) Horizontal-BarChart PieChart (with selection, …) ScatterChart (with squares, triangles, circles, … and more) CandleStickChart (for financial data) BubbleChart (area covered by bubbles indicates the yValue) RadarChart (spider web chart) 图表绘制XY轴绘制 setEnabled(boolean enabled):设置轴是否被绘制。默认绘制,false不会被绘制。 setDrawLabels(boolean enabled):设置为true打开绘制轴的标签。 setDrawAxisLine(boolean enabled):设置为true,绘制轴线 setDrawGridLines(boolean enabled):设置为true绘制网格线。 定义轴线样式 setTextColor(int color):设置轴标签文本颜色。 setTextSize(float size):设置轴标签的字体大小。 setTypeface(Typeface tf):设置轴标签的自定义Typeface setGridColor(int color):设置网格线颜色。 setGridLineWidth(float width):设置网格线宽度。 setAxisLineColor(int color):设置此轴的坐标轴的颜色。 setAxisLineWidth(float width):设置此轴的坐标轴的宽度。 setVisibleXRangeMaximum(float maxXRange):设置x轴最多显示数据条数,(要在设置数据源后调用,否则是无效的) enableGridDashedLine(float lineLength, float spaceLength, float phase):显示网格线虚线模式,“lineLength”控制短线条的长度,“spaceLength”控制两段线之间的间隔长度,“phase”控制开始的点。 自定义轴线的值 setAdjustXLabels(boolean enabled):如果被设置为true,x轴条目将依赖于它自己在进行缩放的时候。如果设置为false,x轴条目将总是保持相同。 setAvoidFirstLastClipping(boolean enabled):如果设置为true,图表将避免第一个和最后一个标签条目被减掉在图表或屏幕的边缘。 setSpaceBetweenLabels(int characters):设置x轴标签之间的空间字符数,默认是4个。 setPosition(XAxisPosition pos):设置XAxis应该出现的位置。可以选择TOP,BOTTOM,BOTH_SIDED,TOP_INSIDE或者BOTTOM_INSIDE。 setStartAtZero(boolean enabled):如果这个打开,轴线总是有最小值0,无论什么类型的图表被展示。 setAxisMaxValue(float max):设置一个自定义的最大值为这条轴,如果设置了,这个值将不会依赖于提供的数据自动计算。 resetAxisMaxValue():调用这个将撤销以前设置的最大值。这意味着,你将再次允许轴自动计算它的最大值。 setAxisMinValue(float min):设置一个自定义的最小值。如果设置了,这个值将不会依赖于你提供的数据进行自动计算。 resetAxisMinValue():调用这个方法撤销以前设置的最小值。这意味着,你将再次允许轴自动计算他的最小值。 setInverted(boolean enabled):如果设置为true,这个轴将被反向,那意味着最高出的将到底部,最低部的到顶端。 setSpaceTop(float percent):设置在图表上最高处的值相比轴上最高值的顶端空间(总轴范围的百分比) setSpaceBottom(float percent):设置在图表上最低处的值相比轴上最低处值的底部空间(总轴范围的百分比) setShowOnlyMinMax(boolean enabled):如果打开了,这个轴将展示出它的最小值和最大值。这将忽略或者覆盖定义过的label-count。 setPosition(YAxisLabelPosition pos):设置轴标签应该被绘制的位置。INSIDE_CHART或者OUTSIDE_CHART中的一个。 自定义影响轴的数值范围应该在图表被设置数据之前应用。 图表样式一些样式相关方法,可以直接使用有关更详尽单独图表类型的样式和设置,请看看具体的图表设置的wiki页面Specific-chart-settings setBackgroundColor(int color):设置整个图表视图的背景 setDescription(String desc):右下角对图表的描述信息 setDescriptionColor(int color):描述信息的颜色 setDescriptionPosition(float x, float y):自定义描述信息位置. setDescriptionTypeface(Typeface t):自定义描述信息字体 setDescriptionTextSize(float size):自定义描述信息字体大小, 最小值6f, 最大值16f. setNoDataTextDescription(String desc):设置空表的描述信息 setDrawGridBackground(boolean enabled):是否绘制网格背景 setGridBackgroundColor(int color):设置网格背景颜色 setDrawBorders(boolean enabled):是否绘制边线 setBorderColor(int color):边线颜色 setBorderWidth(float width):边线宽度,单位dp setMaxVisibleValueCount(int count):设置图表绘制可见标签数量最大值. 仅在setDrawValues() 启用时生效 打印日志 setLogEnabled(boolean enabled):设置为true会激活log输出。使用这种log会对性能造成影响,没有必要用的话关掉它。 刷新 invalidate():这个方法能使图表重绘.要使图表更改生效,这个方法是必要的。 notifyDataSetChanged():让图表知道它的基础数据发生更改,并执行所有必要的重新计算(offsets, legend, maxima, minima, …)。 动态添加数据时,这是必须调用的。 图表的交互启用/禁用交互 setTouchEnabled(boolean enabled):启用图表触摸事件 setDragEnabled(boolean enabled):启用图表拖拽事件 setScaleEnabled(boolean enabled):启用图表缩放事件 setScaleXEnabled(boolean enabled):启用X轴上的缩放 setScaleYEnabled(boolean enabled):启用Y轴上的缩放 setPinchZoom(boolean enabled):XY同时缩放 setDoubleTapToZoomEnabled(boolean enabled):启用双击缩放 setHighlightPerDragEnabled(boolean enabled):拖拽超过图标绘制画布时高亮显示 setHighlightPerTapEnabled(boolean enabled):双击高亮显示 图表的减速器 setDragDecelerationEnabled(boolean enabled):抬起之后继续滚动 setDragDecelerationFrictionCoef(float coef): 减速插值,取值范围[0,1)。0表示立停止。值越大速度下降越缓慢 高亮方式 highlightValues(Highlight[] highs):高亮点的集合,如果为空,全部不高亮 highlightValue(int xIndex, int dataSetIndex):x轴上的数据集合高亮。如果为-1,全部不高两 getHighlighted():获取高亮点的集合,高亮显示使用OnChartValueSelectedListener不会生成一个回调。可以通过ChartData或DataSet对象启用和禁用高亮显示。 自定义高亮符号所有的用户输入在内部被默认ChartHighlighter类处理。它可以用下面的方法自定义实现替换默认highligher: setHighlighter(ChartHighlighter highlighter):通过继承ChartHighlighter类实现自定义高亮符号。通过setHighlighter设置点击等操作高亮显示的符号 选择回调 OnChartValueSelectedListener:触摸高亮值时回调 手势回调 OnChartGestureListener:这个回调可以定制手势操作相关回调。注意该手势的缩放是对图表的Matrix缩放,并不改变X轴的值,如果想实现X轴数值变化缩放,如天月年的切换,需要借助手势类实现。 主要图表类轴AxisBaseAxisBase 这个类,他是XAxis 和YAxis的基类 X轴XAxisXAxis 是AxisBase的子类。XAxis 类是所有的数据和信息的容器与水平轴有关。。XAxis显示什么是交给ChartData对象作为一个ArrayList 或者String[]。XAxis类允许自定义样式和以下部分:水平对齐标签绘制,其中包含轴描述值,为图表X轴提供的数据对象设置。在标签旁边与标签平行绘制了一个“axis-line”。每个在垂直方向坐标轴标签的网格线。 Y轴YAxisYAxis 是AxisBase的子类。YAxis 类是与垂直轴相关的所有数据和信息容器,与左边右边垂直的轴相关。RadarChart 只有一个Y轴,默认情况下,图标的两个轴都启用绘制。 zeroline除了网格线,在水平方向Y轴的每个值,有所谓的zeroline,这是在0位置轴线上值绘制的,是类似于网格线,但可以单独配置。 LimitLine 类两轴支持,所谓LimitLines允许显示特殊信息,如边界或限制。LimitLine在水平方向时添加到YAxis,而在垂直方向时添加到XAxis。这是如何从轴添加和删除LimitLines addLimitLine(LimitLine l):在轴上添加新的 LimitLine removeLimitLine(LimitLine l):从轴上移除 LimitLine setDrawLimitLinesBehindData(boolean enabled):允许控制LimitLines之间的z轴上的实际的数据顺序。如果设置为true,LimitLines在真实数据后边绘制,,否则在上面。默认false 常见问题显示隐藏Y轴线及自定义轴线的显示样式 mChart.getAxisLeft().setEnabled(false):隐藏Y轴左边轴线,此时标签数字也隐藏 mChart.getAxisRight().setEnabled(false):隐藏Y轴右边轴线,此时标签数字也隐藏 mChart.getAxisRight().setDrawAxisLine(false):如果想隐藏轴线但是想显示数字标签 Y轴线数据标签怎么自己控制显示个数 mChart.getAxisLeft().setLabelCount(8, false):此时设置了分8个,可根据自己喜好设置 设置轴线颜色,宽度等信息12345678910111213YAxis leftAxis = mChart.getAxisLeft();// 显示轴线内部INSIDE_CHART leftAxis.setPosition(YAxisLabelPosition.OUTSIDE_CHART);// 设置轴线颜色leftAxis.setAxisLineColor(Color.parseColor(“#ff0000”));// 设置轴线宽度leftAxis.setAxisLineWidth(1); // 设置y轴标签字体大小 leftAxis.setTextSize(20);// 设置自定义字体leftAxis.setTypeface(); // 设置是否显示网格线leftAxis.setDrawGridLines(Boolean); 自定义Y轴方向上的值重写ValueFormatter,使用DataSet.setValueFormatter进行设置123456789101112131415public class CustomerValueFormatter implements ValueFormatter { private DecimalFormat mFormat; public CustomerValueFormatter() { // 此处是显示数据的方式,显示整型或者小数后面小数位数自己随意确定 mFormat = new DecimalFormat("###,###,###,##0"); } @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { // 数据前或者后可根据自己想要显示的方式添加 return mFormat.format(value); }} 自定义Y轴坐标显示的值重写YAxisValueFormatter,使用YAxis.setValueFormatter设置12345678910111213public class CustomerValueFormatter implements YAxisValueFormatter { private DecimalFormat mFormat; public CustomerValueFormatter() { mFormat = new DecimalFormat("###,###,###,##0"); } @Override public String getFormattedValue(float value, YAxis yAxis) { return "¥"+mFormat.format(value); }} 自定义X轴坐标显示的值重写XAxisValueFormatter,使用XAxis.setValueFormatter设置 将x轴标签倾斜显示1234567XAxis xl = mChart.getXAxis();// 设置x轴字体显示角度xl.setLabelRotationAngle(-20);// 设置X轴的位置TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDExl.setPosition(XAxisPosition.BOTTOM);// 设置Lable之间的距离(字符),小于距离将不显示,需要放大图标才能看到xl.setSpaceBetweenLabels(int spaceCharacters) 设置一页数据点数setVisibleXRange(float minXRange, float maxXRange) 补充更多详细介绍可参考 JNChartDemo ChartLib-Demo-Android API document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android图表</tag>
<tag>MPAndroidChart</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android数据库加密]]></title>
<url>%2FAndroid%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8A%A0%E5%AF%86%2F</url>
<content type="text"><![CDATA[Android数据库加密,加密应用里重要的信息,避免干坏事,坏人! 加密方案SQLite不支持加密,应用中重要的数据账号密码等容易被泄露。 加密数据库内容在存储数据时加密内容,在查询时进行解密。但是这种方式不能彻底加密,数据库的表结构等信息还是能被查看到,另外检索数据也是一个问题。 加密数据库文件借助SQLCipher。SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能。 加密性能高、开销小,只要5-15%的开销用于加密 完全做到数据库100%加密 采用良好的加密方式(CBC加密模式) 使用方便,做到应用级别加密 采用OpenSSL加密库提供的算法 加密内容介绍一些常用的加密数据的方式,可以通过这些方式加密存储的数据库内容。 加密算法 描述 优点 缺点 DES,3DES 对称加密算法 算法公开、计算量小、加密速度快、加密效率高 双方都使用同样密钥,安全性得不到保证 AES 对称加密算法 算法公开、计算量小、加密速度快、加密效率高 双方都使用同样密钥,安全性得不到保证 XOR 异或加密 两个变量的互换(不借助第三个变量),简单的数据加密 加密方式简单 Base64 算不上什么加密算法,只是对数据进行编码传输 SHA 非对称加密算法。安全散列算法,数字签名工具。著名的图片加载框架Glide在缓存key时就采用的此加密 破解难度高,不可逆 可以通过穷举法进行破解 RSA 非对称加密算法,最流行的公钥密码算法,使用长度可变的秘钥 不可逆,既能用于数据加密,也可以应用于数字签名 RSA非对称加密内容长度有限制,1024位key的最多只能加密127位数据 MD5 非对称加密算法。全程:Message-Digest Algorithm,翻译为消息摘要算法 不可逆,压缩性,不容易修改,容易计算 穷举法可以破解 加密文件集成AndroidStudio的Module中build.gradle添加SQLCipher的依赖1compile 'net.zetetic:android-database-sqlcipher:3.5.7' GreenDao加密GreenDao有加密的接口,使用非常方便,在GreenDao初始化的时候启动加密模式。 非加密模式 1DaoMaster.OpenHelper.getWritableDatabase() 加密模式 1DaoMaster.OpenHelper.getEncryptedWritableDb(key) 123DaoMaster.OpenHelper encryptedHelper = new DaoMaster.DevOpenHelper(this, VOD_DB, null);// 选用设备唯一码作为加密的keyDaoSession encryptedDaoSession = new DaoMaster(encryptedHelper.getEncryptedWritableDb(getUniquePseudoID())).newSession(); 注意 读取数据库时,key需要保存一致,否则,读出的数据为空 增删改查的数据库操作加密非加密模式下都是一样的。 SQLiteOpenHelper加密使用SQLiteOpenHelper自己定义的接口 初始化SQLiteOpenHelper时,加载so库 12345678public class DBCipherHelper extends SQLiteOpenHelper { public DBCipherHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); //不可忽略的 进行so库加载 SQLiteDatabase.loadLibs(context); }} 使用 传统模式 1234//获取可写数据库SQLiteDatabase db = dbHelper.getWritableDatabase();//获取可读数据库SQLiteDatabase db = dbHelper.getReadableDatabase(); 加密模式 1234//获取写数据库SQLiteDatabase db = dbHelper.getWritableDatabase(key);//获取可读数据库SQLiteDatabase db = dbHelper.getReadableDatabase(key); 加密方案总结 加密内容后,将数据库打开,查询,可以看到加密后的内容,即密文。 加密文件,集成SQLCipher后,数据库文件.db是完全加密的,因此,通过命令查询等操作数据库,会被提示数据库加密,操作失败! 根据不同需求,使用加密文件或者加密内容的方式进行数据的安全保护,SQLCipher会增加apk的大小。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>加密</tag>
<tag>SQLCipher</tag>
<tag>SQLite</tag>
<tag>GreenDao</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android白银篇-GreenDao3]]></title>
<url>%2FAndroid%E7%99%BD%E9%93%B6%E7%AF%87-GreenDao3%2F</url>
<content type="text"><![CDATA[GreenDao 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。它的本质就是提供一个面向对象的接口,使得开发者更加方便地将数据存储到数据库SQLite之中。我们只需要定义数据模型,GreenDao就会为我们生成实体类以及DAOs(data access objects),(在3.0之后不需要我们编写generator而是编写实体类并添加注解,GreenDao会为我们生成schema,以及DAOs)从而避免了开发者编写较多枯燥的数据存储和加载的代码。 Android黄金篇-SQLite数据库 Android数据库加密 GreenDao传送门 GreenDao介绍在GreenDao中,默认会为每一个实体类建立一张数据表,实体类的每一个属性对应数据表中的一列。 优点 Android精简的依赖库,方便集成 性能最大化 内存开销最小化 易于使用的APIs 对Android进行高度优化 配置GreenDao3 采用注解方式来定义实体类,通过gradle插件生成相应的代码。 配置插件在工程根目录下的build.gradle文件里,添加代码:12345dependencies { classpath 'com.android.tools.build:gradle:2.3.3' // 添加GreenDao插件 classpath 'org.greenrobot:greendao-gradle-plugin:3.2.0'} 配置依赖在Module下的build.gradle文件里,添加代码:123456789101112131415apply plugin: 'com.android.application'// 添加apply plugin: 'org.greenrobot.greendao'dependencies { // 添加 compile 'org.greenrobot:greendao:3.2.0'}// 添加greendao { schemaVersion 1 daoPackage 'com.excellence.medical.greendao' targetGenDir 'src/main/java'} schemaVersion: 数据库版本号,默认为1 daoPackage: 生成的DAOs、DaoMaster、DaoSession包名;默认为entities(数据库实体类)所在的包名 targetGenDir: 生成的DAOs、DaoMaster、DaoSession的目录,默认为build/generated/source/greendao generateTests: 设置true自动生成单元测试。 targetGenDirTests: 设置生成单元测试目录。默认为src/androidTest/java 基本用法实体创建带注解@Entity的实体类,实体类即数据表;通常(除开带@Transient注解的成员)实体类中成员就是数据库中对应的字段。然后make project编译项目,实体类会自动生成get、set方法,并且在targetGenDir目录下的daoPackage包里,如src/main/java/com/excellence/medical/greendao,生成DaoMaster、DaoSession、以及AccountDao。 如果想增加或减少数据库字段,删除实体类中自动生成的代码,然后进行增加或减少实体类中的成员。 12345678910@Entity(nameInDb = "account")public class Account { @Id(autoincrement = true) private Long id; @Unique private String accountId; private String accountName; private String accountPwd;} 注解 @Entity修饰实体类名 123456789101112131415161718192021222324@Entity( // schema 名,多个 schema 时设置关联实体。插件产生不支持,需使用产生器 // schema = "myschema", // 标记一个实体是否处于活动状态,活动实体有 update、delete、refresh 方法。默认为 false active = false, // 表名,默认为类名 nameInDb = "Account", // 定义多列索引 indexes = { @Index(value = "name DESC", unique = true) }, // 标记是否创建表,默认 true。多实体对应一个表或者表已创建,不需要 greenDAO 创建时设置 false createInDb = true, // 是否产生所有参数构造器。默认为 true。无参构造器必定产生 generateConstructors = true, // 如果没有 get/set 方法,是否生成。默认为 true generateGettersSetters = true) 修饰实体类成员 123456789101112131415161718192021222324252627282930// 主键,autoincrement设置自增,注意类型是Long,而不是long@Id(autoincrement = true)// 唯一,默认索引@Unique// 列名-字段名,默认使用变量名。变化:customName --> CUSTOM_NAME@Property(nameInDb = "USERNAME")// 索引,unique设置唯一,name设置索引别名@Index(unique = true)// 非空@NotNull// 忽略,不持久化,即数据表不创建该字段,可用关键字transient替代@Transient// 对一,实体属性 joinProperty 对应外联实体ID@ToOne(joinProperty = "fk_dogId")// 对多。实体ID对应外联实体属性 referencedJoinProperty@ToMany(referencedJoinProperty = "fk_userId")// 对多。@JoinProperty:name 实体属性对应外联实体属性 referencedName@ToMany(joinProperties = {@JoinProperty(name = "horseName", referencedName = "name")})// 对多。@JoinEntity:entity 中间表;中间表属性 sourceProperty 对应实体ID;中间表属性 targetProperty 对应外联实体ID@ToMany@JoinEntity(entity = JoinUserWithSheep.class, sourceProperty = "uId", targetProperty = "sId") 初始化在Application中进行初始化,GreenDao打开数据库两种方式:①打开内部(/data/data/xxxpackageNamexxx/)数据库,②打开外部(其他目录下)数据库 内部数据库 1234567891011121314// 注意DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(this, VOD_DB, null);mDaoMaster = new DaoMaster(helper.getWritableDatabase());// 如果想使用Dao,直接用mDaoSession获取对应的Dao来操作mDaoSession = mDaoMaster.newSession();// Dao,执行增删改查操作AccountDao dao = mDaoSession.getAccountDao();// 如果想使用Sql语句,就使用mDaoDatabase操作mDaoDatabase = mDaoSession.getDatabase();// GreenDao有特殊的线程来处理数据库的耗时操作mAsyncSession = mDaoSession.startAsyncSession(); 外部数据库 1234567891011121314151617181920212223242526272829303132333435public class DatabaseContext extends ContextWrapper { private String mDBPath = null; private DatabaseContext(Context base) { super(base); } public DatabaseContext(Context base, String dbPath) { super(base); mDBPath = dbPath; } @Override public File getDatabasePath(String name) { String dbPath = mDBPath + name; if (FileUtils.isFileExists(dbPath)) { return new File(dbPath); } else { return null; } } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { int flags = SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; return SQLiteDatabase.openDatabase(getDatabasePath(name).getAbsolutePath(), factory, flags, null); } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { int flags = SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; return SQLiteDatabase.openDatabase(getDatabasePath(name).getAbsolutePath(), factory, flags, errorHandler); }} 1234// 重写传入DaoMaster.DevOpenHelper的Context,即改变数据库的路径DatabaseContext databaseContext = new DatabaseContext(this, DB_PATH);DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(databaseContext, VOD_DB, null);其他类似打开内部数据库 注意: DaoMaster.DevOpenHelper在数据库升级的时候,会删除所有的表,只能用于Debug调试,正式项目需要封装处理,GreenDao升级请参考:GreenDaoUpgradeHelper 如果GreenDao想使用打开多个数据库,可以创建多个DaoMaster.OpenHelper和DaoSession;同时重写onCreate方法,否则每个数据库的表是一样的。例如:12345678910111213141516DaoMaster.OpenHelper helper = new DaoMaster.OpenHelper(this, DB_NAME) { @Override public void onCreate(Database db) { ConfigDao.createTable(db, false); MemberDao.createTable(db, false); } }; mDaoSession = new DaoMaster(helper.getWritableDatabase()).newSession(); DaoMaster.OpenHelper encryptedHelper = new DaoMaster.OpenHelper(this, ENCRYPTED_DB_NAME) { @Override public void onCreate(Database db) { AccountDao.createTable(db, false); } }; mEncryptedDaoSession = new DaoMaster(encryptedHelper.getEncryptedWritableDb(getUniquePseudoID())).newSession(); 增删改查 使用Dao操作时,数据库里必须有主键,操作才会成功;否则操作无效或达不到预期的结果 GreenDao有一个缓存机制,即把用户插入,更改或查找的实体保存在内存中,当用户下一次查找时先从内存中查找,如果不存在再从数据库中查找,清除缓存使用:DaoSession.clear() 如果没有主键,则只能使用Sql语句操作数据库,可以参考:Android黄金篇-SQLite数据库 Dao增加123456789101112long insert(T entity) // 插入指定实体void insertInTx(T... entities)void insertInTx(java.lang.Iterable<T> entities)void insertInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)long insertWithoutSettingPk(T entity) // 插入指定实体,无主键long insertOrReplace(T entity) // 插入或替换指定实体void insertOrReplaceInTx(T... entities)void insertOrReplaceInTx(java.lang.Iterable<T> entities)void insertOrReplaceInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)void save(T entity) // 依赖指定的主键插入或修改实体void saveInTx(T... entities)void saveInTx(java.lang.Iterable<T> entities) Dao删除1234567void deleteAll() // 删除所有void delete(T entity) // 删除指定的实体void deleteInTx(T... entities)void deleteInTx(java.lang.Iterable<T> entities)void deleteByKey(K key) // 删除指定主键对应的实体void deleteByKeyInTx(K... keys)void deleteByKeyInTx(java.lang.Iterable<K> keys) Dao修改123void update(T entity)void updateInTx(T... entities)void updateInTx(java.lang.Iterable<T> entities) Dao查询123java.util.List<T> loadAll()T load(K key)T loadByRowId(long rowId) QueryBuilder查询1234567891011121314151617QueryBuilder<T> queryBuilder() // Dao// QueryBuilderQueryBuilder<T> where(WhereCondition cond, WhereCondition... condMore) // 条件,AND 连接QueryBuilder<T> whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) // 条件,OR 连接QueryBuilder<T> distinct() // 去重,例如使用联合查询时QueryBuilder<T> limit(int limit) // 限制返回数QueryBuilder<T> offset(int offset) // 偏移结果起始位,配合limit(int)使用QueryBuilder<T> orderAsc(Property... properties) // 排序,升序QueryBuilder<T> orderDesc(Property... properties) // 排序,降序QueryBuilder<T> orderCustom(Property property, java.lang.String customOrderForProperty) // 排序,自定义QueryBuilder<T> orderRaw(java.lang.String rawOrder) // 排序,SQL 语句QueryBuilder<T> preferLocalizedStringOrder() // 本地化字符串排序,用于加密数据库无效QueryBuilder<T> stringOrderCollation(java.lang.String stringOrderCollation) // 自定义字符串排序,默认不区分大小写WhereCondition and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) // 条件,AND 连接WhereCondition or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) // 条件,OR 连接 示例1mAccountDao.queryBuilder().orderDesc(Properties.Date).limit(1).unique() DaoSession异步操作123456789101112131415DaoSession().startAsyncSession().runInTx(new Runnable() { @Override public void run() { // insert // delete // update // query }});DaoSession.startAsyncSession().insertInTxDaoSession.startAsyncSession().deleteInTxDaoSession.startAsyncSession().updateInTxDaoSession.startAsyncSession().insertInTxDaoSession.startAsyncSession().insertOrReplaceInTx DaoSession增删改查1234567891011// DaoSession 的方法转换成 Dao 的对应方法执行<T> long insert(T entity)<T> long insertOrReplace(T entity)<T> void delete(T entity)<T> void deleteAll(java.lang.Class<T> entityClass)<T> void update(T entity)<T,K> T load(java.lang.Class<T> entityClass, K key)<T,K> java.util.List<T> loadAll(java.lang.Class<T> entityClass)<T> QueryBuilder<T> queryBuilder(java.lang.Class<T> entityClass)<T,K> java.util.List<T> queryRaw(java.lang.Class<T> entityClass, java.lang.String where, java.lang.String... selectionArgs)<T> void refresh(T entity) Query重复查询12345678910111213141516// QueryBuilderQuery<T> build()CursorQuery buildCursor()CountQuery<T> buildCount()DeleteQuery<T> buildDelete()// Query// 设置查询参数,从 0 开始Query<T> setParameter(int index, java.lang.Object parameter)Query<T> setParameter(int index, java.lang.Boolean parameter)Query<T> setParameter(int index, java.util.Date parameter)void setLimit(int limit) // 限制返回数void setOffset(int offset) // 偏移结果起始位,配合limit(int)使用// Query 绑定线程,执行非本线程的 Query 抛异常,调用获取本线程 QueryQuery<T> forCurrentThread() // 获取本线程 Query 查询结果1234567891011// QueryBuilder、QueryT unique() // 返回唯一结果或者 nullT uniqueOrThrow() // 返回唯一非空结果,如果 null 则抛异常java.util.List<T> list() // 返回结果集进内存// 懒加载,须在 try/finally 代码中关闭。LazyList<T> listLazy() // 第一次使用返回结果集,所有数据使用后会自动关闭LazyList<T> listLazyUncached() // 返回虚拟结果集,数据库读取不缓存CloseableListIterator<T> listIterator() // 懒加载数据迭代器,不缓存,所有数据使用后会自动关闭// QueryBuilder、CountQuerylong count() // 获取结果数量 混淆在混淆文件proguard-rules.pro中添加 12345678910### greenDAO 3-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {public static java.lang.String TABLENAME;}-keep class **$Properties# If you do not use SQLCipher:-dontwarn org.greenrobot.greendao.database.**# If you do not use RxJava:-dontwarn rx.** @ToOne、@ToMany,1:1、1:n、n:m等多张表关联待续^_^ document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>SQLite</tag>
<tag>GreenDao</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android适配器终结者?]]></title>
<url>%2FAndroid%E9%80%82%E9%85%8D%E5%99%A8%E7%BB%88%E7%BB%93%E8%80%85%EF%BC%9F%2F</url>
<content type="text"><![CDATA[终结Adapter、ViewHolder,封装通用适配器 项目传送门 1compile 'com.excellence:basetools:1.2.4' Android适配器在Android开发中,经常使用Adapter和ViewHolder,总是写着千篇一律的适配器代码,所以进行通用型、万能型的Adapter封装。 MVVM模式Adapter封装最近学习MVVM,发现一系列优点,适配器封装也非常简单,并且可以让ViewHolder去死吧,只留下可爱的Adapter;开启DataBinding,进行封装。 普通Adapter封装其他环境下:不开启DataBinding,封装Adapter和ViewHolder,使用时需要实现Adapter的抽象接口。 以上两种方式都可以实现单布局类型、多布局类型的适配器。 Adapter示例 MVVM模式Adapter示例DataBinding,ListVew、GridView适配器 单布局类型简单到爆炸有木有!!! 1234// 直接创建CommonBindingAdapterCommonBindingAdapter<Flower> adapter = new CommonBindingAdapter<>(mFlowers, R.layout.item_flower, BR.flower);// 设置适配器,等同于ListView.setAdapter()、GridView.setAdapter()mBinding.setAdapter(adapter); 多布局类型主要是实现多布局类型的ViewDelegate的接口 1234567// 直接创建MultiItemTypeBindingAdapterMultiItemTypeBindingAdapter<Flower> adapter = new MultiItemTypeBindingAdapter<>(mFlowers);// 添加多布局类型adapter.addItemViewDelegate(new RoseViewDelegate());adapter.addItemViewDelegate(new TulipViewDelegate());// 设置适配器mBinding.setAdapter(adapter); 实现ItemViewDelegate的接口 1234567891011121314151617181920public static class RoseViewDelegate implements ItemViewDelegate<Flower>{ @Override public int getItemViewLayoutId() { return R.layout.item_rose; } @Override public int getItemVariable() { return BR.rose; } @Override public boolean isForViewType(Flower item, int position) { return item instanceof Rose; }} DataBinding,RecyclerView适配器同上 单布局类型 123456// 直接创建BaseRecyclerBindingAdapterBaseRecyclerBindingAdapter<Flower> adapter = new BaseRecyclerBindingAdapter<>(mFlowers, R.layout.item_flower, BR.flower);// 设置适配器,等同于RecyclerView.setAdapter()mBinding.setAdapter(adapter);// 注意设置LayoutManager,等同于RecyclerView.setLayoutManager()mBinding.setLayoutManager(new LinearLayoutManager(this)); 多布局类型 12345MultiItemTypeBindingRecyclerAdapter<Flower> adapter = new MultiItemTypeBindingRecyclerAdapter<>(mFlowers);adapter.addItemViewDelegate(new RoseViewDelegate());adapter.addItemViewDelegate(new TulipViewDelegate());mBinding.setAdapter(adapter);mBinding.setLayoutManager(new LinearLayoutManager(this)); 普通Adapter示例ListVew、GridView适配器 单布局类型实现CommonAdapter的接口 1234567891011121314151617// 创建adapter类继承CommonAdapter,然后设置适配器即可private class AppGridAdapter extends CommonAdapter<ResolveInfo>{ public AppGridAdapter(Context context, List<ResolveInfo> datas, int layoutId) { super(context, datas, layoutId); } @Override public void convert(ViewHolder viewHolder, ResolveInfo item, int position) { // ViewHolder封装了一些辅助方法,方便View的各种设置 ImageView iconView = viewHolder.getView(android.R.id.icon); iconView.setImageDrawable(item.loadIcon(mPackageManager)); viewHolder.setText(android.R.id.text1, item.loadLabel(mPackageManager).toString()); }} 多布局类型实现ItemViewDelegate接口 123456789101112131415161718192021222324252627282930313233// 多布局适配器private class ChatAdapter extends MultiItemTypeAdapter<People>{ public ChatAdapter(Context context, List<People> messages) { super(context, messages); addItemViewDelegate(new ComputerDelegate()); addItemViewDelegate(new BlueDelegate()); addItemViewDelegate(new PurpleDelegate()); }}// 不同的布局视图private class ComputerDelegate implements ItemViewDelegate<People>{ @Override public int getItemViewLayoutId() { return R.layout.item_computer; } @Override public boolean isForViewType(People item, int position) { return item instanceof ComputerData; } @Override public void convert(ViewHolder viewHolder, People item, int position) { viewHolder.setText(R.id.computer_text, item.getMsg()); }} RecyclerView适配器 单布局类型实现BaseRecyclerAdapter的接口 1234567891011121314151617181920// 创建adapter类继承BaseRecyclerAdapterprivate class AppRecyclerAdapter extends BaseRecyclerAdapter<ResolveInfo>{ private PackageManager mPackageManager = null; public AppRecyclerAdapter(Context context, List<ResolveInfo> datas, int layoutId) { super(context, datas, layoutId); mPackageManager = context.getPackageManager(); } @Override public void convert(RecyclerViewHolder viewHolder, ResolveInfo item, int position) { // ViewHolder封装了一些辅助方法,方便View的各种设置 viewHolder.setText(android.R.id.text1, item.loadLabel(mPackageManager)); viewHolder.setImageDrawable(android.R.id.icon, item.loadIcon(mPackageManager)); }} 多布局类型实现ItemViewDelegate的接口 123456789101112131415161718192021222324252627282930313233// 多布局适配器private class WarAdapter extends MultiItemTypeRecyclerAdapter<People>{ public WarAdapter(Context context, List<People> datas) { super(context, datas); addItemViewDelegate(new ComputerRecyclerDelegate()); addItemViewDelegate(new BlueRecyclerDelegate()); addItemViewDelegate(new PurpleRecyclerDelegate()); }}// 不同的布局视图private class ComputerRecyclerDelegate implements ItemViewDelegate<People>{ @Override public int getItemViewLayoutId() { return R.layout.item_computer; } @Override public boolean isForViewType(People item, int position) { return item instanceof ComputerData; } @Override public void convert(RecyclerViewHolder viewHolder, People item, int position) { viewHolder.setText(R.id.computer_text, item.getMsg()); }} 欢迎star!!! document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android-Adapter</tag>
<tag>适配器终结者</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android6.0+动态权限管理]]></title>
<url>%2FAndroid6-0-%E5%8A%A8%E6%80%81%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[封装Android依赖库,方便Android6.0+环境下动态申请权限。 Android6.0以后,一些权限不只是通过AndroidManifest申明,还需要在代码申请,系统弹出权限申请弹框,让用户确认授权,才能使用这些权限,如:WRITE_EXTERNAL_STORAGE。 Android权限 需要申请权限需要申请的权限分为9组,每组只要有一个权限申请成功,就默认整组权限都可以使用。 123456789101112131415161718192021222324252627282930313233343536373839404142group:android.permission-group.CONTACTS permission:android.permission.WRITE_CONTACTS permission:android.permission.GET_ACCOUNTS permission:android.permission.READ_CONTACTSgroup:android.permission-group.PHONE permission:android.permission.READ_CALL_LOG permission:android.permission.READ_PHONE_STATE permission:android.permission.CALL_PHONE permission:android.permission.WRITE_CALL_LOG permission:android.permission.USE_SIP permission:android.permission.PROCESS_OUTGOING_CALLS permission:com.android.voicemail.permission.ADD_VOICEMAILgroup:android.permission-group.CALENDAR permission:android.permission.READ_CALENDAR permission:android.permission.WRITE_CALENDARgroup:android.permission-group.CAMERA permission:android.permission.CAMERAgroup:android.permission-group.SENSORS permission:android.permission.BODY_SENSORSgroup:android.permission-group.LOCATION permission:android.permission.ACCESS_FINE_LOCATION permission:android.permission.ACCESS_COARSE_LOCATIONgroup:android.permission-group.STORAGE permission:android.permission.READ_EXTERNAL_STORAGE permission:android.permission.WRITE_EXTERNAL_STORAGEgroup:android.permission-group.MICROPHONE permission:android.permission.RECORD_AUDIOgroup:android.permission-group.SMS permission:android.permission.READ_SMS permission:android.permission.RECEIVE_WAP_PUSH permission:android.permission.RECEIVE_MMS permission:android.permission.RECEIVE_SMS permission:android.permission.SEND_SMS permission:android.permission.READ_CELL_BROADCASTS 普通权限,在AndroidManifest.xml中申明即可 1234567891011121314151617181920212223242526272829303132333435363738android.permission.ACCESS_LOCATION_EXTRA_COMMANDSandroid.permission.ACCESS_NETWORK_STATEandroid.permission.ACCESS_NOTIFICATION_POLICYandroid.permission.ACCESS_WIFI_STATEandroid.permission.ACCESS_WIMAX_STATEandroid.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMINandroid.permission.BROADCAST_STICKYandroid.permission.CHANGE_NETWORK_STATEandroid.permission.CHANGE_WIFI_MULTICAST_STATEandroid.permission.CHANGE_WIFI_STATEandroid.permission.CHANGE_WIMAX_STATEandroid.permission.DISABLE_KEYGUARDandroid.permission.EXPAND_STATUS_BARandroid.permission.FLASHLIGHTandroid.permission.GET_ACCOUNTSandroid.permission.GET_PACKAGE_SIZEandroid.permission.INTERNETandroid.permission.KILL_BACKGROUND_PROCESSESandroid.permission.MODIFY_AUDIO_SETTINGSandroid.permission.NFCandroid.permission.READ_SYNC_SETTINGSandroid.permission.READ_SYNC_STATSandroid.permission.RECEIVE_BOOT_COMPLETEDandroid.permission.REORDER_TASKSandroid.permission.REQUEST_INSTALL_PACKAGESandroid.permission.SET_TIME_ZONEandroid.permission.SET_WALLPAPERandroid.permission.SET_WALLPAPER_HINTSandroid.permission.SUBSCRIBED_FEEDS_READandroid.permission.TRANSMIT_IRandroid.permission.USE_FINGERPRINTandroid.permission.VIBRATEandroid.permission.WAKE_LOCKandroid.permission.WRITE_SYNC_SETTINGScom.android.alarm.permission.SET_ALARMcom.android.launcher.permission.INSTALL_SHORTCUTcom.android.launcher.permission.UNINSTALL_SHORTCUT 动态申请 设置targetSdkVersion为23以上 在AndroidManifest.xml中申明需要的权限,包括普通权限和需要申请的权限 对于申请的权限,需要在代码中申请 检测权限,使用ContextCompat可以在任意地方使用该方式 12// 检测权限是否被授权if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) 如果未授权,则必须在Activity中申请 1Activity.requestPermissions() 如果用户拒绝,并且点击了“不再询问”,当再次使用步骤2申请时,界面上不会有任何反应,因此需要判断用户是否点击“不再询问”,在Activity中使用shouldShowRequestPermissionRationale方法判断。然后自定义弹框给用户,让用户进入Setting应用去开启权限。 注意:对于“不再询问”的理解: 123456789101112在6.0时代,需要在程序运行时获取相关权限,展开一个对话框询问是否授予该程序相应权限。从第二次开始运行的时候,会增加一个选项框,“以后不再询问”,如果选择了这个选项,那么以后程序不会再询问是否授予权限了。这时候选择了确认倒还好,之后倒方便了。如果选择了拒绝,那之后也不会显示对话框,但是权限一直是拒绝的。这样是非常不好的体验,不知道的还以为程序崩溃了。所以,我们需要在这个时候也显示相应对话框[自定义的对话框]来告诉用户第一次请求时,返回false如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。注:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。如果想判断是否拒绝权限,需要在请求一次之后的Failure回调里,再次执行shouldShowRequestPermissionRationale方法,返回 false为拒绝 权限申请封装依赖库Permission,欢迎start!!! 将动态申请权限的步骤封装起来,方便使用: 链式申请和回调 可在任何地方调用,不限于Activity 自定义进入Setting应用的提示 引入1compile 'com.excellence:permission:1.0.0' 使用示例123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384/** * 申请单个权限 */private void singleRequest(){ PermissionRequest.with(this).permission(WRITE_EXTERNAL_STORAGE).request(new IPermissionListener() { @Override public void onPermissionsGranted() { Toast.makeText(MainActivity.this, "申请单个权限成功", Toast.LENGTH_SHORT).show(); } @Override public void onPermissionsDenied() { Toast.makeText(MainActivity.this, "申请单个权限失败", Toast.LENGTH_SHORT).show(); } });}/** * 选中“不在询问”情况下,拒绝时默认情况显示SettingDialog的弹框; * * 可以自定义弹框,提示用户进入Setting应用,在rationale监听中提示 * 注意:请使用PermissionActivity的引用,因为我将回调全部在PermissionActivity处理了 */private void singleRequest(){ PermissionRequest.with(this).rationale(new IRationaleListener() { @Override public void OnRationale(final PermissionActivity activity) { new SettingDialog(activity).setTitle("Warning").setOnCancelListener(new SettingDialog.OnCancelListener() { @Override public void onCancel() { /** * 点击取消时,认为请求失败 */ activity.permissionsDenied(); } }).show(); } }).permission(WRITE_EXTERNAL_STORAGE).request(new IPermissionListener() { @Override public void onPermissionsGranted() { Toast.makeText(MainActivity.this, "申请单个权限成功", Toast.LENGTH_SHORT).show(); } @Override public void onPermissionsDenied() { Toast.makeText(MainActivity.this, "申请单个权限失败", Toast.LENGTH_SHORT).show(); } });}/** * 申请多个权限 */private void multiRequest(){ PermissionRequest.with(this).permission(READ_CONTACTS, CAMERA).request(new IPermissionListener() { @Override public void onPermissionsGranted() { Toast.makeText(MainActivity.this, "申请多个权限成功", Toast.LENGTH_SHORT).show(); } @Override public void onPermissionsDenied() { Toast.makeText(MainActivity.this, "申请多个权限失败", Toast.LENGTH_SHORT).show(); } });} 感谢 yanzhenjie tbruyelle googlesamples document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android6.0+</tag>
<tag>动态权限</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android-DIY-ShimmerTextView]]></title>
<url>%2FAndroid-DIY-ShimmerTextView%2F</url>
<content type="text"><![CDATA[Android自定义控件:闪烁文字效果 源码传送门 效果展示 讲解使用线性渲染LinearGradient设置画笔的着色器Shader,Matrix用于对图像的图形处理,然后不停绘制文本。 1234567891011121314151617181920212223242526272829303132333435@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){ super.onSizeChanged(w, h, oldw, oldh); if (mViewWidth == 0) { mViewWidth = getMeasuredWidth(); if (mViewWidth > 0) { mPaint = getPaint(); // 设置线性渲染 mLinearGradient = new LinearGradient(-mViewWidth, 0, 0, 0, new int[] { 0x59ffffff, 0xffffffff, 0x59ffffff }, new float[] { 0, 0.5f, 1 }, Shader.TileMode.CLAMP); // 设置Paint着色器 mPaint.setShader(mLinearGradient); mGradientMatrix = new Matrix(); } }}@Overrideprotected void onDraw(Canvas canvas){ super.onDraw(canvas); if (mGradientMatrix != null) { mTranslate += mViewWidth / 10; if (mTranslate > 2 * mViewWidth) { mTranslate = -mViewWidth; } mGradientMatrix.setTranslate(mTranslate, 0); mLinearGradient.setLocalMatrix(mGradientMatrix); postInvalidateDelayed(50); }} document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>DIY</category>
</categories>
<tags>
<tag>Android</tag>
<tag>自定义控件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android热修复-Sophix]]></title>
<url>%2FAndroid%E7%83%AD%E4%BF%AE%E5%A4%8D-Sophix%2F</url>
<content type="text"><![CDATA[以往当Android App出现bug的时候,甚至仅仅是修改一行代码,都要重新发布新版本对bug进行修复,这样带来的缺点是明显的,需要用户重新升级app,覆盖率太慢,成本太高。所以就出现了热修复技术,通过打补丁的方式,通过从服务器下载补丁包,然后对有问题的类中出问题的方法,进行替换,优点是用户无感知修复,无需下载新的应用,代价小。对比其他的热修复方案,来耍一耍阿里-Sophix 。 介绍“冷热” 插件化 - apk 分为宿主和插件部分,插件在需要的时候才加载进来 热修复 – 更新的类或者插件粒度较小的时候,我们会称之为热修复,一般用于修复bug 热更新 – 2016 Google 的 Android Studio 推出了Instant Run 功能 同时提出了3个名词 热部署 – 方法内的简单修改,无需重启app和Activity。 暖部署 – app无需重启,但是activity需要重启,比如资源的修改。 冷部署 – app需要重启,比如继承关系的改变或方法的签名变化等。 热修复特点 无需重新发版,实时高效热修复 用户无感知修复,无需下载新的应用,无需重装App,代价小 修复成功率高,把损失降到最低 阿里热修复方案对比 方案对比 Andfix开源版本 阿里Hotfix 1.X 阿里Hotfix最新版 (Sophix) 方法替换 支持,除部分情况[0] 支持,除部分情况 全部支持 方法增加减少 不支持 不支持 以冷启动方式支持[1] 方法反射调用 只支持静态方法 只支持静态方法 以冷启动方式支持 即时生效 支持 支持 视情况支持[2] 多DEX 不支持 支持 支持 资源更新 不支持 不支持 支持 so库更新 不支持 不支持 支持 Android版本 支持2.3~7.0 支持2.3~6.0 全部支持包含7.0以上 已有机型 大部分支持[3] 大部分支持 全部支持 安全机制 无 加密传输及签名校验 加密传输及签名校验 性能损耗 低,几乎无损耗 低,几乎无损耗 低,仅冷启动情况下有些损耗 生成补丁 繁琐,命令行操作 繁琐,命令行操作 便捷,图形化界面 补丁大小 不大,仅变动的类 小,仅变动的方法 不大,仅变动的资源和代码[4] 服务端支持 无 支持服务端控制[5] 支持服务端控制 说明: [0] 部分情况指的是构造方法、参数数目大于8或者参数包括long,double,float基本类型的方法。 [1] 冷启动方式,指的是需要重启app在下次启动时才能生效。 [2] 对于Andfix及Hotfix 1.X能够支持的代码变动情况,都能做到即时生效。而对于其他代码变动较大的情况,会走冷启动方式,此时就无法做到即时生效。 [3] Hotfix 1.X已经支持绝大部分主流手机,只是在X86设备以及修改了虚拟机底层结构的ROM上不支持。 [4] 由于支持了资源和库,如果有这些方面的更新,就会导致的补丁变大一些,这个是很正常的。并且由于只包含差异的部分,所以补丁已经是最大程度的小了。 [5] 提供服务端的补丁发布和停发、版本控制和灰度功能,存储开发者上传的补丁包。 其他热修复方案 方案 作者 Tinker 微信(apk补丁) Robust 美团 Amigo 饿了么(apk补丁) Nuwa 个人开发者 Dexposed RocooFix 个人开发者 集成Sophix注册阿里云账号创建App文档传送门 注册完开发者账号,成功登录后进入控制台,添加移动热修复服务。 开通热修复服务后,跳转到热修复产品界面-App管理,创建App “Sophix测试”,创建完成后会出现两个平台的App列表:iOS和Android 点击管理进入Android平台,在客户端里,需要使用到AppId、APPSecret、RSA密钥 客户端集成Sophix文档传送门 引入maven依赖仓库在项目app下的build.gradle中添加maven仓库地址和版本依赖 添加maven仓库地址: 12345repositories { maven { url "http://maven.aliyun.com/nexus/content/repositories/releases" }} 添加依赖: 1compile 'com.aliyun.ams:alicloud-android-hotfix:3.1.2' 添加使用权限 123456<! -- 网络权限 --><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><! -- 外部存储读权限,调试工具加载本地补丁需要 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 配置AndroidManifest文件在application节点里添加配置,用之前在阿里云上创建的App的配置信息AppId、APPSecret、RSA密钥替换value的值: 123456789101112<application> <meta-data android:name="com.taobao.android.hotfix.IDSECRET" android:value="App ID" /> <meta-data android:name="com.taobao.android.hotfix.APPSECRET" android:value="App Secret" /> <meta-data android:name="com.taobao.android.hotfix.RSASECRET" android:value="RSA密钥" /> ···</application> 可参考官方的动图示例: Application接入SDK其他接口使用请查看SDK文档1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556/** * 初始化Sophix */private void initSophix(){ String appVersion; try { appVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; } catch (Exception e) { e.printStackTrace(); appVersion = "1.0.0"; } SophixManager.getInstance().setContext(this).setAppVersion(appVersion).setAesKey(null).setEnableDebug(true).setPatchLoadStatusStub(new PatchLoadStatusListener() { @Override public void onLoad(final int mode, final int code, final String info, final int handlePatchVersion) { // 补丁加载回调信息 StringBuilder msg = new StringBuilder(); msg.append("Mode:").append(mode).append("\n"); msg.append("Code:").append(code).append("\n"); msg.append("Info:").append(info).append("\n"); msg.append("HandlePatchVersion:").append(handlePatchVersion).append("\n"); if (mDisplayListener != null) mDisplayListener.handle(msg.toString()); // 补丁加载回调通知 switch (code) { case PatchStatus.CODE_LOAD_SUCCESS: // 表明补丁加载成功 break; case PatchStatus.CODE_LOAD_RELAUNCH: // 表明新补丁生效需要重启. 开发者可提示用户或者强制重启; // 建议: 用户可以监听进入后台事件,然后调用killProcessSafely自杀 // 注意:不可以直接Process.killProcess(Process.myPid())来杀进程,这样会扰乱Sophix的内部状态。 break; case PatchStatus.CODE_LOAD_FAIL: // 内部引擎异常,推荐此时清空本地补丁,防止失败补丁重复加载 SophixManager.getInstance().cleanPatches(); break; default: // 其它错误信息,查看PatchStatus类说明 break; } } }).initialize(); // 加载新的补丁包 SophixManager.getInstance().queryAndLoadNewPatch();} 至此,Sophix配置完成。 测试 客户端第一版本,如图,打包成Sophix_V1.apk 客户端补丁版本,如图,打包成Sophix_V2.apk 生成补丁,生成补丁文档传送门Windows下载阿里补丁工具SophixPatchTool,运行SophixPatchTool.exe,添加包,如果有签名等设置,则点击设置,配置相应的签名等,然后点击“Go”,生成补丁,即sophix-patch.jar: 上传补丁进入阿里云热修复App管理的Android平台里,即查看AppId、APPSecret、RSA密钥那个页面,添加新版本,成功添加版本后,点击查看详情进入,上传刚刚生成的sophix-patch.jar补丁。 本地测试安装Sophix_V1.apk,同时将补丁sophix-patch.jar放到Android设备的目录里:/sdcard/sophix-patch.jar,下载hotfixdebug工具,安装后,打开进入调试apk,配置如下: 测试成功 发布进入阿里云的补丁详情页面,点击发布。再次进行调试,这次不用Sophix调试工具;先卸载已安装的Sophix_V1,重新安装,打开后再次等待检测补丁更新,再次出现提示ok。 推广有兴趣的童鞋可以看看阿里出品的热修复原理宝典 .hexo-image-stream-lazy {display:block;}.hexo-img-readStream{width:100%;max-width:1100px;margin:3% auto}div.hexo-img-readStream readItems{ background: #fefefe;box-shadow: 0 1px 2px rgba(34, 25, 25, 0.2);margin: 0 1% 3%;padding: 3%;padding-bottom: 9px;display: inline-block;max-width: 25%;}div.hexo-img-readStream readItems img{padding-bottom:10px;margin-top: 0.7em;}div.hexo-img-readStream readItems figcaption{font-size:.8rem;color:#999;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align: center;}div.hexo-img-readStream small{font-size:1rem;float:right;text-transform:uppercase;color:#aaa}div.hexo-img-readStream small a{color:#666;text-decoration:none;transition:.4s color}@media screen and (max-width:750px){.hexo-img-readStream{column-gap:0}} 阿里热修复原理宝典 $('img.hexo-image-stream-lazy').lazyload({ effect:'fadeIn' }); document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>Android</tag>
<tag>热修复</tag>
<tag>Sophix</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java编译注解-自动生成代码]]></title>
<url>%2FJava%E7%BC%96%E8%AF%91%E6%B3%A8%E8%A7%A3-%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E4%BB%A3%E7%A0%81%2F</url>
<content type="text"><![CDATA[Annotation注解在Android的开发中的使用越来越普遍,例如EventBus、ButterKnife、Dagger2等,记一次使用插件annotationProcessor实现下载监听注解框架,为什么不用android-apt呢,我不会告诉你android-apt不再维护了。 Javadoc代码注解-代码文档:传送门 注解简介注解Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。 注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记。 定义注解用的关键字是@interface 注解的用处 生成文档,编译进行检查。这是最常见的,即Javadoc代码注解 跟踪代码依赖性,实现替代配置文件功能。如EventBus、ButterKnife、Dagger2依赖注入等,本篇记录一次编译注解使用过程。 元注解java.lang.annotation提供了四种元注解,专门注解其他的注解: @Documented –注解是否将包含在Javadoc文档中 @Retention –什么时候使用该注解,有三种选择,默认为CLASS RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。 RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式 RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。 @Target –表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括 ElementType.CONSTRUCTOR:用于描述构造器 ElementType.FIELD:成员变量、对象、属性(包括enum实例) ElementType.LOCAL_VARIABLE:用于描述局部变量 ElementType.METHOD:用于描述方法 ElementType.PACKAGE:用于描述包 ElementType.PARAMETER:用于描述参数 ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明 @Inherited – 是否允许子类继承该注解,默认为false Android中使用编译时注解,自动生成代码这里只讲解编译注解,其他注解待续~~~ Downloader源码传送门 DownloadAnnotations:Java工程,申明使用的注解 DownloadCompiler:Java工程,用于编译期间自动生成代码 DownloaderLibrary:下载依赖库 Demo:测试示例 创建自定义注解创建Java工程:DownloadAnnotations 声明一个Download注解,声明周期为Class,作用域为方法 12345678910111213141516@Retention(RetentionPolicy.CLASS)@Target(ElementType.METHOD)public @interface Download{ /** * 下载开始,获取文件大小 * @see com.excellence.downloader.FileDownloader.DownloadTask#getFileSize() * */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.METHOD) @interface onPreExecute { String[] value() default { NO_URL }; }} DownloadAnnotations的build.gradle配置 123456789101112apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])}tasks.withType(JavaCompile) { options.encoding = "UTF-8"}sourceCompatibility = JavaVersion.VERSION_1_7targetCompatibility = JavaVersion.VERSION_1_7 解析编译时注解创建Java工程:DownloadCompiler 解析编译时注解需要继承AbstractProcessor,并且使用注解@AutoService(Processor.class),该注解在编译时自动执行被注解的类,即自动执行该类的Java程序,用于创建类文件。 DownloadProcessor事件注解扫描器 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758@AutoService(Processor.class)public class DownloadProcessor extends AbstractProcessor{ private ElementHandler mElementHandler = null; /** * 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的{@link #init}方法,它会被注解处理工具调用,并输入{@link ProcessingEnvironment}参数。{@link ProcessingEnvironment}提供很多有用的工具类{@link Elements}、{@link Types}、{@link Filer}。 * * @param processingEnvironment */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mElementHandler = new ElementHandler(processingEnvironment.getFiler(), processingEnvironment.getElementUtils()); } /** * 必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。 * * @return */ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>(); annotations.add(Download.onPreExecute.class.getCanonicalName()); return annotations; } /** * 用来指定你使用的Java版本。 * * @return */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /** * 这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数{@link RoundEnvironment},可以让查询出包含特定注解的被注解元素。 * 该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理 * * @param set * @param roundEnv * @return */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) { mElementHandler.clean(); mElementHandler.handleDownload(roundEnv); mElementHandler.createProxyFile(); return true; }} ElementHandler元素处理(代码过长,请查看源码) 编译时自动创建事件代理的类文件的管理类,用于管理有注册代理的类,生成路径:Demo/build/generated/source/apt/debug/com/excellence/downloader/ProxyClassCounter 编译时自动创建事件代理的类文件,用于发送监听接口,生成类文件的路径:Demo/build/generated/source/apt/debug/com/zv/downloader/downloader/SingleThreadActivity$$DownloadListenerProxy DownloadCompiler的build.gradle配置 com.google.auto.service:auto-service:1.0-rc2 谷歌提供的Java 生成源代码库 com.squareup:javapoet:1.9.0 提供了各种 API 让你用各种姿势去生成 Java 代码文件,javapoet传送门123456789101112131415apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.auto.service:auto-service:1.0-rc2' compile 'com.squareup:javapoet:1.9.0' compile project(':DownloadAnnotations')}tasks.withType(JavaCompile) { options.encoding = "UTF-8"}sourceCompatibility = JavaVersion.VERSION_1_7targetCompatibility = JavaVersion.VERSION_1_7 到此注解生成代码过程结束 Downloader依赖库处理注解 根据DownloadCompile里的ElementHandler类创建注解接口ISchedulerListener 1234567891011121314public interface ISchedulerListener<TASK>{ void onPreExecute(TASK task); void onProgressChange(TASK task); void onProgressSpeedChange(TASK task); void onCancel(TASK task); void onError(TASK task); void onSuccess(TASK task);} DownloadScheduler事件调度器,注册、解绑、监听,用于处理任务状态的调度 1234567891011121314151617181920212223242526272829303132333435/** * 初始化代理参数,获取注解类集合 */private void initDownloadCounter(){ try { Class clazz = Class.forName("com.excellence.downloader.ProxyClassCounter"); Method download = clazz.getMethod("getDownloadCounter"); Object object = clazz.newInstance(); Object downloadCounter = download.invoke(object); if (downloadCounter != null) mDownloadCounter = unmodifiableSet((Set<String>) downloadCounter); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }} Demo使用注解监听使用方式12345678910111213141516// 注册Downloader.register(this);// 解绑Downloader.unregister(this);// 监听@Download.onPreExecutepublic void onPre(DownloadTask task){ /** * 注解参数不添加URL,则获取全部任务的下载监听; * 加了URL,则过滤出对应的任务的下载监听 * 如:@Download.onPreExecute({URL1, URL2}) */} 感谢 总李写代码 AriaLyy document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>Java</tag>
<tag>编译注解</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-TextView属性]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-TextView%E5%B1%9E%E6%80%A7%2F</url>
<content type="text"><![CDATA[TextView属性总结 属性 描述 android:autoText 如果设置,将自动执行输入值的拼写纠正。此处无效果,在显示输入法并输入的时候起作用。 android:bufferType 指定getText()方式取得的文本类别。选项editable类似于StringBuilder可追加字符,也就是说getText后可调用append方法设置文本内容。spannable则可在给定的字符区域使用样式。 android:capitalize 设置英文字母大写类型。此处无效果,需要弹出输入法才能看得到 android:cursorVisible 设定光标为显示/隐藏,默认显示。 android:digits 设置允许输入哪些字符。如“1234567890.+-*/%\n()” android:editable 设置是否可编辑。这里无效果。 android:editorExtras 设置文本的额外的输入数据。 android:freezesText 设置保存文本的内容以及光标的位置。 android:gravity 设置文本位置,如设置成center,文本将居中显示。 android:hint Text为空时显示的文字提示信息,可通过textColorHint设置提示信息的颜色。此属性在EditView中使用,但是这里也可以用。 android:imeOptions 附加功能,设置右下角IME动作与编辑框相关的动作,如actionDone右下角将显示一个“完成”,而不设置默认是一个回车符号。此处无用。 android:imeActionId 设置IME动作ID。 android:imeActionLabel 设置IME动作标签。 android:includeFontPadding 设置文本是否包含顶部和底部额外空白,默认为true。 android:inputMethod 为文本指定输入法,需要完全限定名(完整的包名)。例如:com.google.android.inputmethod.pinyin,但是这里报错找不到。 android:inputType 设置文本的类型,用于帮助输入法显示合适的键盘类型。这里无效果。 android:linksClickable 设置链接是否点击连接,即使设置了autoLink。 android:ems 设置TextView的宽度为N个字符的宽度。 android:maxEms 设置TextView的宽度为最长为N个字符的宽度。与ems同时使用时覆盖ems选项。 android:minEms 设置TextView的宽度为最短为N个字符的宽度。与ems同时使用时覆盖ems选项。 android:maxLength 限制显示的文本长度,超出部分不显示。 android:lines 设置文本的行数,设置两行就显示两行,即使第二行没有数据。 android:maxLines 设置文本的最大显示行数,与width或者layout_width结合使用,超出部分自动换行,超出行数将不显示。 android:minLines 设置文本的最小行数,与lines类似。 android:lineSpacingExtra 设置行间距。 android:lineSpacingMultiplier 设置行间距的倍数。如”1.2” android:numeric 如果被设置,该TextView有一个数字输入法。此处无用,设置后唯一效果是TextView有点击效果。 android:password 以小点”.”显示文本 android:phoneNumber 设置为电话号码的输入方式。 android:privateImeOptions 设置输入法选项,此处无用。 android:scrollHorizontally 设置文本超出TextView的宽度的情况下,是否出现横拉条。 android:selectAllOnFocus 如果文本是可选择的,让它获取焦点而不是将光标移动为文本的开始位置或者末尾位置。EditText中设置后无效果。 android:shadowColor 指定文本阴影的颜色,需要与shadowRadius一起使用。 android:shadowDx 设置阴影横向坐标开始位置。 android:shadowDy 设置阴影纵向坐标开始位置。 android:shadowRadius 设置阴影的半径。设置为0.1就变成字体的颜色了,一般设置为3.0的效果比较好。 android:text 设置显示文本. android:textAppearance 设置文字外观。如?android:attr/textAppearanceLargeInverse这里引用的是系统自带的一个外观,?表示系统是否有这种外观,否则使用默认的外观。可设置的值如下:textAppearanceButton/textAppearanceInverse/textAppearanceLarge/textAppearanceLargeInverse/textAppearanceMedium/textAppearanceMediumInverse/textAppearanceSmall/textAppearanceSmallInverse android:textColor 设置文本颜色 android:textColorHighlight 被选中文字的底色,默认为蓝色 android:textColorHint 设置提示信息文字的颜色,默认为灰色。与hint一起使用。 android:textColorLink 文字链接的颜色。 android:textScaleX 设置文字缩放,默认为1.0f。 android:textSize 设置文字大小,推荐度量单位sp,如15sp android:textStyle 设置字形bold(粗体) 0, italic(斜体) 1, bolditalic(又粗又斜) 2,可以设置一个或多个,用“|”隔开 android:typeface 设置文本字体,必须是以下常量值之一:normal 0, sans 1, serif 2, monospace(等宽字体) 3 android:height 设置文本区域的高度,支持度量单位:px(像素),dp,sp,in,mm(毫米) android:maxHeight 设置文本区域的最大高度 android:minHeight 设置文本区域的最小高度 android:width 设置文本区域的宽度,支持度量单位:px(像素),dp,sp,in,mm(毫米)。 android:maxWidth 设置文本区域的最大宽度 android:minWidth 设置文本区域的最小宽度 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>TextView</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android-DIY-ColorTrackView]]></title>
<url>%2FAndroid-DIY-ColorTrackView%2F</url>
<content type="text"><![CDATA[Android自定义控件:文字逐渐染色效果 源码传送门 描述效果展示类似KTV歌词逐渐染色效果 讲解思路:计算染色进度的长度,使用改变色将该长度的文字和背景染色;然后计算未染色的长度,使用原色重新将该长度文字和背景恢复,达到效果。 注意:对于文字颜色的变化,需要注意文本的长度,文本的长度并非是整个View的长度,所以注意文字染色和未染色的长度。少于文字起始位置,则使用原色;超过文字的结束位置,则使用改变色。 文字染色 横向计算的是文字长度 1234567891011121314151617181920/** * 绘制的核心就在于利用mProgress和方向去计算应该clip的范围 * * @param canvas View画布 * @param color 画笔颜色 * @param startX 染色起始位置 * @param endX 染色终止位置 */private void drawTextX(Canvas canvas, int color, int startX, int endX){ mPaint.setColor(color); // 需要还原Clip // save()先把画布的数据保存了(如matrix等),最后绘制完后再restore()则把中间对画布坐标等操作forget掉 canvas.save(Canvas.CLIP_SAVE_FLAG); // clipRect()截取画布中的一个区域 canvas.clipRect(startX, 0, endX, getMeasuredHeight()); canvas.drawText(mText, mTextStartX, getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2), mPaint); // restore()最后要将画布回复原来的数据(记住save()跟restore()要配对使用) canvas.restore();} 纵向计算的是文字高度 12345678private void drawTextY(Canvas canvas, int color, int startY, int endY){ mPaint.setColor(color); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(0, startY, getMeasuredWidth(), endY); canvas.drawText(mText, mTextStartX, getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2), mPaint); canvas.restore();} 背景染色 同理文字染色,进度的长度计算则以整个View尺寸基准。 使用 xml布局 1234567891011121314<com.excellence.shimmer.Widget.ColorTrackView android:id="@+id/colortrackview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/selector" android:focusable="true" android:padding="40dp" app:background_horizontal_padding="18dp" app:background_vertical_padding="18dp" app:direction="left" app:foreground_change_color="@color/colorPrimaryDark" app:foreground_origin_color="@color/colorAccent" app:progressable="true"/> 属性 text_origin_color 文字原始色,默认黑色 text_change_color 文字目标色,默认白色 foreground_origin_color 背景原始色,默认透明 foreground_change_color 背景目标色,默认透明 direction 方向,枚举类型:left、right、top、bottom text 文本 text_size 文本大小,默认30px max 最大进度,默认100 progress 进度,默认0 progressable 是否开启背景染色,默认不开启 感谢 张鸿洋 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>DIY</category>
</categories>
<tags>
<tag>Android</tag>
<tag>自定义控件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[正则表达式]]></title>
<url>%2F%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%2F</url>
<content type="text"><![CDATA[正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为”元字符”)。许多程序设计语言都支持利用正则表达式进行字符串操作。正则表达式是烦琐的,但它是强大的,掌握正则表达式不仅可以提高效率,还会给你带来绝对的成就感。 菜鸟学习:传送门 简介典型的搜索和替换操作要求您提供与预期的搜索结果匹配的确切文本。虽然这种技术对于对静态文本执行简单搜索和替换任务可能已经足够了,但它缺乏灵活性,若采用这种方法搜索动态文本,即使不是不可能,至少也会变得很困难。 通过使用正则表达式,可以: 测试字符串内的模式。例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 替换文本。可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。 基于模式匹配从字符串中提取子字符串。可以查找文档内或输入域内特定的文本。 示例例如Android中,提取出string.xml里的字符串,即去除标签<resource> </resource> <string> </string>等,只留下字符串。 普通方法是在编程工具Android Studio中一个个进行检索和提取字符串,这是非常麻烦的;但使用正则表达式,可以快速完成提取操作。 打开在线工具,Android Studio中Ctrl+F检索栏中也有regex匹配,输入待检测的string.xml里的文本,然后正则表达式输入:<.*?>,点击测试匹配,则可以检索出符合正则表达式的字符串;提取字符串,即使用空格替换匹配的字符串,点击下方替换,提取字符串成功,非常方便快捷。 正则表达式:<.*?> 匹配被<>包含的字符串. 表示匹配除换行符 \n 之外的任何单字符* 表示匹配前面的子表达式零次或多次,该栗子中,表示<>里的字符可以是0个,也可以是多个? 表示匹配前面的子表达式零次或一次,该栗子中,表示.*这个表达式匹配0次或1次 语法正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。 例如: runoo+b 可以匹配 runoob、runooob、runoooooob 等,+ 号代表前面的字符必须至少出现一次(1次或多次)。 runoo*b 可以匹配 runob、runoob、runoooooob 等,* 号代表字符可以不出现,也可以出现一次或者多次(0次、或1次、或多次)。 colou?r 可以匹配 color 或者 colour,? 问号代表前面的字符最多只可以出现一次(0次、或1次)。 普通字符普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。 非打印字符非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列: 字符 描述 \cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 \f 匹配一个换页符。等价于 \x0c 和 \cL。 \n 匹配一个换行符。等价于 \x0a 和 \cJ。 \r 匹配一个回车符。等价于 \x0d 和 \cM。 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 \t 匹配一个制表符。等价于 \x09 和 \cI。 \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。 特殊字符所谓特殊字符,就是一些有特殊含义的字符,如上面说的 runoo*b 中的 *,简单的说就是表示任何字符串的意思。如果要查找字符串中的 * 符号,则需要对 * 进行转义,即在其前加一个 \: runo\*ob 匹配 runo*ob。 许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符”转义”,即,将反斜杠字符\ 放在它们前面。下表列出了正则表达式中的特殊字符: 字符 描述 $ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。 ( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。 * 匹配前面的子表达式零次或多次。要匹配 字符,请使用 \。 + 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。 . 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . 。 [ 标记一个中括号表达式的开始。要匹配 [,请使用 [。 ? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。 \ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\‘ 匹配 “\”,而 ‘(‘ 则匹配 “(“。 ^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 { 标记限定符表达式的开始。要匹配 {,请使用 {。 | 指明两项之间的一个选择。要匹配 |,请使用 \|。 限定符限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。正则表达式的限定符有: 字符 描述 * 匹配前面的子表达式零次或多次。例如,zo 能匹配 “z” 以及 “zoo”。 等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。 ? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 中的”do” 。? 等价于 {0,1}。 {n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 {n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。 {n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 定位符定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。 定位符用来描述字符串或单词的边界,^ 和 $ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。 正则表达式的定位符有: 字符 描述 ^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。 $ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。 \b 匹配一个字边界,即字与空格间的位置。 \B 非字边界匹配。 注意:不能将限定符与定位符一起使用。由于在紧靠换行或者字边界的前面或后面不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。若要匹配一行文本开始处的文本,请在正则表达式的开始使用 ^ 字符。不要将 ^ 的这种用法与中括号表达式内的用法混淆。若要匹配一行文本的结束处的文本,请在正则表达式的结束处使用 $ 字符。 常用的正则表达式校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^\d{m,n}$ 零和非零开头的数字:^(0|[1-9][0-9]*)$ 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$ 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$ 正数、负数、和小数:^(-|+)?\d+(.\d+)?$ 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$ 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$ 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]*$ 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$ 非负整数:^\d+$ 或 ^[1-9]\d*|0$ 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$ 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$ 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$ 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$ 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$ 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$ 校验字符的表达式 汉字:^[\u4e00-\u9fa5]{0,}$ 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$ 长度为3-20的所有字符:^.{3,20}$ 由26个英文字母组成的字符串:^[A-Za-z]+$ 由26个大写英文字母组成的字符串:^[A-Z]+$ 由26个小写英文字母组成的字符串:^[a-z]+$ 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$ 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$ 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$ 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ 可以输入含有^%&’,;=?$\”等字符:[^%&’,;=?$\x22]+ 禁止输入含有~的字符:[^~\x22]+ 特殊需求表达式 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$ 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.? InternetURL:[a-zA-z]+://[^\s] 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=])?$ 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$ 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$ 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7} 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$) 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$) 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$ 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$ 日期格式:^\d{4}-\d{1,2}-\d{1,2} 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$ 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$ 钱的输入格式: 有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]*$ 这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$ 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$ 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$ 必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$ 这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$ 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$ 1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$ 备注:这就是最终结果了,别忘了”+”可以用”*”替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$ 中文字符的正则表达式:[\u4e00-\u9fa5] 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)) 空白行的正则表达式:\n\s*\r (可以用来删除空白行) HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> ( 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式) 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始) 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字) IP地址:((?:(?:25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d?\d)) 个人记录本 正则表达式 说明 示例 (?<=A).*(?=B) 截取AA和BB之间的字符串(不包括A、B) Awww.baidu.comB -> www.baidu.com A.*?B 匹配两个字符串A与B中间的字符串包含A与B baiduAbaidu.comBcom -> Abaidu.comB A.*?(?=B) 匹配两个字符串A与B中间的字符串包含A但是不包含B baiduAbaidu.comBcom -> Abaidu.com 推广Java(Android工具类)中常用的正则表达式:传送门 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>技术小贴士</category>
</categories>
<tags>
<tag>正则表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android小贴士-资源解析]]></title>
<url>%2FAndroid%E5%B0%8F%E8%B4%B4%E5%A3%AB-%E8%B5%84%E6%BA%90%E8%A7%A3%E6%9E%90%2F</url>
<content type="text"><![CDATA[解析Android资源id 解析资源提取资源id中的字段 resId: 12345R.drawable.xxxxR.string.xxxxR.color.xxxxR.dimen.xxxx··· 提取drawable、string、color、dimen等 123456public String getResourceTypeName(int resId){ Resources resources = mView.getResources(); String attrTypeName = resources.getResourceTypeName(resId); retuen attrTypeName;} 提取xxxx资源名 123456public String getResourceEntryName(int resId){ Resources resources = mView.getResources(); String attrEntryName = resources.getResourceEntryName(resId); retuen attrEntryName;} 提取资源中的包名 123456public String getResourcePackageName(int resId){ Resources resources = mView.getResources(); String packageName = resources.getResourcePackageName(resId); retuen packageName;} 提取资源中的包名+资源 123456public String getResourceName(int resId){ Resources resources = mView.getResources(); String resourceName = resources.getResourceName(resId); return resourceName;} 组装资源已知资源的PackageName、TypeName、EntryName,读取资源Id,通过该方式可判断资源是否存在。 123456789/** * * @return resId=0 means resId is not exist. */public int getResourceId(String packageName, String typeName, String entryName){ int resId = getResources().getIdentifier(entryName, typeName, packageName); return resId;} 推广Android应用中换肤可参考该方式:AndroidSkinLoader document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>技术小贴士</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Resource</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Javadoc-代码文档]]></title>
<url>%2FJavadoc-%E4%BB%A3%E7%A0%81%E6%96%87%E6%A1%A3%2F</url>
<content type="text"><![CDATA[写代码时可以用Javadoc来声明一些参数,以明确的指示参数的类型,让编译器帮助检查代码,让代码更安全,更具可读性,即代码就是文档。 文档标记,可以尽量尝试使用:把自己的思想通过适合的方式表达给他人是一种好习惯。 @author做好事留名,表明作者、时间、描述等,还阔以顺带推广博客网站 Android Studio中可以使用该模板,新建类文件时,自动生成12345678/** * <pre> * author : VeiZhang * blog : https://veizhang.github.io/ * time : 2016/6/1 * desc : ListView、GridView通用适配器 * </pre> */ @version记录版本号 123/** * @version 1.0 */ @NonNull告诉编译器,这个参数是非空的,编译器会帮你做出检查 栗子:传入的view不能为空,返回值不能为空1234@NonNullpublic static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) { return make(view, view.getResources().getText(resId), duration);} @Nullable声明参数是可能为空的 栗子:传入的id可能为空,返回值可能为空12345@Nullableprotected List<String> count(@Nullable String id) { return null;} @param输入参数的名称说明,可以自动创建,在Android Studio中,先写好一个方法,然后在方法的上一行输入/**然后Enter即可自动创建模板 栗子:说明传参的String1234567/** * @param String 传入的id */protected List<String> count(@Nullable String id) { return null;} @return输出参数说明,跟@param一样的创建步骤 栗子:说明返回值List1234567891011121314151617/** * @param String 传入的id * * @return 字符串数组 */protected List<String> count(@Nullable String id) { return null;}/** * @return {@code true}:空<br>{@code false}:不为空 */public boolean isNULL(){ return true;} @see链接目标,表示参考。会在java 文档中生成一个超链接,链接到参考的类容 在注释中指向类或方法或变量,用.或#链接123456789101112/** * @see #field // 当前类中的变量 * @see #Constructor(Type, Type...) // 当前类中的构造函数 * @see #method(Type, Type,...) // 当前类中的方法 * @see Class // 链接类 * @see Class#field // 链接类中的变量 * @see Class#Constructor(Type, Type...) //类的构造函数 * @see Class#method(Type, Type,...) // 类的方法 * @see package.Class * @see package.Class#field * @see package.Class#method(Type, Type,...) */ @link链接到一个目标,用法同@see,形式如{@link …}1234/** * {@link #field} * ... */ @deprecated标识对象过期 适用范围:文件、类、方法 栗子:表明该方法过期,如果使用该方法则会出现删除线:isNULL12345@deprecatedpublic boolean isNULL(){ return true;} @throws标识出方法可能抛出的异常 适用范围:方法 栗子:可能抛出IO异常123456789/** * @throws IOException If an input or output exception occurred * * return */public boolean isNULL() throws IOException{ return true;} 资源声明参数是资源类型 常用的 @StringRes 字符串资源 @DrawableRes 图片资源 @ColorRes 颜色资源 @ColorInt 颜色值 @AnimationRes 动画资源 @AnyRes 任何资源 @IdRes id资源 @LayoutRes 布局资源 其他 @IntArrayRes @IntegerRes @MovieRes @TextRes @TextArrayRes @StringArrayRes 栗子:表示传参的类型 12345678910public void setTextColor(@ColorRes int textColorRes){ // 表示传参颜色资源:R.color.black // getResources().getColor(textColorRes) 即为颜色值}public void setTextColor(@ColorInt int textColor){ // 表示传参颜色值:Color.BLACK} 枚举 IntDef整形枚举 StringDef字符串类型枚举 1StringDef 同用法 IntDef 栗子: 单一选择12345678910111213141516171819202122232425262728//visible只能是View.VISIBLE、View.INVISIBLE、View.GONE,不能用0 1 2 替代,更不能用其他的数值,如果传参是其他的数值,则编译器会报错new View().setVisible(int visible); public class View{ private int mVisible; public static final int VISIBLE = 0; public static final int INVISIBLE = 1; public static final int GONE = 2; @IntDef({ VISIBLE, INVISIBLE, GONE }) public @interface Visible { } public void setVisible(@Visible int visible) { mVisible = visible; } @Visible public int getVisible() { return mVisible; }} 多种选择12345678910111213141516171819202122232425262728//多种模式结合,即flag = false时,变成单一选择view.setVisible(View.INVISIBLE | View.GONE);public class View{ private int mVisible; public static final int VISIBLE = 0; public static final int INVISIBLE = 1; public static final int GONE = 2; @IntDef(flag = true, value = { VISIBLE, INVISIBLE, GONE }) public @interface Visible { } public void setVisible(@Visible int visible) { mVisible = visible; } @Visible public int getVisible() { return mVisible; }} 值约束用于约束参数的范围,很好避免参数出错 @IntRange约束int或long类型的范围 @FloatRange约束float或double类型的范围 栗子:@FloatRange:范围是0-1,不是此范围会报错 123456789public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float value){ }public void setAlpha(@IntRange(from = 0, to = 255) int value){ } @Size约束数据、集合以及字符串 例子: 集合不能为空:@size(min=1) 字符串最大6个:@size(max=6) 数组大小只能是2个:@size(2) 数组大小是2的倍数:@size(multiple=2) 12345// 传入 text 长度为8,则会报错,现在最大为6public void setAlpha(@size(max=6) String text){ } 大神都是从细节抓起 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Javadoc</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-level-list]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-level-list%2F</url>
<content type="text"><![CDATA[Level List类型的图形用来管理一组可进行切换的图片。系统会根据level值来自动匹配对应的图片,如手机wifi的信号强度图标,电量剩余图标,就是通过Level List类型来显示的。 语法12345678<?xml version="1.0" encoding="utf-8"?><level-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/drawable_resource" android:maxLevel="integer" android:minLevel="integer" /></level-list> maxLevel:匹配的最大值 minLevel:匹配的最小值 drawable:匹配的图片 用法基本用法在drawable文件夹中新建一个xml文件,然后其它控件通过background引用,ImageView还可以通过src引用,level最开始匹配的值是0。 示例: 1234567891011121314<?xml version="1.0" encoding="utf-8"?><level-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 值为0~5时显示的图片 --> <item android:minLevel="0" android:maxLevel="5" android:drawable="@drawable/sblue" /> <!-- 值为6~10时显示的图片 --> <item android:minLevel="6" android:maxLevel="10" android:drawable="@drawable/green" /></level-list> 引用方法 xml文件里设置,或者使用代码设置 12345678910111213<!-- ImageView可通过src或background设置 --><ImageView android:id="@+id/iv_photo_frame" android:layout_width="match_parent" android:src="@drawable/bg_level" android:layout_height="100dp"/><!-- 其它控件通过background设置 --><TextView android:id="@+id/tv_leveltext" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/bg_level"/> 代码控制Level切换图片ImageView 如果是通过src引用的,可通过getDrawable获取LevelListDrawable对象,也可直接通过setImageLevel方法直接设置level值 12345678910111213141516/** * ImageView 设置Level */ public void setLevel(int level) { mImageView.setLevel(level); }/** * ImageView 根据传入的level值显示对应的图片 */public void changeImageView(int level) { LevelListDrawable levelListDrawable = (LevelListDrawable) mImageView.getDrawable(); levelListDrawable.setLevel(level);} 其他控件如果是通过background引用的,控件可直接通过getBackground方法获取LevelListDrawable对象 12LevelListDrawable levelListDrawable = (LevelListDrawable) mView.getBackground(); levelListDrawable.setLevel(level); 注意 item元素级别不能出现负数 level-list中item放置的前后顺序需要注意,系统会从上往下匹配,如果查找到的item符合当前的状态,则不会再继续向下查找其它item如下:设置Level为2,则第一个item匹配,就不匹配第二个item,顺序很重要 123456789<?xml version="1.0" encoding="utf-8"?><level-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:maxLevel="5" android:drawable="@drawable/sblue" /> <item android:maxLevel="2" android:drawable="@drawable/green" /></level-list> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>level-list</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android黄金篇-SQLite性能优化]]></title>
<url>%2FAndroid%E9%BB%84%E9%87%91%E7%AF%87-SQLite%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-2017-07-9%2F</url>
<content type="text"><![CDATA[优化Android中SQLite性能 项目地址: https://github.com/VeiZhang/DBDao 插入数据比较下面四种方式,比较增加1000条数据的效率 普通语法插入 平均耗时:6050ms 12345678private void normalInsert(){ for (int i = 0; i < INSERT_COUNT; i++) { People people = new People("name" + i, i); mDBUtil.insert(people); }} 预编译插入 平均耗时:5700ms 12345678910111213private void stmtInsert(){ String sql = String.format("insert into %1$s(%2$s, %3$s) values(?, ?)", TABLE_NAME, PEOPLE_NAME, PEOPLE_AGE); SQLiteStatement stmt = mDBUtil.getDatabase().compileStatement(sql); for (int i = 0; i < INSERT_COUNT; i++) { // 绑定的数据不能为null,并且是从1开始绑定 People people = new People("name" + i, i); stmt.bindString(1, people.name); stmt.bindLong(2, people.age); stmt.executeInsert(); }} 事务插入 平均耗时:120ms 12345678910111213private void transactionInsert(){ try { mDBUtil.getDatabase().beginTransaction(); normalInsert(); mDBUtil.getDatabase().setTransactionSuccessful(); } finally { mDBUtil.getDatabase().endTransaction(); }} 事务+预编译插入 平均耗时:80ms 1234567891011121314private void transactionStmtInsert(){ // 类似GreenDao的做法 try { mDBUtil.getDatabase().beginTransaction(); stmtInsert(); mDBUtil.getDatabase().setTransactionSuccessful(); } finally { mDBUtil.getDatabase().endTransaction(); }} 相比较下,事务+预编译的方式,对插入性能提升很大,GreenDao第三方数据依赖库就是使用事务+预编译的方式。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>SQLite</tag>
<tag>性能优化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android铂金篇-SQLite全文检索]]></title>
<url>%2FAndroid%E9%93%82%E9%87%91%E7%AF%87-SQLite%E5%85%A8%E6%96%87%E6%A3%80%E7%B4%A2%2F</url>
<content type="text"><![CDATA[在检索大量数据时,普通的数据检索查询条件处理不当时,会检索全部的记录数,导致耗费一定的时间,除了优化查询语句、分页检索等方式提高效率外,全文检索可以让查询速度变得很快。 全文检索简介全文检索的简称是FTS(full text search), 在sqlite里面的话,我们有FTS3和FTS4可以使用。FTS其实就是创建一张虚拟表以供查询;它的一个很重要的作用就是可以让查询速度变得很快。根据官方统计数据统计显示,下面是两种查询的速度比较: 创建两种表 12CREATE VIRTUAL TABLE enrondata1 USING fts3(content TEXT); /* FTS3 table */CREATE TABLE enrondata2(content TEXT); /* Ordinary table */ 查询速度比较 12SELECT count(*) FROM enrondata1 WHERE content MATCH 'linux'; /* 0.03 seconds */SELECT count(*) FROM enrondata2 WHERE content LIKE '%linux%'; /* 22.5 seconds */ 全文检索使用创建虚表对于已经存在数据记录的数据表,可以创建虚表,然后复制这些记录到虚表中,然后对虚表进行查询,而非查询原数据表 12345678// 创建虚表vir_table,字段可以不带类型CREATE VIRTUAL TABLE IF NOT EXISTS vir_table USING FTS3(column1, column2...);// 拷贝原表记录INSERT INTO vir_table SELECT * FROM table// 删除虚表DROP TABLE IF EXISTS vir_table 虚表增删改查虚表的基本操作与普通表的操作是一样的1234567891011// 增INSERT INTO vir_table (column1, column2...) VALUES ('column1', 'column2'...);// 删DELETE FROM vir_table WHERE column1='column1';// 改UPDATE vir_table SET column1='column' WHERE column1='column1';// 查SELECT * FROM vir_table WHERE column1 like 'column1%' and column2 like 'column2%'; 虽然可以使用普通表的查询,但是效率上非常糟糕(SQLite将做全表扫描),体现不出全文检索的好处来,虚表的检索使用的是 MATCH 运算符: 12SELECT column1, column2 FROM vir_table WHERE column1 MATCH 'column1';SELECT column1, column2 FROM vir_table WHERE column2 MATCH 'column2'; MATCH 右侧的表达式支持模糊查询(类似like)、之处指定列查询、支持AND/OR/NEAR/NOT等运算,模糊查询使用的是*,不再是%,下面是MATCH的几种写法: 1234SELECT column1, column2 FROM vir_table WHERE vir_table MATCH 'col*';SELECT column1, column2 FROM vir_table WHERE vir_table MATCH 'column1:col*';SELECT column1, column2 FROM vir_table WHERE vir_table MATCH 'column1 AND column2';SELECT column1, column2 FROM vir_table WHERE vir_table MATCH '(column1 NEAR column3) OR (column2 AND column4)'; 注意: 一条sql语句里只能出现一次MATCH判断,WHERE column1 MATCH 'column1' AND column2 MATCH 'column2'是不行的,可以改成WHERE vir_table MATCH 'column1:col* AND column2:col*'。 MATCH的模糊查询不能是这种形式:'*col*',只能模糊右边:col*或者不使用通配符:col。 全文检索会导致虚表增加,更新和删除记录变慢,需要考虑实际情况进行利弊权衡,对于查询操作量级较大,可以考虑全文检索。 触发器对虚表的增删改查不会影响到原普通表的数据记录,为了保证虚表、原普通表数据一致,所以要对原普通表进行修改,在对虚表增删改的同时,对原普通表同时进行增删改。对于多个地方修改虚表和原普通表,同时操作两个表比较麻烦,因此可以考虑触发器,即在普通表中建立触发器 (虚表中不可以建触发器) ,当虚表操作时,触发器就会触发,自动对普通表进行增删改。 创建虚表的同时,创建触发器: 1234567891011// 创建插入触发器CREATE TRIGGER IF NOT EXISTS tri_insert AFTER INSERT ON vir_table BEGIN INSERT INTO table(column1, column2) VALUES(NEW.column1, NEW.column2); END// 创建删除触发器CREATE TRIGGER IF NOT EXISTS tri_delete AFTER DELETE ON vir_table BEGIN DELETE FROM table WHERE column1 = OLD.column1; END// 创建修改触发器CREATE TRIGGER IF NOT EXISTS tri_update AFTER UPDATE ON vir_table BEGIN UPDATE table SET column1 = NEW.column1 WHERE column2 = NEW.column2; END// 触发器的删除DROP TRIGGER IF EXISTS tri_insert 参考 SQLite全文检索(1) SQLite全文检索(2) document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>SQLite</tag>
<tag>全文检索</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android黄金篇-SQLite数据库]]></title>
<url>%2FAndroid%E9%BB%84%E9%87%91%E7%AF%87-SQLite%E6%95%B0%E6%8D%AE%E5%BA%93%2F</url>
<content type="text"><![CDATA[Android SQLite,是一款轻量级的关系型数据库。在很多嵌入式设备都用来存储数据。 SQLite介绍SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百K的内存就足够了,因而特别适合在移动设备上使用。 SQLite特点 轻量级 使用 SQLite 只需要带一个动态库,就可以享受它的全部功能,而且那个动态库的尺寸想当小。 独立性 SQLite 数据库的核心引擎不需要依赖第三方软件,也不需要所谓的“安装”。 隔离性 SQLite 数据库中所有的信息(比如表、视图、触发器等)都包含在一个文件夹内,方便管理和维护。 跨平台 SQLite 目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:Android和IOS。 多语言接口 SQLite 数据库支持多语言编程接口。 安全性 SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据。 弱类型的字段 同一列中的数据可以是不同类型 SQLite数据类型SQLite 数据类型是一个用来指定任何对象的数据类型的属性。SQLite 中的每一列,每个变量和表达式都有相关的数据类型。您可以在创建表的同时使用这些数据类型。SQLite 使用一个更普遍的动态类型系统。在 SQLite 中,值的数据类型与值本身是相关的,而不是与它的容器相关。一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据存入值自动判断。SQLite具有以下五种常用的数据类型: 存储类 描述 NULL 值是一个 NULL 值。 INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。 REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。 TEXT 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。 BLOB 值是一个 blob 数据,完全根据它的输入存储。 传送门 SQLiteDatabase的介绍Android提供了创建和是用SQLite数据库的API。SQLiteDatabase代表一个数据库对象,提供了操作数据库的一些方法。在Android的SDK目录下有sqlite3工具,我们可以利用它创建数据库、创建表和执行一些SQL语句。下面是SQLiteDatabase的常用方法。 方法 描述 openOrCreateDatabase(String path,SQLiteDatabase.CursorFactory factory) 打开或创建数据库 insert(String table,String nullColumnHack,ContentValues values) 插入一条记录 delete(String table,String whereClause,String[] whereArgs) 删除一条记录 query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy) 查询一条记录 update(String table,ContentValues values,String whereClause,String[] whereArgs) 修改记录 execSQL(String sql) 执行一条SQL语句 close() 关闭数据库 打开或创建数据库在Android 中使用SQLiteDatabase的静态方法openOrCreateDatabase(String path,SQLiteDatabae.CursorFactory factory)打开或者创建一个数据库。它会自动去检测是否存在这个数据库,如果存在则打开,不存在则创建一个数据库;创建成功则返回一个SQLiteDatabase对象,否则抛出异常FileNotFoundException。 下面是创建名为“stu.db”数据库的代码:openOrCreateDatabase(String path,SQLiteDatabae.CursorFactory factory)参数1 数据库创建的路径参数2 一般设置为null就可以了1db=SQLiteDatabase.openOrCreateDatabase("/data/data/com.lingdududu.db/databases/stu.db",null); 创建表创建了一张用户表,属性列为:id(主键并且自动增加)、sname(学生姓名)、snumber(学号)1234567private void createTable(SQLiteDatabase db){ // 创建表SQL语句 String stu_table="CREATE TABLE IF NOT EXISTS usertable(_id integer primary key autoincrement,sname text,snumber text)"; // 执行SQL语句 db.execSQL(stu_table);} 判断数据表是否存在sqlite_master这个表是系统的表,sqlitedatabase会为每一个数据库创建一个这样的表,目的是用来记录用户自己创建的表的索引。如果用户进行了表的增删改查操作,这个表都会相应的进行索引的改动。通过查询sqlite_master,判断表是否存在。123456789101112private boolean isTableExist(){ String sql = "select count(*) as c from sqlite_master where type ='table' and name = 'tableName'"; Cursor cursor = db.rawQuery(sql, null); if(cursor.moveToNext()) { int count = cursor.getInt(0); if(count>0) retutrn true; } return false;} 数据表删除1234567private void drop(SQLiteDatabase db){ // 删除表的SQL语句 String sql ="DROP TABLE IF EXISTS stu_table"; // 执行SQL db.execSQL(sql); } 数据总记录数12345678910111213public long allCaseNum( ){ long count = 0; // 查询记录数语句 String sql = "select count(*) from tableName"; Cursor cursor = db.rawQuery(sql, null); if(cursor != null) { cursor.moveToFirst(); long count = cursor.getLong(0); cursor.close(); } return count; } 插入数据两种方式插入数据 SQLiteDatabase的insert(String table,String nullColumnHack,ContentValues values)方法参数1 表名称,参数2 空列的默认值参数3 ContentValues类型的一个封装了列名称和列值的Map; 1234567891011private void insert(SQLiteDatabase db){ // 实例化常量值 ContentValues cValue = new ContentValues(); // 添加用户名 cValue.put("sname","xiaoming"); // 添加密码 cValue.put("snumber","01005"); // 调用insert()方法插入数据 db.insert("stu_table",null,cValue); } 编写插入数据的SQL语句,直接调用SQLiteDatabase的execSQL()方法来执行 1234567private void insert(SQLiteDatabase db){ //插入数据SQL语句 String stu_sql="insert into stu_table(sname,snumber) values('xiaoming','01005')"; //执行SQL语句 db.execSQL(sql); } 删除数据两种方式 调用SQLiteDatabase的delete(String table,String whereClause,String[] whereArgs)方法参数1 表名称参数2 删除条件参数3 删除条件值数组 123456789private void delete(SQLiteDatabase db) { // 删除条件 String whereClause = "id=?"; // 删除条件参数 String[] whereArgs = {String.valueOf(2)}; // 执行删除 db.delete("stu_table",whereClause,whereArgs); } 编写删除SQL语句,调用SQLiteDatabase的execSQL()方法来执行删除 1234567private void delete(SQLiteDatabase db) { // 删除SQL语句 String sql = "delete from stu_table where _id = 6"; // 执行SQL语句 db.execSQL(sql); } 修改数据两种方式 调用SQLiteDatabase的update(String table,ContentValues values,String whereClause, String[] whereArgs)方法参数1 表名称参数2 跟行列ContentValues类型的键值对Key-Value参数3 更新条件(where字句)参数4 更新条件数组 12345678910111213private void update(SQLiteDatabase db) { // 实例化内容值 ContentValues values = new ContentValues(); // 在values中添加内容 values.put("snumber","101003"); // 修改条件 String whereClause = "id=?"; // 修改添加参数 String[] whereArgs={String.valuesOf(1)}; // 修改 db.update("usertable",values,whereClause,whereArgs); } 编写更新的SQL语句,调用SQLiteDatabase的execSQL执行更新 1234567private void update(SQLiteDatabase db){ // 修改SQL语句 String sql = "update stu_table set snumber = 654321 where id = 1"; // 执行SQL db.execSQL(sql); } 查询数据在Android中查询数据是通过Cursor类来实现的,当我们使用SQLiteDatabase.query()方法时,会得到一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多有关查询的方法,具体方法如下:public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit); 各个参数的意义说明:参数table:表名称参数columns:列名称数组参数selection:条件字句,相当于where参数selectionArgs:条件字句,参数数组参数groupBy:分组列参数having:分组条件参数orderBy:排序列参数limit:分页查询限制参数Cursor:返回值,相当于结果集ResultSet Cursor是一个游标接口,提供了遍历查询结果的方法,如移动指针方法move(),获得列值方法getString()等. Cursor游标常用方法 方法 描述 getCount() 获得总的数据项数 isFirst() 判断是否第一条记录 isLast() 判断是否最后一条记录 moveToFirst() 移动到第一条记录 moveToLast() 移动到最后一条记录 move(int offset) 移动到指定记录 moveToNext() 移动到下一条记录 moveToPrevious() 移动到上一条记录 getColumnIndexOrThrow(String columnName) 根据列名称获得列索引 getInt(int columnIndex) 获得指定列索引的int类型值 getString(int columnIndex) 获得指定列缩影的String类型值 示例:查询数据12345678910111213141516171819202122private void query(SQLiteDatabase db) { // 查询获得游标 Cursor cursor = db.query ("usertable",null,null,null,null,null,null); // 判断游标是否为空 if(cursor.moveToFirst() { // 遍历游标 for(int i=0;i<cursor.getCount();i++) { cursor.move(i); // 获得ID int id = cursor.getInt(0); // 获得用户名 String username=cursor.getString(1); // 获得密码 String password=cursor.getString(2); // 输出用户信息 System.out.println(id+":"+sname+":"+snumber); } } } SQLiteOpenHelper该类是SQLiteDatabase一个辅助类。这个类主要生成一 个数据库,并对数据库的版本进行管理。在特殊情况下,即当打开外部数据库如/sdcard/sqlite.db,则不会执行管理;而一般情况下,即打开/data/data/package/databases/应用里的内部数据库会执行管理。当在程序当中调用这个类的方法getWritableDatabase()或者 getReadableDatabase()方法的时候,如果当时没有数据,那么Android系统就会自动生成一个数据库。 SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里面的3个函数: onCreate(SQLiteDatabase)在数据库第一次生成的时候会调用这个方法,也就是说,只有在创建数据库的时候才会调用,当然也有一些其它的情况,一般我们在这个方法里边生成数据库表。 onUpgrade(SQLiteDatabase,int,int)当数据库需要升级的时候,Android系统会主动的调用这个方法。一般我们在这个方法里边删除数据表,并建立新的数据表,当然是否还需要做其他的操作,完全取决于应用的需求。 onOpen(SQLiteDatabase)这是当打开数据库时的回调函数,一般在程序中不是很常使用。 SQLiteOpenHelper操作内部数据库数据库帮助类SQLiteOpenHelper操作内部数据库时,在应用安装后第一次启动时会执行onCreate方法,在此方法中创建数据库。123456789101112131415161718192021222324252627public class DBUtils extends SQLiteOpenHelper { protected static final int DB_VERSION = BuildConfig.dbVersion; public static final String SQLITE_MASTER = "sqlite_master "; protected SQLiteDatabase mDatabase = null; public DBUtils(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); try { mDatabase = getWritableDatabase(); } catch (Exception e) { mDatabase = getReadableDatabase(); } } @Override public void onCreate(SQLiteDatabase db) { if (mDatabase == null) mDatabase = db; } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { mDatabase = db; }} SQLiteOpenHelper操作外部数据库数据库帮助类SQLiteOpenHelper操作外部数据库时,在应用安装后第一次启动时不会执行onCreate方法。 借助ContextWrapper类打开外部数据库,注意数据库文件需要判断是否存在,否则报错 1234567891011121314151617181920212223242526272829303132333435public class DatabaseContext extends ContextWrapper { private String mDBPath = null; private DatabaseContext(Context base) { super(base); } public DatabaseContext(Context base, String dbPath) { super(base); mDBPath = dbPath; } @Override public File getDatabasePath(String name) { String dbPath = mDBPath + name; if (FileUtils.isFileExists(dbPath)) { return new File(dbPath); } else { return null; } } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { int flags = SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; return SQLiteDatabase.openDatabase(getDatabasePath(name).getAbsolutePath(), factory, flags, null); } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { int flags = SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; return SQLiteDatabase.openDatabase(getDatabasePath(name).getAbsolutePath(), factory, flags, errorHandler); }} 创建数据库帮助类,开启数据库操作 12345678910public class UtilDao{ private void initDao() { // DB_PATH 数据库文件路径,如:/sdcard/db/sqlite.db DatabaseContext dbContext = new DatabaseContext(mContext, DB_PATH); // 然后创建数据库帮助类,后面的操作与内部数据库一致。参数Context、数据表名、数据库版本号等 DBUtils dbUtils = new DBUtils(mContext, tableName, null, dbVersion); }} Android常用SQLite命令 打开数据库 1> sqlite3 sql.db 查看 1234sqlite> .database // 显示数据库信息sqlite> .tables // 显示表名sqlite> .schema // 命令可以查看创建数据表时的SQL命令sqlite> .schema table_name // 查看创建表table_name时的SQL的命令 插入记录 1insert into table_name values (field1, field2, field3...); 删除 1drop table if exists table_name; // 删除表 修改 1update table set field1='xxxxx' where option1=''; 查询 12select * from table_name; // 查看table_name表中所有记录select * from table_name where field1='xxxxx'; // 查询符合指定条件的记录 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>SQLite</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-selector]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-selector%2F</url>
<content type="text"><![CDATA[selector中文的意思选择器,在Android中常常用来作组件的背景,这样做的好处是省去了用代码控制实现组件在不同状态下不同的背景颜色或图片的变换。使用十分方便。 selector的定义selector就是状态列表(StateList),可以添加一个或多个item子标签,而相应的状态是在item标签中定义的。定义的xml文件可以作为两种资源使用:drawable和color。作为drawable资源使用时,一般和shape一样放于drawable目录下,item必须指定android:drawable属性;作为color资源使用时,则可以放于drawable目录下,也可放于color目录下,item必须指定android:color属性。 设置状态: android:state_enabled: 设置触摸或点击事件是否可用状态,一般只在false时设置该属性,表示不可用状态 android:state_pressed: 设置是否按压状态,一般在true时设置该属性,表示已按压状态,默认为false android:state_selected: 设置是否选中状态,true表示已选中,false表示未选中 android:state_checked: 设置是否勾选状态,主要用于CheckBox和RadioButton,true表示已被勾选,false表示未被勾选 android:state_checkable: 设置勾选是否可用状态,类似state_enabled,只是state_enabled会影响触摸或点击事件,而state_checkable影响勾选事件 android:state_focused: 设置是否获得焦点状态,true表示获得焦点,默认为false,表示未获得焦点 android:state_window_focused: 设置当前窗口是否获得焦点状态,true表示获得焦点,false表示未获得焦点,例如拉下通知栏或弹出对话框时,当前界面就会失去焦点;另外,ListView的ListItem获得焦点时也会触发true状态,可以理解为当前窗口就是ListItem本身 android:state_activated: 设置是否被激活状态,true表示被激活,false表示未激活,API Level 11及以上才支持,可通过代码调用控件的setActivated(boolean)方法设置是否激活该控件 android:state_hovered: 设置是否鼠标在上面滑动的状态,true表示鼠标在上面滑动,默认为false,API Level 14及以上才支持 Color-Selectorcolor-selector 就是颜色状态列表,可以跟color一样使用,颜色会随着组件的状态而改变。 Color-Selector语法123456789101112<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:color="color" // 颜色值,#RGB,$ARGB,#RRGGBB,#AARRGGBB android:state_pressed=["true" | "false"] // 是否触摸 android:state_focused=["true" | "false"] // 是否获得焦点 android:state_selected=["true" | "false"] // 是否被状态 android:state_checkable=["true" | "false"] // 是否可选 android:state_checked=["true" | "false"] // 是否选中 android:state_enabled=["true" | "false"] // 是否可用 android:state_window_focused=["true" | "false"] /> // 是否窗口聚焦</selector> Color-Selector示例 在res/drawable/目录下新建文件color_selector.xml 12345678<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:color="#ffff0000"/> <!-- 按压时 --> <item android:state_focused="true" android:color="#ff0000ff"/> <!-- 有焦点时 --> <item android:color="#ff000000"/> <!-- 默认时 --></selector> xml布局调用 12345678<Button android:id="@+id/bt_about" style="@style/Button_style" android:layout_width="250dp" android:layout_height="50dp" android:layout_margin="5dp" android:textColor="@drawable/color_selector" android:text="@string/about" /> 效果 Drawable-Selectordrawable-selector 是背景图状态列表,可以跟图片一样使用,背景会根据组件的状态变化而变化。 Drawable-Selector语法1234567891011121314151617<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize=["true" | "false"] // drawable的大小是否当中状态变化,true表示是变化,false表示不变换,默认为false android:dither=["true" | "false"] // 当位图与屏幕的像素配置不一样时(例如,一个ARGB为8888的位图与RGB为555的屏幕)会自行递色(dither)。设置为false时不可递色。默认true android:variablePadding=["true" | "false"] > // 内边距是否变化,默认false <item android:drawable="drawable" //图片资源 android:state_pressed=["true" | "false"] // 是否触摸 android:state_focused=["true" | "false"] // 是否获取到焦点 android:state_hovered=["true" | "false"] // 光标是否经过 android:state_selected=["true" | "false"] // 是否选中 android:state_checkable=["true" | "false"] // 是否可勾选 android:state_checked=["true" | "false"] // 是否勾选 android:state_enabled=["true" | "false"] // 是否可用 android:state_activated=["true" | "false"] // 是否激活 android:state_window_focused=["true" | "false"] /> // 所在窗口是否获取焦点</selector> Drawable-Selector示例 在res/drawable/目录下新建文件drawable_selector.xml 1234567<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_selected="true" android:drawable="@drawable/button_bg_press" /> // 选中时 <item android:state_focused="true" android:drawable="@drawable/button_bg_press" /> // 有焦点时 <item android:state_pressed="true" android:drawable="@drawable/button_bg_press" /> // 按压时 <item android:drawable="@drawable/button_bg_normol" /></selector> xml布局调用 12345678<Button android:id="@+id/bt_about" style="@style/Button_style" android:background="@drawable/drawable_selector" android:layout_width="250dp" android:layout_height="50dp" android:layout_margin="5dp" android:text="@string/about" /> 效果 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>selector</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-layer-list]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-layer-list%2F</url>
<content type="text"><![CDATA[layer-list可以将多个drawable按照顺序层叠在一起显示,默认情况下,所有的item中的drawable都会自动根据它附上view的大小而进行缩放,layer-list中的item是按照顺序从下往上叠加的,即先定义的item在下面,后面的依次往上面叠放。 简单示例 Drawable,layer-list结合shape 123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item > <shape android:shape="rectangle" > <solid android:color="#0000ff"/> </shape> </item> <item android:bottom="25dp" android:top="25dp" android:left="25dp" android:right="25dp"> <shape android:shape="rectangle" > <solid android:color="#00ff00" /> </shape> </item> <item android:bottom="50dp" android:top="50dp" android:left="50dp" android:right="50dp"> <shape android:shape="rectangle" > <solid android:color="#ff0000" /> </shape> </item> </layer-list> 布局文件 123456789<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="150dp" android:layout_height="150dp" android:background="@drawable/layer_list"/> </LinearLayout> 效果图 红色item最后定义在最上方,绿色item中间,最先定义蓝色最下边,属性设置如:1234android:bottom="50dp" 表示该item下边以ImageView下边界往里面缩了50dpandroid:top="50dp" 表示该item上边以ImageView上边界往里面缩了50dpandroid:left="50dp" 表示该item左边以ImageView左边界往里面缩了50dpandroid:right="50dp" 表示该item右边以ImageView右边界往里面缩了50dp 其他item类似 layer-list实现三面边框 Drawable 1234567891011121314<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item > <shape android:shape="rectangle" > <solid android:color="#ff0000"/> </shape> </item> <item android:bottom="2dp" android:top="2dp" android:right="2dp"> <shape android:shape="rectangle" > <solid android:color="#ffffff" /> </shape> </item> </layer-list> 效果图 阴影效果 使用layer-list可以将多个drawable按照顺序层叠在一起显示,像上图中的Tab,是由一个红色的层加一个白色的层叠在一起显示的结果,阴影的圆角矩形则是由一个灰色的圆角矩形叠加上一个白色的圆角矩形。先看下代码吧,以下是Tab背景的代码: 1234567891011121314151617181920212223242526<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 第一种加载方式 --> <!--<item android:drawable="@drawable/bg_tab_selected" android:state_checked="true" />--> <!-- 第二种加载方式 --> <item android:state_checked="true"> <layer-list> <!-- 红色背景 --> <item> <color android:color="#E4007F" /> </item> <!-- 白色背景 --> <item android:bottom="4dp" android:drawable="@android:color/white" /> </layer-list> </item> <item> <layer-list> <!-- 红色背景 --> <item> <color android:color="#E4007F" /> </item> <!-- 白色背景 --> <item android:bottom="1dp" android:drawable="@android:color/white" /> </layer-list> </item> </selector> 以下是带阴影的圆角矩形: 123456789101112131415161718192021<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 灰色阴影 --> <item android:left="2dp" android:top="4dp"> <shape> <solid android:color="@android:color/darker_gray" /> <corners android:radius="10dp" /> </shape> </item> <!-- 白色前景 --> <item android:bottom="4dp" android:right="2dp"> <shape> <solid android:color="#FFFFFF" /> <corners android:radius="10dp" /> </shape> </item> </layer-list> 从上面的示例代码可以看到,layer-list可以作为根节点,也可以作为selector中item的子节点。layer-list可以添加多个item子节点,每个item子节点对应一个drawable资源,按照item从上到下的顺序叠加在一起,再通过设置每个item的偏移量就可以看到阴影等效果了。layer-list的item可以通过下面四个属性设置偏移量: android:top 顶部的偏移量 android:bottom 底部的偏移量 android:left 左边的偏移量 android:right 右边的偏移量 这四个偏移量和控件的margin设置差不多,都是外间距的效果。如何不设置偏移量,前面的图层就完全挡住了后面的图层,从而也看不到后面的图层效果了。比如上面的例子,Tab背景中的白色背景设置了android:bottom之后才能看到一点红色背景。那么如果偏移量设为负值会怎么样呢?经过验证,偏移超出的部分会被截掉而看不到,不信可以自己试一下。有时候这很有用,比如当我想显示一个半圆的时候。另外,关于item的用法,也做下总结:1. 根节点不同时,可设置的属性是会不同的,比如selector下,可以设置一些状态属性,而在layer-list下,可以设置偏移量;2. 就算父节点同样是selector,放在drawable目录和放在color目录下可用的属性也会不同,比如drawable目录下可用的属性为android:drawable,在color目录下可用的属性为android:color;3. item的子节点可以为任何类型的drawable类标签,除了上面例子中的shape、color、layer-list,也可以是selector,还有其他没讲过的bitmap、clip、scale、inset、transition、rotate、animated-rotate、lever-list等等。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>layer-list</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-XMLDrawable]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-XMLDrawable%2F</url>
<content type="text"><![CDATA[Drawable:可直接使用.png、.jpg、.gif、9.png等图片作为资源,也可使用多种XML文件作为资源。就是这些资源都能生成Drawable对象,并对XML文件作出相关处理。 Android青铜篇-shape Android青铜篇-layer-list Android青铜篇-selector Android青铜篇-level-list StateListDrawable作用:StateListDrawable对象所显示的Drawable对象会随着目标组件状态的改变而改变 组成:1根元素<selector/>,子元素<item/> 子元素的属性:android:color或android:drawableandroid:state_xxx:状态 示例123456<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="false" android:drawable="@mipmap/start"/> <item android:state_pressed="true" android:drawable="@mipmap/start_down"/></selector> LayerDrawable作用:可包含一个Drawable数组,系统会按照Drawable对象的数组顺序绘制,索引越大越被绘制在上层 组成:1根元素:<layer-list>,子元素:<item/> 子元素的属性:android:drawable 作为LayerDrawable的Drawable对象android:id 为Drawable对象指定标识符android:buttom|top…等 指定Drawable的绘制位置 示例一 1234567<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!--调用android自带的id修改,利用@android:id 修改父style的background--> <item android:drawable="@mipmap/p_1" android:id="@android:id/background"/> <item android:drawable="@mipmap/p_2" android:id="@android:id/progress"/></layer-list> 示例二 1234567891011<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!--可以在item里面创建各种各样的XMLDrawable--> <item> <bitmap android:src="@mipmap/p_3" android:gravity="center"/> </item> <item> <bitmap android:src="@mipmap/p_4" android:gravity="center"/> </item></layer-list> ShapeDrawable作用:设置一个基本的几何图形(矩形、圆形、线条灯) 根元素:<shape/>根元素的属性:android:shape=[“rectangle”|”oval”|”line”|”ring”] 子元素:123456<corners/>:设置整体或者四个边角的弧度<gradient/>:渐变(可选择渐变的角度但必须是45的倍数,默认为0,渐变的中心点,渐变的类型,渐变的半径和开始和终止的颜色)<padding/>:内边距 (可以控制四周的边距)<size/>:形状的大小 (设置形状的宽高)<solid/>:单种颜色填充 <stroke/>:绘制边框 (可设置画笔的颜色和粗细 并 设置每画一条线的长度和间距且必须两者都设置才有效) 示例1234567891011<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="3dp"/> <padding android:left="7dp" android:right="7dp" android:bottom="7dp" android:top="7dp"/> <gradient android:angle="45" android:startColor="#0000" android:endColor="#ffff"/></shape> ClipDrawable作用:从Drawable上截取一个“图片片段” 根元素:<clip>,不使用子元素。根元素属性: android:drawable: 选定Drawable对象android:clipOrientation:指定截取方向android:gravity:从什么地方开始截取 总结:选定图片并选择方向与位置截取图片 使用:从java中获取ClipDrawable并用setLevel()改变截取大小 //setLevel()只能从0~10000 示例 Drawbale 123456<!--res/drawable/test_clip.xml--><clip xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@mipmap/start" android:clipOrientation="horizontal" android:gravity="center"></clip> Layout 123456789101112131415<!--res/layout/activity_main.xml--><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.chen.android.test.MainActivity"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/test_clip"/></LinearLayout> Activity 12345678910111213141516171819202122232425262728293031323334353637/*实现图片渐渐展开的效果*/public class MainActivity extends AppCompatActivity { int data = 0; int what = 0X11; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView img = (ImageView)findViewById(R.id.imageView); //ImageView.getDrawable()获取的是当前控件里的图片,返回的是Drawable类型,还有说明Drawable对象可随意变成子对象并调用子对象的方法 final ClipDrawable clipDrawable = (ClipDrawable)img.getDrawable(); //创建Handler等待计时器传送的信息,使图片扩展 final Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == what){ clipDrawable.setLevel(data);//扩大截取的图片面积 data += 200; } } }; //创建计时器 final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { if (data >= 10000){ timer.cancel(); } mHandler.obtainMessage(what).sendToTarget(); } },0,300); }} AnimationDrawable简介:放在res/anim下,支持逐帧动画和补间动画 根元素:<set>根元素属性:android:interpolator=”参数”参数:@android:anim/ 为开头 选择 linear_interpolator:匀速变换 | accelerate_interpolar:加速变换 | decelerate_interpolator:减速变换android:shareInterpolator= “true|false” :是否让资源的interpolator与根元素相同android:duration=”时间”:定义持续时间 子元素(同样可以设置duration):1234<alpha>:设置开始和结束的透明度<scale>:设置缩放的中心、开始的X,Y的尺寸和结束时X,Y的尺寸<translate>:设置图片的开始位置和结束位置进行位移<rotate>:设置旋转的中心、开始的角度和结束时候的角度 注意:利用android:fillAfter=”true|false”:设置保留后的状态(哪个状态想保留就用这个,如果都像就放在中) 使用:利用AnimationUtils的静态方法loadAninmation(Context context,int resId) 示例 Drawbale 123456789101112131415161718192021222324<!--在res/anim/test_animtaion中--><set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator" android:shareInterpolator="true" android:fillAfter="true"> <alpha android:fromAlpha="50.0" android:toAlpha="100.0" /> <scale android:pivotX="50%" android:pivotY="50%" android:fromXScale="1.0" android:fromYScale="1.0" android:toXScale="1.5" android:toYScale="1.5" android:duration="3000" /> <translate android:fromXDelta="30" android:toXDelta="300" android:fromYDelta="40" android:toYDelta="90" android:duration="3000"/></set> Activity 123456789101112131415/*实现动画*/public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView img = (ImageView)findViewById(R.id.imageView); /*利用工具类获取对象*/ Animation animation= AnimationUtils.loadAnimation(this,R.anim.test_animation); /*将动画附加在图片上*/ img.startAnimation(animation); }} document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>Drawable</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Artifactory搭建本地JFrog的Library仓库]]></title>
<url>%2FArtifactory%E6%90%AD%E5%BB%BA%E6%9C%AC%E5%9C%B0JFrog%E7%9A%84Library%E4%BB%93%E5%BA%93%2F</url>
<content type="text"><![CDATA[JFrog的Artifactory是一款Maven仓库服务端软件,可以用来在内网搭建maven仓库,供团队、公司内部公共库的上传和发布以及使用,以提高公共代码使用的便利性。 个人Library仓库使用:传送门 搭建环境 JFrog提供免费并开源的Artifactory环境包,下载Artifactory,目前下载的是5.3.0版本,官网:https://www.jfrog.com 系统环境:Windows、Linux 安装Java环境,并且JDK版本1.8+ 启动服务两种启动方式:官网启动方式讲解 Manual Installation 解压下载文件,进入artifactory-oss-5.3.0\bin\目录,Window环境双击artifactory.bat文件;Linux环境执行artifactory.sh文件 执行文件后,等待Artifactory开启,运行成功(注意不能关闭启动服务的控制台,否则服务也会被终止): Service Installation后台启动,不需要在服务运行期间打开控制台,即可以关闭服务的控制台。 安装服务 Windows上执行installService.bat脚本 Linux上执行./installService.sh,需要root权限 启动服务 Windows上控制台执行命令sc start Artifactory Linux上执行命令service artifactory start 相关命令 12345678910Windows: sc stop Artifactory // 停止服务 sc query Artifactory // 检查服务状态,检测服务是否运行或检测运行状态 uninstallService.bat // 卸载服务Linux: service artifactory check // 检查服务状态,检测服务是否运行或检测运行状态 service artifactory stop // 停止服务 tail -f $ARTIFACTORY_HOME/logs/artifactory.log // 查看Artifactory日志 ./uninstallService.sh // 卸载服务 使用该方式启动服务后,账号密码已默认为admin和password;接下来的步骤与Manual Installation方式基本一致,只是不用设置账号密码。 服务初始化 Manual Installation的启动方式不要关闭控制窗口,关闭后本地仓库也会关闭;Service Installation可以关闭控制窗口。打开本地仓库:http://localhost:8081/artifactory ,首次进入需要设置管理员密码,设置完成后,可跳过其他的设置,可以在后面再创建仓库。 输入账号:admin,密码:password,登录 创建Library仓库,如果选择gradle,会生成下面4个Repository: 发布Library到本地仓库 打开AndroidStudio,新建Library类型的Module 配置项目build.gradle 123456789101112131415161718buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.3' // 添加 classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}allprojects { repositories { jcenter() }} 配置LibraryModule的build.gradle 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134apply plugin: 'com.android.library'/*** apply plugin ***/// 添加apply plugin: 'com.jfrog.artifactory'// 添加apply plugin: 'maven-publish'android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' testCompile 'junit:junit:4.12'}// 生成源码任务task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources'}// 生成Javadoc任务task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator))}task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir}javadoc { options{ encoding "UTF-8" charSet 'UTF-8' author true version true links "http://docs.oracle.com/javase/7/docs/api" }}/*** 上传源码 ***/artifacts { archives javadocJar archives sourcesJar}/*** 添加 ***/// 本地仓库地址def MAVEN_LOCAL_PATH = 'http://localhost:8081/artifactory'// 项目名称[可修改]def ARTIFACT_ID = 'jfrog'// 发布的版本[可修改]def VERSION_NAME = '1.0.0'// jcenter上的路径[可修改]def GROUP_ID = 'com.excellence'/*** setting pom ***/publishing { publications { aar(MavenPublication) { groupId GROUP_ID version = VERSION_NAME artifactId ARTIFACT_ID // Tell maven to prepare the generated "*.aar" and source file for publishing artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") artifact javadocJar artifact sourcesJar pom.withXml { def dependencies = asNode().appendNode('dependencies') configurations.compile.allDependencies.each { // 如果有compile fileTree(),group会为空,需要去除 if (it.group != null) { def dependency = dependencies.appendNode('dependency') dependency.appendNode('groupId', it.group) dependency.appendNode('artifactId', it.name) dependency.appendNode('version', it.version) } } } } }}/*** setting artifactorypublish ***/artifactory { contextUrl = MAVEN_LOCAL_PATH publish { repository { /** * repoKey:指定发布到的仓库名称[可修改] */ // The Artifactory repository key to publish to repoKey = 'gradle-release-local' // 发布者用户名[可修改] username = "admin" // 发布者密码[可修改] password = "password" } defaults { // 这里的'aar'对应publishing任务中的'aar',任务名称可自定义 // Tell the Artifactory Plugin which artifacts should be published to Artifactory. publications('aar') publishArtifacts = true // Properties to be attached to the published artifacts. properties = ['qa.level': 'basic', 'dev.team': 'core'] // Publish generated POM files to Artifactory (true by default) publishPom = true } }} 这里仅仅是简单的配置使用,当然你也可以查些高级的使用,比如在 gradle.properties中进行配置的安全性做法,这里就不深入了。 123artifactory_user=${security.getCurrentUsername()}artifactory_password=${security.getEncryptedPassword()!"insert password"}artifactory_contextUrl=http://localhost:8081/artifactory 说明 在这里上传的事 release.aar 包,故执行命令的时候需要执行release打包 artifactory/publish/repository/repokey 是你要上传的respository名称,当然可以新建,这里我们建立的是gradle仓库,上传的仓库是gradle-release-local 三种方式上传 操作上传点击打开Gradle projects,进入Module的Tasks中,顺序执行下面步骤 123build目录->双击assembleRelease : 打release 包publishing目录->双击generatePomFileForAarPublication : 生成 pom.xml 文件publishing目录->双击artifactoryPublish :上传 命令上传从Terminal控制台进入到Module中,执行命令 1gradle assembleRelease generatePomFileForAarPublication artifactoryPublish 保密命令上传,即同命令上传一致,但是不泄露用户和密码修改依赖项目中的build.gradle文件,使用参数替代username和password,此时编译不通过,因为参数是通过命令传入的,如然后执行命令 1gradle -Puser=用户名 -Ppwd=密码 assembleRelease generatePomFileForAarPublication artifactoryPublish 版本更新修改LibraryModule中的VERSION_NAME,然后重新上传即可 项目引用Library 配置项目build.gradle 1234567allprojects { repositories { jcenter() // 添加仓库地址 maven { url "http://localhost:8081/artifactory/gradle-release-local/" } }} 配置工程Module的build.gradle 1compile 'com.excellence:jfrog:1.0.0' 基本组成: 1group_id:artifact_Id:version_name group_id : com.excellence artifact_id : jfrog version_name: 1.0.0 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>攻城狮</category>
<category>好好学习</category>
</categories>
<tags>
<tag>gradle</tag>
<tag>JFrog</tag>
<tag>Artifactory</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Android青铜篇-shape]]></title>
<url>%2FAndroid%E9%9D%92%E9%93%9C%E7%AF%87-shape%2F</url>
<content type="text"><![CDATA[很多时候,使用shape能够实现的效果,你用一张图片也能够实现,但问题是一张图片无论你怎么压缩,它都不可能比一个xml文件小,因此,为了获得一个高性能的手机App,我们在开发中应该遵循这样一个原则:能够用shape实现的效果尽量不使用图片来实现。 基本属性corners、solid、gradient、padding、stroke、size Corners1234567<corners // 定义圆角 android:radius="dimension" // 全部的圆角半径 android:topLeftRadius="dimension" // 左上角的圆角半径 android:topRightRadius="dimension" // 右上角的圆角半径 android:bottomLeftRadius="dimension" // 左下角的圆角半径 android:bottomRightRadius="dimension" // 右下角的圆角半径/> Corners标签是用来字义圆角的,其中radius与其它四个并不能共同使用。android:radius:定义四个角的的圆角半径。其它四个是逐个字义每个角的圆角半径。 solidsolid用以指定内部填充色只有一个属性:1<solid android:color="color" /> gradientgradient用以定义渐变色,可以定义两色渐变和三色渐变,及渐变样式,它的属性有下面几个:1234567891011<gradient android:type=["linear" | "radial" | "sweep"] // 共有3中渐变类型,线性渐变(默认)/放射渐变/扫描式渐变 android:angle="integer" // 渐变角度,必须为45的倍数,0为从左到右,90为从上到下,仅对线性渐变有效 android:centerX="float" // 渐变中心X的相当位置,范围为0~1 android:centerY="float" // 渐变中心Y的相当位置,范围为0~1 android:startColor="color" // 渐变开始点的颜色 android:centerColor="color" // 渐变中间点的颜色,在开始与结束点之间 android:endColor="color" // 渐变结束点的颜色 android:gradientRadius="float" // 渐变的半径,只有当渐变类型为radial时才能使用 android:useLevel=["true" | "false"] // 用于指定是否将该shape当成一个LevelListDrawable来使用,使用LevelListDrawable时就要设置为true。设为false时才有渐变效果 /> 使用LevelListDrawable时就要设置为true。设为false时才有渐变效果有三种渐变类型,分别是:linear(线性渐变)、radial(放射性渐变)、sweep(扫描式渐变) padding用来定义内部边距123456<padding android:left="dimension" android:top="dimension" android:right="dimension" android:bottom="dimension" /> stroke这是描边属性,可以定义描边的宽度,颜色,虚实线等1234567<stroke android:width="dimension" // 描边的宽度 android:color="color" // 描边的颜色 // 以下两个属性设置虚线 android:dashWidth="dimension" // 虚线的宽度,值为0时是实线 android:dashGap="dimension" // 虚线的间隔/> size用来定义图形的大小的1234<size android:width="dimension" android:height="dimension" /> Shape的属性rectangle、oval、line、ring上面我们讲了Shape的子标签的的作用,但Shape本身还没讲,Shape自已是可以定义当前Shape的形状的,比如矩形,还有椭圆形,线形和环形;这些都是通过Shape标签的 shape属性来定义的,Shape标签总共有下面几个属性:12345678910<shape // shape的形状,默认为矩形,可以设置为矩形(rectangle)、椭圆形(oval)、线性形状(line)、环形(ring) android:shape=["rectangle" | "oval" | "line" | "ring"] // 下面的属性只有在android:shape="ring时可用: android:innerRadius // 尺寸,内环的半径。 android:innerRadiusRatio // 浮点型,以环的宽度比率来表示内环的半径, android:thickness // 尺寸,环的厚度 android:thicknessRatio // 浮点型,以环的宽度比率来表示环的厚度,例如,如果android:thicknessRatio="2", android:useLevel // boolean值,如果当做是LevelListDrawable使用时值为true,否则为false. /> document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>Android联盟</category>
</categories>
<tags>
<tag>Android</tag>
<tag>shape</tag>
</tags>
</entry>
<entry>
<title><![CDATA[AndroidStudio发布Library到jCenter]]></title>
<url>%2FAndroidStudio%E5%8F%91%E5%B8%83Library%E5%88%B0jCenter%2F</url>
<content type="text"><![CDATA[在开发过程中,常常引用优秀的第三方依赖库,我们也可以发布自己的依赖库到jCenter上,公开分享依赖库,供大家方便集成使用,不用再麻烦导入jar包或者Library工程。 个人主页 使用AndroidStudio或者IDEA上,只需要添加几行代码到build.gradle文件就可以使用这个library了,如123dependencies { compile 'com.excellence:basetools:1.2.2'} 依赖库服务器Android Studio是从build.gradle里面定义的Maven 仓库服务器上下载library的。Apache Maven是Apache开发的一个工具,提供了用于贡献library的文件服务器。总的来说,只有两个标准的Android library文件服务器:jCenter 和 Maven Central(这里只介绍jCenter)。 jCenterjCenter是一个由 bintray.com维护的Maven仓库我们在项目的build.gradle 文件中如下定义仓库,就能使用jCenter了:12345allprojects { repositories { jcenter() }} 发布library申请Bintray账号 注册账号 各位大兄弟需要一个bintray账号,进入Bintray主页注册账号,注意注册个人账号:https://bintray.com/signup/oss ,注册企业账号:https://bintray.com/signup ,我们需要注册个人账号,如果使用企业账号会出现上传的依赖库到期等问题。 注意注册邮箱问题:不能使用QQ、163等邮箱,如果使用GitHub快速登录,也要注意GitHub邮箱不能是QQ、163等邮箱。 登录 创建AndroidLibrary仓库 Add New Repository新建仓库,注意仓库的命名:maven,因为上传依赖时,会默认上传到maven仓库 记录API KEY 右上角菜单->Edit Profile->API key,输入登录密码,出现API KEY,复制API KEY,用于上传依赖,注意API KEY保密 引入bintray-release 创建Module 启动AndroidStudio,然后新建一个Android Library的Module 项目的build.gradle引入 在你的项目的build.gradle添加bintray-release的classpath,注意是项目的build.gradle,不是module的,如: 1234dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.novoda:bintray-release:0.3.4' // 添加} 配置待上传的Module的build.gradle,详细设置可查看官方WiKi 打开刚刚新建的Module里的build.gradle文件,添加配置,如: 12345678910111213apply plugin: 'com.android.library'apply plugin: 'com.novoda.bintray-release' // 添加// 添加publish { userOrg = 'veizhang' // bintray.com注册的用户名[必填] repoName = 'maven' // 远程仓库名称,可不填,默认名称:maven,可改为其他已创建的仓库名 groupId = 'com.excellence' // jCenter上的路径[必填] artifactId = 'basetools' // 项目名称[必填] publishVersion = '1.2.2' // 版本号[必填] desc = 'Android通用适配器和常用的工具类' // 依赖描述[可写可不写] website = 'https://github.com/VeiZhang/BaseToolsLibrary' // Git远程仓库链接[必填,否则出现400的错误,不会自动创建依赖包]} 至此,准备工作已完成,准备上传 上传 打开AndroidStudio下方的Terminal命令窗口,cd module进入待上传的Module目录中,然后输入命令,执行完毕。 1gradle clean build bintrayUpload -PbintrayUser=veizhang -PbintrayKey=xxxxxxxxxxxxxxxxxxxxxxxxxxx -PdryRun=false 注意: 需要替换PbintrayKey为账号里的API KEY; PdryRun=false,执行命令,没有问题则会上传,如果改成PdryRun=true会运行所有的环节,但不会上传; PbintrayUser对应用户名; 上传成功后,在你的maven仓库里出现Module 审核 此时还不能引用,最后一步,只需要进入上传的依赖库中,点击Add to Jcenter,给管理员审核依赖,提交的时候要做一个简短的英文描述,尽量不要用中文,审核一般需要等待几个小时,如果想快速通过审核,Module中什么都不写,新建完Module后直接上传审核,这种方式亲测可快速通过审核,审核成功后可以引用依赖,如图 1compile 'com.excellence:basetools:1.2.2' 依赖更新 在开发过程中,不断拓展集成新功能到Module中,那我们要同时更新jCenter上的依赖,只需要把版本号提高,发布即可123publish { publishVersion = '1.2.2' // 更新版本} 问题集合 Could not create package ‘dorisgm/maven/commonlibrary2’: HTTP/1.1 400 Bad Request [message:Please enter a valid VCS URL for your package.] 400 表示Bintray自动创建依赖库失败,原因是需要在gradle文件里添加website描述,这样才会自动创建依赖库;否则在Bintray网站上手动创建依赖库,然后上传也可以 Error:Execution failed for task ‘:xxxxx:bintrayUpload’.> Could not create package> ‘xxxxx/maven/xxxxxxxxx’: HTTP/1.1 401 Unauthorized 401 这个错误很明显是认证错误,可能是你的用户名或者API key错了 Could not create package ‘xxxxx’: HTTP/1.1 404 Not Found [message:Repo ‘maven’ was not found] 先Create Repository(名字默认是maven,必须要先创建仓库,或者把repoName改成其他已创建的仓库名),然后add new package如果没有出现Add to jCenter或者在bintray里面没有搜索到上传的依赖,则可能是Repository设置私有,改成public就可以了 Javadoc异常 1.Execution failed for task ‘:core:mavenAndroidJavadocs’.Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting): ‘/Users/zhou/git/app/core/build/tmp/mavenAndroidJavadocs/javadoc.options’ 2.androidstudio mavenAndroidJavadocs FAILED GBK编码不可映射 JavaDoc注解出现异常,一般是有中文注解导致,改成英文; 或者修改项目的build.gradle配置和修改Module的build.gradle配置 1234567891011121314allprojects { repositories { jcenter() } /**避免中文注释:编码GBK的不可映射字符**/ tasks.withType(Javadoc) { options{ encoding "UTF-8" charSet 'UTF-8' links "http://docs.oracle.com/javase/7/docs/api" } }} 12345/**避免Javadocs错误:找不到引用**/tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8')} Execution failed for task ‘:core:lint’. Lint found errors in the project; aborting build. 在上传过程中执行了lint检查,所以可能会报上面的错误。解决方法就是避免lint的检查,在Module的build.gradle下面就要添加如下配置: 12345android { lintOptions { abortOnError false }} 下载的依赖看不了源码 Module会被打包成arr文件,其中包含资源文件,大家在引用时可以查看源码。上传到Bintray时,如果混淆了(即开启了minifyEnabled true),可能会导致引用的Library看不到源码,去掉Module里混淆文件proguard-rules.pro的两行代码,即:12-renamesourcefileattribute SourceFile #保证异常时显示行号-keepattributes SourceFile,LineNumberTable document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>攻城狮</category>
<category>好好学习</category>
</categories>
<tags>
<tag>Android</tag>
<tag>AndroidStudio</tag>
<tag>gradle</tag>
<tag>jCenter</tag>
<tag>Bintray</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MarkDown]]></title>
<url>%2FMarkDown%2F</url>
<content type="text">< alt和title即对应HTML中的alt和title属性(都可省略): alt表示图片显示失败时的替换文本 URL表示图片的url地址 title表示鼠标悬停在图片时的显示文本(注意这里要加引号) # 语法 效果 1  2 ![][tiimor] 注意例2的写法使用了URL标识符的形式,在链接一节有介绍。 在文末有tiimor的定义: 1[tiimor]:http://cdn.tiimor.cn/images/%E6%8F%90%E7%99%BE%E4%B8%87.jpg 链接文字超链接 # 语法 效果 1 [我的博客](http://tiimor.cn "悬停显示") 我的博客 2 [我的微博][weibo] 我的微博 语法2由两部分组成: 第一部分使用两个中括号,[ ]里的标识符,左边是文字:图片显示失败时的替换文本;右边是URL标识符:替代链接地址 第二部分标记实际URL。 使用替代URL的方式能达到复用的目的,一般把全文所有的URL变量统一放在文章末尾,这样看起来比较干净。 图片链接给图片加链接的本质是混合图片显示语法和普通的链接语法。普通的链接中[ ]内部是链接要显示的文本,而图片链接[ ]里面则是要显示的图片。直接混合两种语法当然可以,但是十分啰嗦,为此我们可以使用URL标识符的形式。 # 语法 效果 1 [![tiimor]](http://cdn.tiimor.cn/images/%E6%8F%90%E7%99%BE%E4%B8%87.jpg) 2 [![tiimor]][MarkDown] 因为图片本身和链接本身都支持URL标识符的形式,所以图片链接也可以很简洁,左边是显示的图片链接地址,右边是跳转链接地址。注意,此时鼠标悬停时显示的文字是图片的title,而非链接本身的title了。 本文URL变量都放置于文末 锚点其实呢,每一个标题都是一个锚点,和HTML的锚点(#)类似,比如我们 语法 效果 [回到顶部](#top) 回到顶部 不过要注意,标题中的英文字母都要转化为小写字母,否则不能跳转,或者使用Html标签, 如:123目录(#目录)# 目录<a name="目录"> 列表无序列表 昵称:张益达 别名:张大炮 英文名:Snake 多级无序列表 编程语言 开发语言 Java 有序列表一般效果就是在数字后面加一个点,再加一个空格。不过看起来起来可能不够明显。面向对象的三个基本特征: 封装 继承 多态 有序列表自动排序也可以在第一行指定1.,而接下来的几行用星号*(或者继续用数字1. )就可以了,它会自动显示成2、3、4……。面向对象的七大原则: 开闭原则 里氏转换原则 依赖倒转原则 接口隔离原则 组合/聚合复用原则 “迪米特”法则 单一职责原则 多级有序列表和无序列表一样,有序列表也有多级结构: 这是一级的有序列表,数字1还是1 这是二级的有序列表,阿拉伯数字在显示的时候变成了罗马数字 这是三级的有序列表,数字在显示的时候变成了英文字母 四级的有序列表显示效果,就不再变化了,依旧是英文字母 复选框列表 需求分析 系统设计 详细设计 编码 测试 交付 您可以使用这个功能来标注某个项目各项任务的完成情况。 块引用常用于引用文本 文本摘自《深入理解计算机系统》P27 令人吃惊的是,在哪种字节顺序是合适的这个问题上,人们表现得非常情绪化。实际上术语“little endian”(小端)和“big endian”(大端)出自Jonathan Swift的《格利佛游记》一书,其中交战的两个派别无法就应该从哪一端打开一个半熟的鸡蛋达成一致。因此,争论沦为关于社会政治的争论。只要选择了一种规则并且始终如一的坚持,其实对于哪种字节排序的选择都是任意的。 “端”(endian)的起源以下是Jonathan Swift在1726年关于大小端之争历史的描述:“……下面我要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今的皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破较小的一端,违令者重罚。” 块引用有多级结构 数据结构 树 二叉树 平衡二叉树 满二叉树 代码高亮在三个反引号后面加上编程语言的名字,另起一行开始写代码,最后一行再加上三个反引号。1public static void main(String[]args){} //Java 1int main(int argc, char *argv[]) //C 1echo "hello GitHub" #Bash 1document.getElementById("myH1").innerHTML="Welcome to my Homepage"; //javascipt 1string &operator+(const string& A,const string& B) //cpp 表格 表头1 表头2 表格单元 表格单元 表格单元 表格单元 表头1 表头2 表格单元 表格单元 表格单元 表格单元 对齐表格可以指定对齐方式 左对齐 居中 右对齐 col 3 is some wordy text $1600 col 2 is centered $12 zebra stripes are neat $1 混合其他语法表格单元中的内容可以和其他大多数GFM语法配合使用,如: 使用普通文本的删除线,斜体等效果 名字 描述 Help Display the help window. Close Closes a window 表格中嵌入图片(链接) 其实前面介绍图片显示、图片链接的时候为了清晰就是放在在表格中显示的。 图片 描述 提莫 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>README</tag>
<tag>MarkDown</tag>
</tags>
</entry>
<entry>
<title><![CDATA[那些年AndroidStudio上的开发插件神器]]></title>
<url>%2F%E9%82%A3%E4%BA%9B%E5%B9%B4AndroidStudio%E4%B8%8A%E7%9A%84%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6%E7%A5%9E%E5%99%A8%2F</url>
<content type="text"><![CDATA[AndroidSutdio常用插件,使用方便,提高开发效率。 插件安装:File->Settings->Plugins 安装方式:1.在线安装 2.离线安装 GsonFormat快速将json字符串转换成一个Java Bean,免去我们根据json字符串手写对应Java Bean的过程。使用方法:快捷键Alt+S也可以使用Alt+Insert选择GsonFormat Android Code Generator根据布局文件快速生成对应的Activity,Fragment,Adapter,Menu。 Eclipse Code Formatter用于格式化代码的插件,可以将以前Eclipse的格式化脚本兼容到AndroidStudio中。传送门 Android ButterKnife Zelezny配合ButterKnife实现注解,从此不用写findViewById。在Activity,Fragment,Adapter中选中布局xml的资源id自动生成butterknife注解。使用方法:Ctrl+Shift+B或者Alt+Insert选择Generate ButterKnife Injections如图上所示使用 RemoveButterKnife用于移除代码中对ButterKnife使用的AS插件,传送门 Android Parcelable code generatorJavaBean序列化,快速实现Parcelable接口。 LeakCanary帮助你在开发阶段方便的检测出内存泄露的问题,使用起来更简单方便。传送门 Material Theme UI添加Material主题到你的AndroidStudio,美观界面 TranslationPlugin翻译插件,支持中英互译、单词朗读。传送门 Genymotion速度较快的android模拟器,通过插件快速打开模拟器 Markdown Navigator可以直接在AndroidStudio中编辑MD文档,并且实时查看。传送门 Android Methods Count显示依赖库中得方法数 Lifecycle Sorter可以根据Activity或者fragment的生命周期对其生命周期方法位置进行先后排序,快捷键Ctrl + alt + K CodeGlance在右边可以预览代码,实现快速定位 findBugs-IDEA查找bug的插件,Android Studio也提供了代码审查的功能(Analyze-Inspect Code…) ADB WIFI使用wifi无线调试你的app,无需root权限 AndroidWiFiADB无线调试应用 AndroidPixelDimenGeneratorAndroid Studio自动生成dimen.xml文件插件 JsonOnlineViewer在Android Studio中请求、调试接口 Android Drawable Importer这是一个非常强大的图片导入插件。它导入Android图标与Material图标的Drawable ,批量导入Drawable ,多源导入Drawable(即导入某张图片各种dpi对应的图片)传送门 SelectorChapek for Android通过资源文件命名自动生成Selector文件。 Android Styler根据xml自动生成style代码的插件 GradleDependenciesHelperPluginmaven gradle 依赖支持自动补全,传送门 Android Postfix Completion可根据后缀快速完成代码,系统已经有这些功能,如sout、notnull等,这个插件在原有的基础上增添了一些新的功能 Android Holo Colors Generator通过自定义Holo主题颜色生成对应的Drawable和布局文件 dagger-intellij-plugindagger可视化辅助工具 AndroidProguardPlugin一键生成项目混淆代码插件,值得你安装~(不过目前可能有些第三方项目的混淆还未添加完全) otto-intellij-pluginotto事件导航工具。 eventbus-intellij-plugineventbus导航插件,传送门 Sexy Editor设置AS代码编辑区的背景图。重启完成之后,进入设置界面,选择other Setting下的Sexy Editor ,右侧insert一张或多张图片即可,上面的其他设置可以设置方位、间隔时间、透明度等等,设置完成后,要关闭打开的文件,重新打开项目文件即可在代码编辑区显示插入的图片,作为代码编辑区的背景图。 folding-plugin布局文件分组的插件 Android-DPI-CalculatorDPI计算插件 gradle-retrolambda在java 6 7中使用lambda表达式插件,修改编译的jdk为java8,然后可以使用lambda表达式。 Android Studio Prettify可以将代码中的字符串写在string.xml文件中,选中字符串鼠标右键选择图中所示这个插件还可以自动书写findViewById .ignore在Git中想要过滤掉一些不想提交的文件,可以把相应的文件添加到.gitignore中 CheckStyle-IDEACheckStyle-IDEA 是一个检查代码风格的插件,比如像命名约定,Javadoc,类设计等方面进行代码规范和风格的检查,你们可以遵从像Google Oracle 的Java 代码指南 ,当然也可以按照自己的规则来设置配置文件,从而有效约束你自己更好地遵循代码编写规范。传送门 PermissionsDispatcher plugin自动生成6.0权限的代码,传送门 WakaTime记录你在IDE上的工作时间,传送门 AndroidLocalizationer可用于将项目中的 string 资源自动翻译为其他语言的 Android Studio/IntelliJ IDEA 插件 SingletonTest快速生成单例模式的预设 jimu Mirror能够实时预览Android布局,它会监听布局文件的改动,如果有代码变化,就会立即刷新UI。传送门 jRebel For Android不仅能够做到UI布局的实时预览,它甚至做到了让你更改java代码后就能实时替换apk中的类文件,达到应用实时刷新,官网的介绍是:Skip build, install and run,因此它可以节约我们很多很多的时间,它的效果也十分不错。传送门 Codota搜索最好的Android代码。传送门 LayoutFormatter一键格式化你的 XML 文件的 Android Studio 插件。 android-strings-search-plugin一个可以通过输入文字找到strings.xml资源的插件。 ideaVimvim结合AndroidStudio。 ExynapExynap 一个帮助开发者自动生成样板代码的 AndroidStudio 插件 eventbus3-intellij-plugin引导 EventBus 的 post 和 event(对于最新版的 EventBus 3.0.0 有效)主要Bug修复工作:修改包名和方法名以适应 EventBus 3.X替换一个在新版的 intellij plugin SDK 已经不存在的类增加若干 try-catch ,防止插件崩溃 gradle-cleaner-intellij-pluginForce clear delaying & no longer needed Gradle tasks. MVPHelper一款Intellj IDEA 和Android Studio的插件,可以为MVP生成接口以及实现类,解放双手。 Matchmaker这是一款专为微信小程序开发的插件,目前可在 IntelliJ IDEA 中使用。它可以帮你完成重复机械无趣麻烦的绑定方法的过程,自动的将需要新建的方法注入到 js 文件中去。 Emoji Support Plugin让 Intellij 支持 Emoji 输入提醒 Open-Uploader上传apk文件到指定的地址,提供自定义参数 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>攻城狮</category>
</categories>
<tags>
<tag>AndroidStudio</tag>
</tags>
</entry>
<entry>
<title><![CDATA[专治AndroidStudio中Gradle疑难杂症]]></title>
<url>%2F%E4%B8%93%E6%B2%BBAndroidStudio%E4%B8%ADGradle%E7%9A%84%E7%96%91%E9%9A%BE%E6%9D%82%E7%97%87%2F</url>
<content type="text"><![CDATA[Gradle是一个基于JVM的构建工具,是一款通用灵活的构建工具,支持maven, Ivy仓库,支持传递性依赖管理,而不需要远程仓库或者是pom.xml和ivy.xml配置文件,基于Groovy,build脚本使用Groovy编写。AndroidStudio在使用过程中常见的Gradle问题 方法数超过64K 工程中,包括引用的library的方法数超过65536(64K)时出现 删除不用的方法,删除不使用的jar;compile fileTree(dir: 'libs', include: ['*.jar']),会编译libs下所有的jar,使用compile files()依次添加需要的依赖 分包,配置build.gradle文件,通过在defaultConfig中设置multiDexEnabled可以开启分包模式,分包之后的Dex就低于了限制数,保证了正常的打包12345android { defaultConfig { multiDexEnabled true }} 包冲突 1.Android Studio遇到transformClassesWithDexForDebug错误2.Android Studio com.android.dex.DexException: Multiple dex files define(重复引用包) 去掉相同的依赖包 将compile files()替换为provided files() 使用如下方式,防止v4等包冲突123compile(xxx) { exclude group: "com.android.support", module: "support-v4"} 重复依赖 引用依赖时,出现文件重复:如引用RxJavaAdapter和RxJava 去掉其中一个文件,去掉rxjava:2.0.8 12compile 'com.squareup.retrofit2:adapter-rxjava:2.2.0'compile 'io.reactivex.rxjava2:rxjava:2.0.8' 更换为兼容的依赖库版本修改rxjava2为rxjava,如compile 'io.reactivex:rxjava:1.2.2' 依赖so异常1java.lang.UnsatisfiedLinkError: Couldn't load ktvDb_jni from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.test-2.apk"],nativeLibraryDirectories=[/data/app-lib/com.example.test-2, /system/lib]]]: findLibrary returned null 在build.gradle文件里添加依赖的.so库1234567android { sourceSets { main { jniLibs.srcDirs = ['libs'] } }} 中文乱码 Android设备上运行显示中文乱码 123android { compileOptions.encoding = "GBK"} 编码 UTF-8 的不可映射字符gradle2.0+是JavaCompile,否则是Compile 123tasks.withType(JavaCompile) { options.encoding = "UTF-8"} 123tasks.withType(Compile) { options.encoding = "UTF-8"} 控制台Logcat打印乱码File->Settings->Editor->File Encodings->修改项目编码为GBK 找不到HttpClient方法 Android Studio 使用ApachHttp传输下载时,找不到HttpClient 在build.gradle里面添加1useLibrary 'org.apache.http.legacy' Gradle编译禁用Lint报错 在build.gradle里添加123456android { lintOptions { checkReleaseBuilds false abortOnError false }} 运行内存溢出 AndroidStudio运行APK时,出现java.lang.OutOfMemoryError:GC overhead limit exceeded 解决方法: 12345android { dexOptions { javaMaxHeapSize "4g" }} document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>攻城狮</category>
</categories>
<tags>
<tag>Android</tag>
<tag>AndroidStudio</tag>
<tag>gradle</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Windows上GitHub+Hexo搭建个人博客]]></title>
<url>%2FWindows%E4%B8%8AGitHub%2BHexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%2F</url>
<content type="text"><![CDATA[工具安装安装的工具:Git,Nodejs,Hexo,可选安装:TortoiseGit、GitHub Desktop,建议下载使用VPN代理,已安装可略过 Git下载并安装Git,或者参考这个链接 Nodejs下载Nodejs,按照既定的套路安装即可 HexoHexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 安装安装Git、Nodejs后,在任意目录右键Git Bash Here,然后执行以下命令安装Hexo 1$ npm install -g hexo-cli Hexo常用命令 12345$ hexo init [foler] # 初始化$ hexo g # 生成静态网页,在当前目录下生成public文件夹$ hexo clean # 清除缓存 调试时网页正常情况下可以忽略此条命令$ hexo s # 启动本地web服务,可以在http://localhost:4000/ 查看,用于博客预览$ hexo d # 部署博客到远端:GitHub等平台 命令简写 1234$ hexo n == hexo new # 新建文章或页面$ hexo g == hexo generate$ hexo s == hexo server$ hexo d == hexo deploy GitHub工程创建GitHub Pages免费的静态站点,其特点:免费托管、自带主题、支持自制页面等。 登录GitHub网站,没有账号则创建,已有账号则登录 Start a project创建仓库;每个帐号只能有一个仓库来存放个人主页,而且仓库名字必须是username.github.io,这是特殊的命名约定你可以通过http://username.github.io 来访问你的个人主页,此时未部署网页,访问的是404网页 SSH密钥,在网站部署到yourname.github.io仓库时需要权限 SSH检测电脑是否有权限访问GitHub使用以下命令,没有设置SSH密钥是拒绝访问,图为访问成功 1$ ssh -T [email protected] 生成SSH密钥,任意目录下,右键打开Git Bash Here,使用以下命令生成密钥,需要输入:①密钥文件生成目录,默认即可;②SSH访问时需要的密码,Enter可跳过;③再次确认密码,Enter可跳过 1$ ssh-keygen -t rsa -C "GitHub账户注册的邮箱" 打开生成的id_rsa.pub文件,复制文件内容,进入博客仓库Settings的Deploy keys,Add deploy key并且勾选Allow write access;或者进入https://github.com/settings/ssh ,New SSH key。填写完Title,将复制的id_rsa.pub文件内容写到Key中 再次测试SSH是否有访问权限,注意:没有权限网页最终是不能部署到GitHub Pages上的 博客博客工程 任意目录下打开Git Bash Here,执行以下命令初始化,创建Blog工程目录,如:username.github.io,称为站点目录 1$ hexo init username.github.io 12345678910├── .deploy # 需要部署的文件├── node_modules # Hexo插件├── public # 生成的静态网页文件├── scaffolds # 模板├── source # 博客正文和其他源文件, 404 favicon CNAME 等都应该放在这里| ├── _drafts # 草稿| └── _posts # 文章├── themes # 主题├── _config.yml # 全局配置文件└── package.json 启动本地web服务,预览网页 123// 进入站点目录$ cd username.github.io$ hexo s 打开浏览器,输入localhost:4000,打开博客主页,显示的内容是初始化时生成的博客,位置:source\_post\hello-world.md,可删除 第一篇博客,使用以下命令生成博客,博客文件在source\_post目录下文件HelloWorld.md,然后预览第一篇博客 1$ hexo new HelloWorld 博客模板 [可略过] 博客模板在站点目录\scaffolds\里,自行添加、修改、删除,命令说明 12// [layout]博客模板$ hexo new [layout] <title> 默认创建博客,生成在source\_posts目录下 1$ hexo new <title> 创建草稿,生成在source\_drafts目录下 1$ hexo new --draft <title> 创建资源目录tags,生成目录在source目录下 1$ hexo new page tags 创建标签 [可略过],执行命令,生成文件source\tags\index.md,修改type为tags,即设置页面类型为标签 12$ cd your-hexo-site$ hexo new page tags 创建分类 [可略过],同理标签,执行命令,在生成的模板文件里,设置type为categories 1$ hexo new page categories 更多操作,请查看Hexo使用文档 主题为了美观博客网页,我们可以使用漂亮的主题。在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于站点根目录下,主要包含 Hexo 本身的配置,称为站点配置文件;另一份位于主题目录下,这份配置由主题作者提供,主要用于配置主题相关的选项,称为主题配置文件,默认的主题是themes\landscape Clone一份NexT主题到themes目录下,主题目录为next 12$ cd themes$ git clone https://github.com/iissnan/hexo-theme-next.git next 修改网站主题,站点配置文件有很多设置属性 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071# Hexo Configuration## Docs: https://hexo.io/docs/configuration.html## Source: https://github.com/hexojs/hexo/# Site # 站点信息title: # 标题subtitle: # 副标题description: # 站点描述author: # 作者language: # 语言timezone: # 时区# URL # 链接格式## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'url: http://yoursite.com # 用于RSS订阅,填写自己的主页地址root: /permalink: :year/:month/:day/:title/ # 修改链接格式,可以去掉日期,改成:title/,则链接http://2017/5/20/Hello变成http://Hellopermalink_defaults: # Directory # 目录source_dir: source # 源文件public_dir: public # 生成的网页文件tag_dir: tags # 标签archive_dir: archives # 归档category_dir: categories # 分类code_dir: downloads/codei18n_dir: :lang # 国际化skip_render:# Writing # 写作new_post_name: :title.md # File name of new posts # 新文章标题default_layout: post # 默认模板(post page photo draft)titlecase: false # Transform title into titlecase # 标题转换成大写external_link: true # Open external links in new tab # 新标签页里打开连接filename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: falsefuture: truehighlight: # 语法高亮 enable: true line_number: true # 显示行号 auto_detect: false tab_replace:# Category & Tag # 分类和标签default_category: uncategorized # 默认分类category_map:tag_map:# Date / Time format # 日期时间格式## Hexo uses Moment.js to parse and display date## You can customize the date format as defined in## http://momentjs.com/docs/#/displaying/format/date_format: YYYY-MM-DDtime_format: HH:mm:ss# Pagination # 分页## Set per_page to 0 to disable paginationper_page: 10 # 每页文章数, 设置成 0 禁用分页pagination_dir: page# Extensions # 插件和主题## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: landscape # 主题切换,根据/themes/目录下的文件名更换,文件名即为主题名# Deployment # 部署, 同时发布在 GitHub## Docs: https://hexo.io/docs/deployment.htmldeploy: type: 我们只需要修改其中的部分,主题切换theme属性值landscape换成next,属性值需要与主题目录一样,否则网页打不开,启动命令预览 1234567891011121314151617# Sitetitle: 班德尔城subtitle: 微笑,简单,奋发description: 安静地做一只约德尔人。author: TiiMorlanguage: zh-Hanstimezone: Asia/Shanghai···# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: next # 主题切换,切换为next主题··· 注意:每一项的填写,其:后面都要保留一个空格,配置文件属性描述 主题推荐,这里有大量的主题列表供选择,主题预览,我选择的是比较流行的NexT主题,其他主题同理NexT切换即可,NexT使用文档有详细的主题配置文件说明 RSS订阅 [可略过]:站点目录下,Git Bash Here执行下面命令,然后修改站点配置文件里面的url: http://yoursite.com为自己主页地址,即可完成RSS订阅 1$ npm install hexo-generator-feed --save 部署在部署之前,我们可以通过 hexo clean && hexo s 命令进行本地预览,执行完后,打开网页:http://localhost:4000/ 进行调试,确认没问题后,部署。 我们已经生成了静态网页,但是只能自己查看,想给其他人欣赏,最重要的一步是部署到GitHub上 打开站点配置文件,在最底部,修改添加如下信息。username:GitHub用户名和仓库的名字branch:master分支 1234deploy: type: git repo: [email protected]:username/username.github.io.git branch: master 然后执行命令发布,如果遇到输入密码,输入在设置SSH密钥时的passphrase密码即可: 12// 清除缓存,生成静态网页,部署$ hexo clean && hexo g && hexo d 发布成功后,浏览器打开http://username.github.io ,其他人就可以给你的博客点赞! 遇到的坑: 出现ERROR Deployer not found: git,则执行命令npm install hexo-deployer-git --save,重新部署 长时间未能部署成功,可能超时,建议使用代理连接 未识别地址里的账户,站点配置文件里上面的deploy信息不对 没有权限,需要生成SSH密钥添加到GitHub Deploy Keys 图床 写博客过程中,很多地方需要用到图片,图片虽然占用空间并不大,可是积少成多,GitHub Pages自带空间只有300M,我们可以将图片存放到七牛云上,通过外链访问图片。七牛云储存提供10G的免费空间,以及每月10G的流量;还有各种图形处理功能、缩略图等功能。 注册七牛账号,过程比较简单 进入资源主页->添加资源->添加对象存储 进入存储空间->内容管理->上传图片 选择文件,上传成功->关闭,返回上一级->复制外链,即可引用图片 补充 不同电脑写blog我们可以将网页源码存放在GitHub上,新建仓库保存,或者使用username.github.io创建分支保存;如果不想公布博客源文件可以使用GitHub的private仓库(付费)或者其他方式如 码云 上的private仓库(免费)、coding等备份;同理,码云、coding上也可以搭建个人博客,步骤类似。在新电脑上时,对于网站源码在GitHub上,clone工程到本地,安装相应的工具Git、Nodejs、Hexo,生成SSH密钥,允许访问权限,执行命令初始化环境之后,就可以继续写博客。 12345// 不需要hexo init$ npm install -g hexo-cli # 安装hexo$ npm install # 安装依赖包$ npm install hexo-deployer-git # 用git部署插件$ npm install hexo-generator-feed --save # RSS插件 绑定域名在阿里云的万网上购买域名,根据需求选择域名,我在阿里云上购买的是.cn域名;购买成功后,准备添加域名解析,使用cmd命令ping username.github.io,记录结果显示的ip地址,在万网上添加解析,进入新手引导设置,设置网站解析,输入购买的域名和刚刚记录的ip,会自动添加两个A类地址,或者手动添加两个A类地址;此时在浏览器输入域名会出现404,因为还未绑定GitHub Pages,进入username.github.io工程,添加文件CNAME,填写购买的域名保存,在浏览器中输入域名即可访问个人博客网站。注意:每次发布网站会清除CNAME文件,可以将CNAME文件放置于站点目录\source\目录下发布,则不会清除CNAME文件 Enjoy! document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>GitHub</tag>
<tag>Hexo</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HelloWorld]]></title>
<url>%2FHelloWorld%2F</url>
<content type="text"><![CDATA[做一只安静的约德尔人。 document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return; } const img = document.createElement('img'); img.style = 'display:none !important;'; img.src = el.dataset.src; img.addEventListener('error', () => { img.remove(); el.style.color = 'inherit'; el.style.backgroundImage = 'none'; el.style.background = 'none'; }); img.addEventListener('load', () => { img.remove(); }); document.body.appendChild(img); });]]></content>
<categories>
<category>好好学习</category>
</categories>
<tags>
<tag>HelloWorld</tag>
</tags>
</entry>
</search>