DataBinding踩坑指南

0x01 ViewBinding

1.使用 View Binding 先要在Module 的 build.gradle 文件注册

1
2
3
4
5
6
android {
...
buildFeatures {
viewBinding true
}
}

2.会根据布局文件,编译之后自动生成对应的Binding class,可以在Activity 和 Fragment 直接调用 inflate 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

0x02 DataBinding

1.在 build.gradle 文件中开启lib

1
2
3
4
5
6
android {
...
buildFeatures {
dataBinding true
}
}

2.布局文件start with a root tag of layout followed by a data element

1
2
3
4
5
6
7
8
9
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

3.在Activity中使用

1
2
3
4
5
6
7
8
9
10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
// or
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)

binding.user = User("Test", "User")
}

Fragment, ListView, or RecyclerView adapter, you may prefer to use the inflate()

1
2
3
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

0x03 view binding 和 data binding 比较

View binding and data binding both generate binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding:

  • Faster compilation: View binding requires no annotation processing, so compile times are faster.
  • Ease of use: View binding does not require specially-tagged XML layout files, so it is faster to adopt in your apps. Once you enable view binding in a module, it applies to all of that module’s layouts automatically.

Conversely, view binding has the following limitations compared to data binding:

Because of these considerations, it is best in some cases to use both view binding and data binding in a project. You can use data binding in layouts that require advanced features and use view binding in layouts that do not.

0x04 遇到的坑

  • 等标签,如果使用databinding ,子布局xml的 root tag 依旧需要layout 标签嵌套 data 标签。否者编译报错,找不到对应的属性

自定义Notification遇到的坑

自定义Notification 实现:

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
// RemoteViews for notification
private var rv: RemoteViews? = null
private var rvExpanded: RemoteViews? = null
private fun customNotification(process: Int) {
// custom RemoteViews
if (rv == null) rv = RemoteViews(packageName, R.layout.notification_small)
rv?.setTextViewText(R.id.notification_title, "这是一个小标题")
if (rvExpanded == null) rvExpanded = RemoteViews(packageName, R.layout.notification_large)
rvExpanded?.setTextViewText(R.id.large_notification_title, "这是一个大标题,支持很多的内容: $process%")

// PendingIntent
val intentNotification = Intent(this, PlayMusicActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendIntent = PendingIntent.getActivity(
this,
0,
intentNotification,
PendingIntent.FLAG_UPDATE_CURRENT
)

// build notification
val customNotification =
NotificationCompat.Builder(applicationContext, CHANNEL_ID)
.setSmallIcon(R.drawable.bravo) // small Icon
.setStyle(NotificationCompat.DecoratedCustomViewStyle()) // 自定义contentView
.setCustomContentView(rv!!)
.setContent(rvExpanded!!)
.setCustomBigContentView(rvExpanded!!)
.setCustomHeadsUpContentView(rvExpanded!!)
.setOngoing(true) // 一直显示
.setAutoCancel(false) // 点击后消失
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // 锁屏显示,需要配合权限设置
.setPriority(NotificationCompat.PRIORITY_HIGH) // Priority
.setOnlyAlertOnce(true) // 声音,震动,仅弹出一次
.setContentIntent(pendIntent)
.build()
startForeground(NOTIFICATION_ID, customNotification)
}

1.使用 NotificationCompat 兼容各个版本差异性

2.RemoteViews 布局文件不支持 constraintlayout ,切记

3.在SDK 26之后必须要绑定Channel,所以通知要先创建Channel

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
fun checkAndCreateChannel(
context: Context,
channelId: String,
channelName: String,
desc: String = channelName
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
try {
// 查找分组
val nc = notificationManager.getNotificationChannel(channelId)
Log.d("ChannelHelper", "${nc.id} Notification Channel exist.")
} catch (e: Exception) {
Log.d("ChannelHelper", "empty channel need create.")
// 创建分组
val mChannel =
NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = desc
enableLights(true)
enableVibration(true)
}
notificationManager.createNotificationChannel(mChannel)
}
}
}

Android-JsBridge实现本地H5混合开发

Android JsBridge 混合开发框架

0x01 Java 调用 Js

我们知道,native层调用h5,在WebView中,如果java要调用js的方法,

0x0101 loadUrl <4.4

Android4.4以前使用WebView.loadUrl("javascript:function()")

0x0102 evaluateJavascript >4.4

Android4.4以后,使用以下方式

1
2
3
4
5
6
webView.evaluateJavascript("javascript:function()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(MainActivity.this, "onReceiveValue From JS: " + value, Toast.LENGTH_SHORT).show();
}
});

0x02 Js 调用 Java

h5层如何调native,有以下几种形式

0x0201 addJavascriptInterface >4.2

在4.2之前有安全隐患,JS可以动态获取到整个底层的实例信息,漏洞已经在Android 4.2上修复了,即使用@JavascriptInterface注解。

0x0202 shouldOverrideUrlLoading拦截自定义scheme

0x0203 onJsAlert,onJsConfirm,onJsPrompt

WebChromeClient对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的alert,confirm,prompt,WebChromeClient对象中的三个方法对应的就会被触发,进行拦截处理。

推荐使用onJsPrompt,使用频次最少,支持返回值。

1
2
3
4
5
6
7
8
9
10
11
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
val msg = handleMessage(message)
result?.confirm("Java 处理之后的 Json 数据:$msg")
return true
}

0x0204 onConsoleMessage

这是Android提供给Js调试在Native代码里面打印日志信息的API,同时这也成了其中一种Js与Native代码通信的方法。在Js代码中调用console.log(‘xxx’)方法。

0x03 自定义通信协议

jsbridge://className:callbackId/methodName?json

假设我们需要调用native层的Logger类的log方法,参数是msg,执行完成后js层要有一个回调,那么地址就如下

jsbridge://Logger:callbackId/log?{"msg":"message from js."}

RecyclerView实现瀑布流以及细节问题

RecyclerView实现瀑布流以及细节问题

1)使用 StaggeredGridLayoutManager

1
2
3
4
5
6
7
8
9
10
val layoutManager = StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE// 禁止左右交换
recyclerView.layoutManager = layoutManager
// decoration
recyclerView.addItemDecoration(StaggeredDividerItemDecoration(16, true))
// adapter
mAdapter = PjListVPAdapter()
recyclerView.adapter = mAdapter
// animator
recyclerView.itemAnimator = DefaultItemAnimator()

2)如果需要Item之间的间隔,就需要自定义 ItemDecoration

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
/**
* 瀑布流ItemDecoration
* 必须配合RecyclerView的StaggeredGridLayoutManager一起使用
*
* @author Dench
* @data 2020-09-04
*/
class StaggeredDividerItemDecoration(
space: Int, // 间隔 pix
private val includeEdge: Boolean = false // 是否显示边距
) : ItemDecoration() {
private val space = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
space.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val lp = view.layoutParams as StaggeredGridLayoutManager.LayoutParams
val lm = parent.layoutManager as StaggeredGridLayoutManager
val spanIndex: Int = lp.spanIndex
val spanCount: Int = lm.spanCount
if (includeEdge) {
outRect.left = space * (spanCount - spanIndex) / spanCount
outRect.right = space * (spanIndex + 1) / spanCount
outRect.top = space
} else {
outRect.left = space * spanIndex / spanCount
outRect.right = space * (spanCount - spanIndex - 1) / spanCount
outRect.bottom = space
}
}
}
阅读更多

ARouter源码解析

ARouter源码解析

0x01 init ()

ARouter 的入口,初始化SDK ARouter.init(mApplication);

1
2
3
4
5
6
7
8
9
10
11
public static void init(Application application) {
if (!hasInit) { // 避免重复初始化
logger = _ARouter.logger; // 日志初始化
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application); // 正式初始化
if (hasInit) { // 初始化之后
_ARouter.afterInit(); // 管理拦截器的服务 InterceptorService 初始化
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}

_ARouter.init()

阅读更多

implementation compileOnly和api

implementation和api

implementationapi是取代之前的compile的,其中apicompile是一样的效果,implementation有所不同,通过implementation依赖的库只能自己库本身访问,举个例子,A依赖B,B依赖C,如果B依赖C是使用的implementation依赖,那么在A中是访问不到C中的方法的,如果需要访问,请使用api依赖

compile only

compile onlyprovided效果是一样的,只在编译的时候有效, 不参与打包

runtime only

runtimeOnlyapk效果一样,只在打包的时候有效,编译不参与

跟编译环境相关

  • test implementation

testImplementationtestCompile效果一样,在单元测试和打包测试apk的时候有效

  • debug implementation

debugImplementationdebugCompile效果相同, 在debug模式下有效

  • release implementation

releaseImplementationreleaseCompile效果相同,只在release模式和打包release包情况下有效

StatusBarHelper

StatusBarHelper

StatusBar 工具类

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
object StatusBarHelper {
/**
* 内容显示在状态栏下面(LAYOUT_FULLSCREEN) > 6.0
* true:白底黑字,false:黑底白字
*/
fun fitSystemBar(activity: Activity, light: Boolean = true) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
val window = activity.window
val decorView = window.decorView
var visibility = decorView.systemUiVisibility
visibility = visibility or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
visibility = if (light) {
visibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
visibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
decorView.systemUiVisibility = visibility

window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = Color.TRANSPARENT
}

/**
* 调整状态栏文字、图标颜色 > 6.0
* true:白底黑字,false:黑底白字
*/
fun lightStatusBar(activity: Activity, light: Boolean = true) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
var window: Window = activity.window
var visibility = window.decorView.systemUiVisibility
visibility = if (light) {
visibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
visibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
window.decorView.systemUiVisibility = visibility
}


// 获取状态栏高度
fun getStatusBarHeight(activity: Activity): Int {
var result: Int = 0
var resId = activity.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resId > 0) result = activity.resources.getDimensionPixelOffset(resId)
return result
}
}
阅读更多

Gradle常用命令

Gradle 常用命令

0x01 快速构建命令

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
# 查看构建版本
./gradlew -v

# 清除build文件夹
./gradlew clean

# 检查依赖并编译打包
./gradlew build

# 编译并安装debug包
./gradlew installDebug

# 编译并打印日志
./gradlew build --info

# 译并输出性能报告,性能报告一般在 构建工程根目录 build/reports/profile
./gradlew build --profile

# 调试模式构建并打印堆栈日志
./gradlew build --info --debug --stacktrace

# 离线模式
./gradlew aDR --offline

# 守护进程
./gradlew build --daemon

# 并行编译模式
./gradlew build --parallel --parallel-threads=N

0x02 构建并安装命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 编译并打Debug包
./gradlew assembleDebug

# 这个是简写 assembleDebug
./gradlew aD

# 编译并打Release的包
./gradlew assembleRelease

# 这个是简写 assembleRelease
./gradlew aR

# Debug模式打包并安装
./gradlew install app:assembleDebug

# Release模式打包并安装
./gradlew installRelease

# 卸载Release模式包
./gradlew uninstallRelease

# Flavor渠道包
./gradlew install app:assemble<Flavor>Debug

0x03 查看包依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
./gradlew dependencies

# 查看app模块依赖
./gradlew app:dependencies

# 检索依赖库
./gradlew app:dependencies | grep CompileClasspath

# windows 没有 grep 命令
./gradlew app:dependencies | findstr "CompileClasspath"

# 将检索到的依赖分组找到 比如flavorDebugCompileClasspath就是flavor渠道分发的开发编译依赖
./gradlew app:dependencies --configuration <flavor>DebugCompileClasspath

# 依赖树过长可以保存到本地文件方便查看
./gradlew app:dependencies --configuration <flavor>DebugCompileClasspath >1.log

0x04 依赖包更新

1
2
3
4
5
# 依赖包更新命令
./gradlew build --refresh-dependencies

# 强制更新最新依赖,清除构建并构建
./gradlew clean build --refresh-dependencies

阿里云 Maven 仓库

0x01 阿里云Maven仓库

仓库地址:https://maven.aliyun.com/mvn/view

1
2
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }

0x02 Gradle配置指南

在 build.gradle 文件中加入以下代码:

阅读更多
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×