Android 日常开发问题总结

访客模式 PC 端不显示盘符 快速点击打开两个重复的 Activity
判断当前是否为机主模式 判断应用是否已安装
Android P 蓝牙开关状态栏无图标显示 Menu 键导致屏幕亮屏
拨打紧急电话无通话记录 … …

1. 访客模式 PC 端不显示盘符


在之前 Android 系统开发的 Bug 库中,遇到一个访客模式下 PC 端不显示盘符的问题,如果你也有此问题,这边给您提供一种方案!

✒ 问题现象(这个问题主要是针对 MTK 平台)

Device 切换到访客模式下,连接电脑 USB ,打开传输模式,却发现在 PC 端无法显示内部存储的盘符。

✒ 问题原因

这个现象是不合理的,原生代码逻辑(或者说 Pixel)是不存在这个问题的,Bug 的原因是 MTK 合入了一个 patch 所导致。

导致问题的修改如下:MtpService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
synchronized (this) {
if (sServerHolder != null) {
Log.d(TAG, "MTP server is still running.");
} else {
mVolumeMap = new HashMap<>();
mStorageMap = new HashMap<>();
mStorageManager.registerListener(mStorageEventListener);
mVolumes = StorageManager.getVolumeList(user.getIdentifier(), 0);
for (StorageVolume volume : mVolumes) {
if (Environment.MEDIA_MOUNTED.equals(volume.getState())) {
// 跟踪 volumeMountedLocked() 函数
volumeMountedLocked(volume.getPath());
} else {
Log.e(TAG, "StorageVolume not mounted " + volume.getPath());
}
}
}
... ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void volumeMountedLocked(String path) {
// For update storage
/*
* 问题就出在这里!
* MTK 为了修改一个 Bug:偶现显示两个内部存储盘符,所以在这边重新
* 获取一遍 StroageVolume ,但是没有考虑多用户模式!
*
*/
StorageVolume[] volumes = mStorageManager.getVolumeList(new UserHandle();
mVolumes = volumes;
for (int i = 0; i < mVolumes.length; i++) {
StorageVolume volume = mVolumes[i];
if (volume.getPath().equals(path)) {
mVolumeMap.put(path, volume);
if (!mMtpDisabled) {
// In PTP mode we support only primary storage
if (volume.isPrimary() || !mPtpMode) {
addStorageLocked(volume);
}
}
break;
}
}
}

✒ 解决方案

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
private void volumeMountedLocked(String path) {
// For update storage, support multi-user mode
/*
* 博主发现原生设计对多用户读取盘符是这么操作的:
* mVolumes = StorageManager.getVolumeList(user.getIdentifier(), 0);
* 直接进行如下修改:添加多用户模式判断逻辑
*
*/

// 修改方案
StorageVolume[] volumes = mStorageManager.getVolumeList(
new UserHandle(ActivityManager.getCurrentUser()).getIdentifier(), 0);

mVolumes = volumes;
for (int i = 0; i < mVolumes.length; i++) {
StorageVolume volume = mVolumes[i];
if (volume.getPath().equals(path)) {
mVolumeMap.put(path, volume);
if (!mMtpDisabled) {
// In PTP mode we support only primary storage
if (volume.isPrimary() || !mPtpMode) {
addStorageLocked(volume);
}
}
break;
}
}
}

2. 快速点击打开两个重复的 Activity


✒ 问题现象

如果你经常遇到一种情况:快速点击按钮(比如 Settings 界面),弹出两个重复的目标 Activity !解决方法看这里!

✒ 问题原因

其实这是一个系统原生自带的 Bug,强迫症受不了,直接提供解决方案!

✒ 解决方案

1、找到你的控件所在类,然后添加一个变量,如下:

1
private long mStartTime = 0;   // 定义一个初始判断时间变量

2、然后在点击处理事件中,找到目标 Activity 点,添加如下代码:

1
2
3
4
5
6
7
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
long interval = System.currentTimeMillis() - mStartTime; // 两次点击时间间隔
if (interval >= 500) {
mStartTime = System.currentTimeMillis();
showConfirmDialog(infoItem, isChecked, null); // target Activity
}

就这么简单,不需要按照网上众说纷纭,无需设置 SingerTop、SingerTask ,更没必要单独设置个 onclickListen 监听,试试看,效果很好!


3. 判断当前是否为机主模式


我们在维护和修改 Android 系统或 Bug 的时候,有时候需要区分机主模式和访客模式,针对不同模式,需要添加和修改不同的代码逻辑!

1
2
3
4
5
6
7
import android.app.ActivityManager;
... ...

// 目标逻辑代码中添加,非机主模式
if (ActivityManager.getCurrentUser() != UserHandle.USER_OWNER) {
... ...
}

4. 判断应用是否已安装


在 Android 系统开发过程中,有时候我们需要判断应用(Package)是否已存在于 Device 中。判断方法很多,我们提供一种方案!

源码中加入如下代码即可判断某 Apk 是否安装:

1
2
3
4
5
6
7
8
9
10
11
12
public  boolean isPkgInstalled(Context context, String packageName) {
if (packageName == null || "".equals(packageName)) {
return false;
}
android.content.pm.ApplicationInfo info = null;
try {
info = context.getPackageManager().getApplicationInfo(packageName, 0);
return info != null;
} catch (NameNotFoundException e) {
return false;
}
}

5. Android P 蓝牙开关状态栏无图标显示


这是个原生设计,为了节约状态栏图标空间(刘海屏导致状态栏空间进一步缩小),只在蓝牙连接设备后才显示相应图标。

如果你的项目需求是需要显示蓝牙图标,按照如下方式改动:

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
// frameworks/base/.../PhoneStatusBarPolicy.java

private final void updateBluetooth() {
int iconId = R.drawable.stat_sys_data_bluetooth;
String contentDescription = mContext.getString(R.
string.accessibility_quick_settings_bluetooth_on);
boolean bluetoothVisible = false;
if (mBluetooth != null) {

// added by marco, begin
if (mBluetooth.isBluetoothEnabled()) {
iconId = R.drawable.stat_sys_data_bluetooth;
contentDescription = mContext.getString(R.
string.accessibility_quick_settings_bluetooth_on);
bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
// added by marco, end

if (mBluetooth.isBluetoothConnected()) {
iconId = R.drawable.stat_sys_data_bluetooth_connected;
contentDescription = mContext.getString(R.
string.accessibility_bluetooth_connected);
bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
}

mIconController.setIcon(mSlotBluetooth, iconId, contentDescription);
mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible);
}

6. Menu 键导致屏幕亮屏


项目中遇到的一个问题,测试反馈 power 键灭屏之后,快速点击 Menu 键,屏幕会被重新激活亮起。其实从源码的流程来看,事件处理机制没有问题,但测试个人觉得是个问题,本着测试自己觉得是问题你开发就必须改的“精神”,那就看看怎么改吧。

这边,我直接给你解决方案:

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
public class PhoneWindowManager implements WindowManagerPolicy {

... ...

private boolean isWakeKeyWhenScreenOff(int keyCode) {
switch (keyCode) {
// ignore volume keys unless docked
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
return mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED;

// ignore media and camera keys
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
case KeyEvent.KEYCODE_CAMERA:

// added by marco, begin
// ignore menu key
case KeyEvent.KEYCODE_MENU: // 屏幕 Menu 键激活屏幕亮度
// added by marco, end
return false;
}
return true;
}
... ...
}

7. 拨打紧急电话无通话记录


Android 原生代码中,默认不会将紧急电话作为通话记录保存,如以下源码:

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
// On some devices, to avoid accidental redialing of emergency numbers, we *never* log
// emergency calls to the Call Log. (This behavior is set on a per-product basis, based
// on carrier requirements.)
boolean okToLogEmergencyNumber = false;
CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
Context.CARRIER_CONFIG_SERVICE);
PersistableBundle configBundle = configManager.getConfigForSubId(
mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle));
if (configBundle != null) {
okToLogEmergencyNumber = configBundle.getBoolean(
CarrierConfigManager.KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL);
}

// Don't log emergency numbers if the device doesn't allow it.
final boolean isOkToLogThisCall = (!isEmergency || okToLogEmergencyNumber)
&& !isUnloggableNumber(number, configBundle);

sendAddCallBroadcast(callType, duration);

if (isOkToLogThisCall) {
Log.d(TAG, "Logging Call log entry: " + callerInfo + ", "
+ Log.pii(number) + "," + presentation + ", " + callType
+ ", " + start + ", " + duration);
boolean isRead = false;
if (isSelfManaged) {
// Mark self-managed calls are read since they're being handled by their own app.
// Their inclusion in the call log is informational only.
isRead = true;
}
AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
viaNumber, presentation, callType, features, accountHandle, start, duration,
dataUsage, initiatingUser, isRead, logCallCompletedListener);
logCallAsync(args);
} else {
Log.d(TAG, "Not adding emergency call to call log.");
}

通过注释我们就知道,okToLogEmergencyNumber 留个各个厂商自己决定是否将紧急电话保存到通话记录中,我们看下:

1
2
3
4
5
6
7
8
/**
* Determines if the current device should allow emergency numbers to be logged in the Call Log.
* (Some carriers require that emergency calls *not* be logged, presumably to avoid the risk of
* accidental redialing from the call log UI. This is a good idea, so the default here is
* false.)
*/
public static final String
KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";

allow_emergency_numbers_in_call_log_bool 默认为 false,所以我们只需要如下修改即可。

1
2
3
4
5
6
7
8
9
10
 static {
sDefaults = new PersistableBundle();
sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false);
sDefaults.putBoolean(KEY_AUTO_RETRY_FAILED_WIFI_EMERGENCY_CALL, false);
sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true);

// sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, true);