自定义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
}
}
阅读更多

Your browser is out-of-date!

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

×