Problems专题:Parcel

Problems专题:Parcel

0x01 Unmarshalling unknown type

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
Thread Name: 'main' 
java.lang.RuntimeException: Parcel android.os.Parcel@bbfcc04: Unmarshalling unknown type code 2131296928 at offset 1088
at android.os.Parcel.readValue(Parcel.java:2750)
at android.os.Parcel.readSparseArrayInternal(Parcel.java:3126)
at android.os.Parcel.readSparseArray(Parcel.java:2354)
at android.os.Parcel.readValue(Parcel.java:2728)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3045)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)
at android.os.BaseBundle.unparcel(BaseBundle.java:232)
at android.os.Bundle.getSparseParcelableArray(Bundle.java:1010)
at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2133)
at android.app.Activity.onRestoreInstanceState(Activity.java:1173)
at android.app.Activity.performRestoreInstanceState(Activity.java:1128)
at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1318)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3025)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1840)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6878)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)

问题分析

情况1 Parcelable 对象为空,反序列化异常

情况2 Parcelable 序列化和反序列化的字段和顺序没有完全对应

情况3 自定义View的数据保存与恢复

解决方案

情况1 Parcelable 对象在序列化和反序列化增加 null 值判断

情况2 Parcelable 对象 read 和 write 字段的类型和顺序保持一直

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Account implements Parcelable {
public Account(Parcel in) {
this.name = in.readString();
this.type = in.readInt();
if (TextUtils.isEmpty(name)) {
throw new android.os.BadParcelableException("the name must not be empty: " + name);
}
// ...
this.accessId = in.readString();
// ...
}

public int describeContents() {
return 0;
}

public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(type);
dest.writeString(accessId);
}
// ...
}

情况3 自定义View的数据保存与恢复

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
override fun onSaveInstanceState(): Parcelable? {
Log.d("NavigationBar", "onSaveInstanceState: selectedId=${mSelectedId}")
return SavedState(super.onSaveInstanceState(), mSelectedId)
}

override fun onRestoreInstanceState(state: Parcelable?) {
if (state is SavedState) {
val id = state.selectedId
Log.d("NavigationBar", "onRestoreInstanceState: selectedId=${state.selectedId}")
super.onRestoreInstanceState(state.superState)
select(id)
return
}
return super.onRestoreInstanceState(state)
}

internal class SavedState : BaseSavedState {
var selectedId: Int = View.NO_ID

constructor(source: Parcel) : super(source) {
selectedId = source.readInt()
Log.d("NavigationBar", "readFromParcel: selectedId=$selectedId")
}

constructor(superState: Parcelable?, selectedId: Int) : super(superState) {
this.selectedId = selectedId
}

override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeInt(selectedId)
Log.d("NavigationBar", "writeToParcel: selectedId=$selectedId")
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<SavedState> {
override fun createFromParcel(parcel: Parcel): SavedState {
return SavedState(parcel)
}

override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}

0x02 android.os.TransactionTooLargeException

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
java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 542688 bytes
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:160)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6878)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
Caused by: android.os.TransactionTooLargeException: data parcel size 542688 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:479)
at android.app.IActivityManager$Stub$Proxy.activityStopped(IActivityManager.java:3941)
at java.lang.reflect.Method.invoke(Native Method)
at com.taobao.monitor.impl.common.c.invoke(ActivityManagerHook.java:89)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy2.activityStopped(Unknown Source)
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144)
... 7 more
android.os.TransactionTooLargeException: data parcel size 542688 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:479)
at android.app.IActivityManager$Stub$Proxy.activityStopped(IActivityManager.java:3941)
at java.lang.reflect.Method.invoke(Native Method)
at com.taobao.monitor.impl.common.c.invoke(ActivityManagerHook.java:89)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy2.activityStopped(Unknown Source)
at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6878)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)

问题分析

1 Intent 传递的数据过大。

2 onSaveInstance 保存的数据过大。

解决方案

尽可能的使用少量的数据。

大数据考虑持久化和其他传递形式。

链表相交

编写一个程序,找到两个单链表相交的起始节点。

例如,下面的两个链表:

1
2
3
4
5
A:          a1 → a2

c1 → c2 → c3

B: b1 → b2 → b3

在节点 c1 开始相交。

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

方法一:哈希集合

思路和算法

判断两个链表是否相交,可以使用哈希集合存储链表节点。

首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:

  • 如果当前节点不在哈希集合中,则继续遍历下一个节点;

  • 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。

如果链表 headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回 null。

复杂度分析

时间复杂度:O(m+n),其中 m 和 n 是分别是链表 headA 和 headB 的长度。需要遍历两个链表各一次。

空间复杂度:O(m),其中 m 是链表 headA 的长度。需要使用哈希集合存储链表 headA 中的全部节点。

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
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> visited = new HashSet<ListNode>();
ListNode temp = headA;
while (temp != null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null) {
if (visited.contains(temp)) {
return temp;
}
temp = temp.next;
}
return null;
}
}

方法二:双指针

思路和算法

使用双指针的方法,可以将空间复杂度降至 O(1)。

只有当链表 headA 和 headB 都不为空时,两个链表才可能相交。因此首先判断链表 headA 和 headB 是否为空,如果其中至少有一个链表为空,则两个链表一定不相交,返回 null。

当链表 headA 和 headB 都不为空时,创建两个指针 pA 和 pB,初始时分别指向两个链表的头节点 headA 和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:

  • 每步操作需要同时更新指针 pA 和 pB;

  • 如果指针 pA 不为空,则将指针 pA 移到下一个节点;如果指针 pB 不为空,则将指针 pB 移到下一个节点。

  • 如果指针 pA 为空,则将指针 pA 移到链表 headB 的头节点;如果指针 pB 为空,则将指针 pB 移到链表 headA 的头节点。

  • 当指针 pA 和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null。

证明

考虑两种情况,第一种情况是两个链表相交,第二种情况是两个链表不相交。

复杂度分析

时间复杂度:O(m+n),其中 m 和 n 是分别是链表 headA 和 headB 的长度。两个指针同时遍历两个链表,每个指针遍历两个链表各一次。

空间复杂度:O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}

参考连接:

https://leetcode-cn.com/problems/intersection-of-two-linked-lists/solution/xiang-jiao-lian-biao-by-leetcode-solutio-a8jn/

https://zhuanlan.zhihu.com/p/48313122

RecyclerView Item 嵌套 ScrollView

RecyclerView Item 嵌套 ScrollView

RecyclerView Item 嵌套 ScrollView 产生 Touch 事件冲突,通过自定义ScrollView来拦截和处理事件

image-20210908211849925

自定义 ItemScrollView 代码如下

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
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView

class ItemScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
return super.onInterceptTouchEvent(ev)
}

private var lastY: Float = 0f

override fun onTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
lastY = ev.y
}

MotionEvent.ACTION_MOVE -> {
val currentY = ev.y
this.scrollBy(0, (lastY - currentY).toInt())
lastY = currentY
}

MotionEvent.ACTION_UP -> {
lastY = 0f
}
}

return canScroll()
}

private fun canScroll(): Boolean {
val child = getChildAt(0)
child?.let {
return height < child.height
}
return false
}
}

PowerShell最佳实践

PowerShell最佳实践

Windows 10 系统自带 PowerShell,美化教程

image-20210909150114264

0x01 安装Fluent Terminal

在Windows 应用商店安装,或者Github

0x02 安装powershell模块

1、安装posh-git、oh-my-posh和Get-ChildItemColor(美化ls命令):

在powershell管理员模式下:

1
2
3
$ Install-Module posh-git -Scope CurrentUser
$ Install-Module oh-my-posh -Scope CurrentUser
$ Install-Module DirColors

2、设置修改powershell的配置文件:

1
2
$ if (!(Test-Path -Path $PROFILE )) { New-Item -Type File -Path $PROFILE -Force }
$ notepad $PROFILE

输入内容:

1
2
3
4
Import-Module DirColors
Import-Module posh-git
Import-Module oh-my-posh
Set-PoshPrompt -Theme PowerLine

其中主题名可以在下面的路径里找到,可以自行切换主题。

1
C:\Program Files\WindowsPowerShell\Modules\oh-my-posh\3.163.0\themes

0x03 安装Powerline字体

在Fluent Terminal设置 powerline 字体和字体大小。

0x04 文件管理器命令

在文件夹中打开:

1
2
3
4
5
6
7
8
# I 在文件夹中打开
$ explorer (gl)
# II 在文件夹中打开
$ start .
# III 在文件夹中打开
$ ii .
# 打开当前根目录
$ ii /

在当前文件夹打开PowerShell:

空白处 Shift + 鼠标右键,在弹出的菜单中选择PowerShell.

0x05 VS Code 命令

1
$ code .

Problems专题:Dialog

Problems专题:Dialog

0x01 OnKeyDown部分机型无法监听

问题分析

监听返回键和音量键,重载OnKeyDown()方法,部分机型会失效。

解决方案

给相应的Dialog监听setOnKeyListener()。

1
2
3
4
5
6
7
8
9
10
// 解决不同机型版本兼容问题,onKeyDown 可能被拦截
setOnKeyListener { dialog, keyCode, event ->
Log.d(TAG, "setOnKeyListener: keyCode = $keyCode, event = $event")
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
handleKeyEvent(keyCode)
true
} else {
false
}
}

注意区分keycode,防止业务层重复处理

0x02 DialogFragment不能自动弹出软键盘

方案一:延迟弹出软键盘
在dialog显示之后,延迟200ms再显示软键盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//强制显示或者关闭系统键盘
public static void toggleKeyboard(final EditText editText, final boolean status) {
if (editText == null) return;
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
InputMethodManager m = (InputMethodManager)
ApplicationExtKt.getAppContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (status) {
m.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
} else {
IBinder windowToken = editText.getWindowToken();
if (windowToken != null) {
m.hideSoftInputFromWindow(windowToken, 0);
}
}
}
}, status ? 200 : 100);
}

方案二:设置 SoftInputMode 为 SOFT_INPUT_STATE_ALWAYS_VISIBLE

1
2
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
inputEditText.requestFocus();

0x03 关闭DialogFragment无法关闭软键盘

问题分析

一般情况下,在onPause或者dismiss方法直接调用hideKeyboard就可以

1
2
3
4
override fun onPause() {
KeyBoardUtils.hideKeyboard(binding.etSearch)
super.onPause()
}

但是,在某些时候还是会存在关闭不成功的情况。这是由于Dialog下面的Activity或Fragment存在EditText等抢占焦点,导致在DialogFragment在调用dismiss方法时,键盘已经被抢占焦点,所以无法关闭。

解决方案

在DialogFragment的dismiss方法回调

1
2
3
4
override fun onDismiss(dialog: DialogInterface) {
listener?.onDialogDismiss()
super.onDismiss(dialog)
}

在前一个Activity或者Fragment中重新关闭键盘。

1
2
3
4
5
6
7
// 消除弹框遗留下来的keyboard
private fun onDialogDismiss() {
// 消除弹框
Handler().postDelayed({
KeyBoardUtils.hideKeyboard(binding.root)
}, 200)
}

RecyclerView的几种Decoration

RecyclerView的几种Decoration

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
import android.content.res.Resources
import android.graphics.Rect
import android.util.TypedValue
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class SimplePaddingDecoration(
spaceDp: Int,
val orientation: Int = RecyclerView.VERTICAL
) : RecyclerView.ItemDecoration() {
private val dividerHeight: Int = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
spaceDp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition

if (orientation == RecyclerView.VERTICAL) {
// 竖直
if (position + 1 != parent.adapter?.itemCount) {
outRect.set(0, 0, 0, dividerHeight)
} else {
outRect.set(0, 0, 0, 0)
}
} else {
// 水平
if (position + 1 != parent.adapter?.itemCount) {
outRect.set(0, 0, dividerHeight, 0)
} else {
outRect.set(0, 0, 0, 0)
}
}
}
}
Your browser is out-of-date!

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

×