Android6.0 wakelock深入分析
这篇博客我们分析下Power的持锁,从PowerManager到PowerManagerService再到hal分析。
一、PowerManager的持锁接口
我们先来看下PowerManager对应用提供的接口:
- public WakeLock newWakeLock(int levelAndFlags, String tag) {
- validateWakeLockParameters(levelAndFlags, tag);//验证wakelock的flag是否有效
- return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
- }
validateWakeLockParameters函数如下:主要对flag没有下面这些flag做过滤
- public static void validateWakeLockParameters(int levelAndFlags, String tag) {
- switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) {
- case PARTIAL_WAKE_LOCK://cpu锁
- case SCREEN_DIM_WAKE_LOCK://屏幕微亮,键盘暗
- case SCREEN_BRIGHT_WAKE_LOCK://屏幕亮,键盘暗
- case FULL_WAKE_LOCK://全亮
- case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- case DOZE_WAKE_LOCK:
- case DRAW_WAKE_LOCK:
- break;
- default:
- throw new IllegalArgumentException("Must specify a valid wake lock level.");
- }
- if (tag == null) {
- throw new IllegalArgumentException("The tag must not be null.");
- }
- }
我们再看WakeLock类,先看下面两个持锁,第二个timeout的持锁,先持锁,然后发送一个延迟消息再解锁。
- public void acquire() {
- synchronized (mToken) {
- acquireLocked();
- }
- }
- public void acquire(long timeout) {
- synchronized (mToken) {
- acquireLocked();
- mHandler.postDelayed(mReleaser, timeout);
- }
- }
- private final Runnable mReleaser = new Runnable() {
- public void run() {
- release();
- }
- };
再来看acquireLocked,流程最后是调用了service的acquireWakeLock,但是有一个细节我们注意下,这里有一个mCount的计数。也就是没调用一次这个函数,都会对wakelock的mCount加1。
- private void acquireLocked() {
- if (!mRefCounted || mCount++ == 0) {
- // Do this even if the wake lock is already thought to be held (mHeld == true)
- // because non-reference counted wake locks are not always properly released.
- // For example, the keyguard's wake lock might be forcibly released by the
- // power manager without the keyguard knowing. A subsequent call to acquire
- // should immediately acquire the wake lock once again despite never having
- // been explicitly released by the keyguard.
- mHandler.removeCallbacks(mReleaser);
- Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
- try {
- mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
- mHistoryTag);
- } catch (RemoteException e) {
- }
- mHeld = true;
- }
- }
我们再来看看release函数,上面说的mCount计数,这里就会对这个计数进行判断,只有当计数为0,才会去调用service的releaseWakeLock函数。
- public void release(int flags) {
- synchronized (mToken) {
- if (!mRefCounted || --mCount == 0) {
- mHandler.removeCallbacks(mReleaser);
- if (mHeld) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
- try {
- mService.releaseWakeLock(mToken, flags);
- } catch (RemoteException e) {
- }
- mHeld = false;
- }
- }
- if (mCount < 0) {
- throw new RuntimeException("WakeLock under-locked " + mTag);
- }
- }
- }
所以我们总结下,使用PowerManager的wakelock持锁和释放锁。必须成对出现,当持了2次锁,也必须释放两次锁。才会调用service的释放锁,去真正释放。
二、PowerManagerService相关接口
Service的acquireWakeLock函数我们就不看了,主要是对wakelock的flag做验证,然后调用了acquireWakeLockInternal函数:
- private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws, String historyTag, int uid, int pid) {
- synchronized (mLock) {
- WakeLock wakeLock;
- int index = findWakeLockIndexLocked(lock);//查找wakelock,IBinder对象为wakelock唯一性
- boolean notifyAcquire;
- if (index >= 0) {
- wakeLock = mWakeLocks.get(index);
- if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
- // Update existing wake lock. This shouldn't happen but is harmless.
- notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
- uid, pid, ws, historyTag);
- wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);//更新参数
- }
- notifyAcquire = false;
- } else {
- wakeLock = new WakeLock(lock, flags, tag, packageName, ws, historyTag, uid, pid);//新建
- try {
- lock.linkToDeath(wakeLock, 0);
- } catch (RemoteException ex) {
- throw new IllegalArgumentException("Wake lock is already dead.");
- }
- mWakeLocks.add(wakeLock);
- setWakeLockDisabledStateLocked(wakeLock);
- notifyAcquire = true;
- }
- applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);//看是否需要唤醒设备
- mDirty |= DIRTY_WAKE_LOCKS;
- updatePowerStateLocked();// 更新电源状态
- if (notifyAcquire) {
- notifyWakeLockAcquiredLocked(wakeLock);
- }
- }
- }
updatePowerStateLocked函数我们放在后面说,我们再来看看applyWakeLockFlagsOnAcquireLocked函数:
- private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) {
- if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0//wakelock中的flag有这个flag就需要唤醒设备
- && isScreenLock(wakeLock)) {
- String opPackageName;
- int opUid;
- if (wakeLock.mWorkSource != null && wakeLock.mWorkSource.getName(0) != null) {
- opPackageName = wakeLock.mWorkSource.getName(0);
- opUid = wakeLock.mWorkSource.get(0);
- } else {
- opPackageName = wakeLock.mPackageName;
- opUid = wakeLock.mWorkSource != null ? wakeLock.mWorkSource.get(0)
- : wakeLock.mOwnerUid;
- }
- wakeUpNoUpdateLocked(SystemClock.uptimeMillis(), wakeLock.mTag, opUid,
- opPackageName, opUid);
- }
- }
同样release,我们也直接分析releaseWakeLockInternal函数:
- private void releaseWakeLockInternal(IBinder lock, int flags) {
- synchronized (mLock) {
- int index = findWakeLockIndexLocked(lock);//找wakelock
- if (index < 0) {
- if (DEBUG_LC) {
- Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
- + " [not found], flags=0x" + Integer.toHexString(flags));
- }
- return;
- }
- WakeLock wakeLock = mWakeLocks.get(index);
- if (DEBUG_LC) {
- Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
- + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags));
- }
- if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {//距离传感器相关
- mRequestWaitForNegativeProximity = true;
- }
- wakeLock.mLock.unlinkToDeath(wakeLock, 0);
- removeWakeLockLocked(wakeLock, index);
- }
- }
removeWakeLockLocked函数,去除wakelock,最后调用updatePowerStateLocked
- private void removeWakeLockLocked(WakeLock wakeLock, int index) {
- mWakeLocks.remove(index);//去除wakelock
- notifyWakeLockReleasedLocked(wakeLock);
- applyWakeLockFlagsOnReleaseLocked(wakeLock);//是否触发userActivity
- mDirty |= DIRTY_WAKE_LOCKS;
- updatePowerStateLocked();
- }
applyWakeLockFlagsOnReleaseLocked函数:
- private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
- if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0//有这个flag触发userActivity
- && isScreenLock(wakeLock)) {
- userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_OTHER,
- PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
- wakeLock.mOwnerUid);
- }
- }
下面我们就来看看之前acquire和realease都调用的函数updatePowerStateLocked:
- private void updatePowerStateLocked() {
- if (!mSystemReady || mDirty == 0) {
- return;
- }
- if (!Thread.holdsLock(mLock)) {
- Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
- }
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
- try {
- // Phase 0: Basic state updates.
- updateIsPoweredLocked(mDirty);
- updateStayOnLocked(mDirty);
- updateScreenBrightnessBoostLocked(mDirty);
- // Phase 1: Update wakefulness.
- // Loop because the wake lock and user activity computations are influenced
- // by changes in wakefulness.
- final long now = SystemClock.uptimeMillis();
- int dirtyPhase2 = 0;
- for (;;) {
- int dirtyPhase1 = mDirty;
- dirtyPhase2 |= dirtyPhase1;
- mDirty = 0;
- updateWakeLockSummaryLocked(dirtyPhase1);
- updateUserActivitySummaryLocked(now, dirtyPhase1);
- if (!updateWakefulnessLocked(dirtyPhase1)) {
- break;
- }
- }
- // Phase 2: Update display power state.
- boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
- // Phase 3: Update dream state (depends on display ready signal).
- updateDreamLocked(dirtyPhase2, displayBecameReady);
- // Phase 4: Send notifications, if needed.
- finishWakefulnessChangeIfNeededLocked();
- // Phase 5: Update suspend blocker.
- // Because we might release the last suspend blocker here, we need to make sure
- // we finished everything else first!
- updateSuspendBlockerLocked();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
- }
- }
其实这个函数我们先看下updateWakeLockSummaryLocked函数,根据wakelock来指定mWakeLockSummary
- private void updateWakeLockSummaryLocked(int dirty) {
- if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) {
- mWakeLockSummary = 0;
- final int numWakeLocks = mWakeLocks.size();
- for (int i = 0; i < numWakeLocks; i++) {
- final WakeLock wakeLock = mWakeLocks.get(i);
- switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
- case PowerManager.PARTIAL_WAKE_LOCK:
- if (!wakeLock.mDisabled) {
- // We only respect this if the wake lock is not disabled.
- mWakeLockSummary |= WAKE_LOCK_CPU;//持cpu锁
- }
- break;
- case PowerManager.FULL_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
- break;
- case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT;
- break;
- case PowerManager.SCREEN_DIM_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_SCREEN_DIM;
- break;
- case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF;
- break;
- case PowerManager.DOZE_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_DOZE;
- break;
- case PowerManager.DRAW_WAKE_LOCK:
- mWakeLockSummary |= WAKE_LOCK_DRAW;
- break;
- }
- }
- // Cancel wake locks that make no sense based on the current state.
- if (mWakefulness != WAKEFULNESS_DOZING) {
- mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
- }
- if (mWakefulness == WAKEFULNESS_ASLEEP
- || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
- mWakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
- | WAKE_LOCK_BUTTON_BRIGHT);
- if (mWakefulness == WAKEFULNESS_ASLEEP) {
- mWakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
- }
- }
- // Infer implied wake locks where necessary based on the current state.
- if ((mWakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
- if (mWakefulness == WAKEFULNESS_AWAKE) {
- mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
- } else if (mWakefulness == WAKEFULNESS_DREAMING) {
- mWakeLockSummary |= WAKE_LOCK_CPU;
- }
- }
- if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
- mWakeLockSummary |= WAKE_LOCK_CPU;
- }
- if (DEBUG_SPEW) {
- Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
- + PowerManagerInternal.wakefulnessToString(mWakefulness)
- + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
- }
- }
- }
最后我们主要看下updateSuspendBlockerLocked这个函数:
- private void updateSuspendBlockerLocked() {
- final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);//是否需要持锁
- final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
- final boolean autoSuspend = !needDisplaySuspendBlocker;
- final boolean interactive = mDisplayPowerRequest.isBrightOrDim();
- // Disable auto-suspend if needed.
- // FIXME We should consider just leaving auto-suspend enabled forever since
- // we already hold the necessary wakelocks.
- if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {//配置是否需要自动持锁开启
- setHalAutoSuspendModeLocked(false);
- }
- // First acquire suspend blockers if needed.
- if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
- mWakeLockSuspendBlocker.acquire();//wakelock持锁
- mHoldingWakeLockSuspendBlocker = true;
- }
- if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
- mDisplaySuspendBlocker.acquire();//Display持锁
- mHoldingDisplaySuspendBlocker = true;
- }
- ......
- // Then release suspend blockers if needed.
- if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
- mWakeLockSuspendBlocker.release();//wakelock锁释放
- mHoldingWakeLockSuspendBlocker = false;
- }
- if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
- mDisplaySuspendBlocker.release();//Display锁释放
- mHoldingDisplaySuspendBlocker = false;
- }
- // Enable auto-suspend if needed.
- if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {//配置设置&&可以自动持锁
- setHalAutoSuspendModeLocked(true);//自动持锁开启
- }
- }
先来看看needDisPlaySuspendBlockerLocked函数,是否将Display锁释放
- private boolean needDisplaySuspendBlockerLocked() {
- if (!mDisplayReady) {
- return true;//需要持锁
- }
- if (mDisplayPowerRequest.isBrightOrDim()) {
- // If we asked for the screen to be on but it is off due to the proximity
- // sensor then we may suspend but only if the configuration allows it.
- // On some hardware it may not be safe to suspend because the proximity
- // sensor may not be correctly configured as a wake-up source.
- if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
- || !mSuspendWhenScreenOffDueToProximityConfig) {
- return true;
- }
- }
- if (mScreenBrightnessBoostInProgress) {
- return true;
- }
- // Let the system suspend if the screen is off or dozing.
- return false;
- }
这里有两个锁Display和WakeLocks锁,但是这两个锁和之前PowerManager的锁意义不一样,这两个锁是针对hal层的是真正的锁。我们来看下这两个锁。
在PowerManagerService的构造函数中就创建了这两个锁。
- mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
- mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
createSuspendBlockerLocked函数就是新建SuspendBlockerImpl对象:
- private SuspendBlocker createSuspendBlockerLocked(String name) {
- SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name);
- mSuspendBlockers.add(suspendBlocker);
- return suspendBlocker;
- }
再来看看SuspendBlockerImpl 类:
- private final class SuspendBlockerImpl implements SuspendBlocker {
- private final String mName;
- private final String mTraceName;
- private int mReferenceCount;
- public SuspendBlockerImpl(String name) {
- mName = name;
- mTraceName = "SuspendBlocker (" + name + ")";
- }
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mReferenceCount != 0) {
- Slog.wtf(TAG, "Suspend blocker \"" + mName
- + "\" was finalized without being released!");
- mReferenceCount = 0;
- nativeReleaseSuspendBlocker(mName);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
- }
- } finally {
- super.finalize();
- }
- }
- @Override
- public void acquire() {
- synchronized (this) {
- mReferenceCount += 1;
- if (mReferenceCount == 1) {//这里也使用了计数
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\".");
- }
- Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
- nativeAcquireSuspendBlocker(mName);//持锁调用hal层函数
- }
- }
- }
- @Override
- public void release() {
- synchronized (this) {
- mReferenceCount -= 1;
- if (mReferenceCount == 0) {//使用计数
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\".");
- }
- nativeReleaseSuspendBlocker(mName);//释放锁调用hal层
- Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
- } else if (mReferenceCount < 0) {
- Slog.wtf(TAG, "Suspend blocker \"" + mName
- + "\" was released without being acquired!", new Throwable());
- mReferenceCount = 0;
- }
- }
- }
三、hal层持锁相关函数
hal层的持锁和释放锁的函数如下,在com_android_server_power_PowerManagerService.cpp文件
- static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
- ScopedUtfChars name(env, nameStr);
- acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
- }
- static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
- ScopedUtfChars name(env, nameStr);
- release_wake_lock(name.c_str());
- }
我们再看看power.c的代码,在hardware目录下。先来看持锁:
- int
- acquire_wake_lock(int lock, const char* id)
- {
- initialize_fds();
- if (g_error) return g_error;
- int fd;
- if (lock == PARTIAL_WAKE_LOCK) {
- fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
- }
- else {
- return EINVAL;
- }
- return write(fd, id, strlen(id));
- }
先来看initialize_fds函数,调用open_file_descriptors函数:
- static inline void
- initialize_fds(void)
- {
- if (g_initialized == 0) {
- if(open_file_descriptors(NEW_PATHS) < 0)
- open_file_descriptors(OLD_PATHS);
- g_initialized = 1;
- }
- }
我们先来看看NEW_PATHS
- const char * const NEW_PATHS[] = {
- "/sys/power/wake_lock",
- "/sys/power/wake_unlock",
- };
再来看看open_file_descriptors函数:
- static int
- open_file_descriptors(const char * const paths[])
- {
- int i;
- for (i=0; i<OUR_FD_COUNT; i++) {
- int fd = open(paths[i], O_RDWR | O_CLOEXEC);
- if (fd < 0) {
- fprintf(stderr, "fatal error opening \"%s\"\n", paths[i]);
- g_error = errno;
- return -1;
- }
- g_fds[i] = fd;//填充g_fds,一个是wake_lock另一个是wake_unlock
- }
- g_error = 0;
- return 0;
- }
现在再来看看这两个函数
- int
- acquire_wake_lock(int lock, const char* id)
- {
- initialize_fds();
- if (g_error) return g_error;
- int fd;
- if (lock == PARTIAL_WAKE_LOCK) {
- fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];//获取wake_lock的fd
- }
- else {
- return EINVAL;
- }
- return write(fd, id, strlen(id));//往里面写值
- }
- int
- release_wake_lock(const char* id)
- {
- initialize_fds();
- if (g_error) return g_error;
- ssize_t len = write(g_fds[RELEASE_WAKE_LOCK], id, strlen(id));//获取wake_unlock的fd,往里面写值
- return len >= 0;
- }
这样看上层PowerManager的两个锁是否存在我们可以查看/sys/power/wake_lock和/sys/power/wake_unlock这两个目录。
- root@lte26007:/sys/power # cat wake_lock
- PowerManagerService.Display PowerManagerService.WakeLocks
- root@lte26007:/sys/power # cat wake_unlock
- KeyEvents radio-interface
如果灭屏了,Display锁会释放
- root@lte26007:/sys/power # cat wake_unlock
- KeyEvents PowerManagerService.Broadcasts PowerManagerService.Display radio-interface
autosuspend我们这里也就不讲了。
四、总结
这篇博客我们主要分析了从PowerManager的持锁,然后到PowerManagerService的一些逻辑处理。最后由PowerManagerService调用hal层真正的锁来让cpu保持工作。
原文地址: http://46aae4d1e2371e4aa769798941cef698.devproxy.yunshipei.com/kc58236582/article/details/51564328
Android6.0 wakelock深入分析相关推荐
- android adbd分析,android6.0 adbd深入分析(三)adb root重启adbd流程
上篇博客中分析过adb root pc到adbd的流程,这篇博客我们再来讲下adb root是adbd重启并且获取root的流程.我们再来回顾之前的函数: void restart_root_serv ...
- 零死角玩转Android6.0系统Healthd深入分析
零死角玩转Android6.0系统Healthd深入分析 概述 Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework ...
- (原创)android6.0系统 PowerManager深入分析
概述 一直以来,电源管理是电子产品设计中非常重要的环节,也是任何电子设备中最为重要的系统模块之一,优秀的电源管理方案,能够提供持久的续航能力,良好的用户体验,更能提升电子产品的竞争力. 移动设备的电量 ...
- (原创)android6.0系统 PowerManager深入分析(很具体)
概述 一直以来,电源管理是电子产品设计中很重要的环节.也是不论什么电子设备中最为重要的系统模块之中的一个,优秀的电源管理方案.可以提供持久的续航能力,良好的用户体验.更能提升电子产品的竞争力. 移动设 ...
- Android6.0的SMS(短信)源码分析--短信接收
1 SMS接收流程 Android6.0中对短信的处理比起老版本还是变化有点大的.在分析源代码之前,我们可以先猜测一下Android中接收短信的大致流程.首先根据之前分析phone应用的经验, ...
- android6.0中app crash流程分析
要根据这个流程分析一下如何在应用中截获系统的app crash弹框,然后做到更人性化 基于Android 6.0的源码剖析, 分析Android应用Crash是如何处理的. /frameworks/b ...
- android 蓝牙找不到电脑,Android6.0 蓝牙搜索不到设备原因
原因: 为提供更高的数据保护 Android6.0版本上增加了关于Wifi和蓝牙的权限,以下是官方文档说明: 图1 修改方法: 在AndroidManifest 中添加权限 或者 注意 如果targe ...
- 编译可在Nexus5上运行的CyanogenMod13.0 ROM(基于Android6.0)
编译可在Nexus5上运行的CyanogenMod13.0 ROM (基于Android6.0) 作者:寻禹@阿里聚安全 前言 下文中无特殊说明时CM代表CyanogenMod的缩写. 下文中说的&q ...
- android6.0麦克风权限,android 6.0权限检测以及6.0以下,麦克风、相机权限判断
android 6.0以上权限 android 6.0以上权限,我是通过PermissionsDispatcher进行申请,操作的,具体使用方法,见PermissionsDispatcher,Andr ...
最新文章
- php和python哪个学起来简单一点-Php和python php和python哪个容易学
- usaco The Castle(flood fill)
- Linux备份压缩命令
- PAT1053 住房空置率 (20 分)
- hibernate session的load和get方法
- window.atob()与window.btoa()方法实现编码与解码
- 如何使WordPress博客添加多个sidebar侧边栏
- linux 对象 调出r_[转载]linux系统下安装R包
- java.lang.IllegalStateException: Unable to read meta-data for class 问题的解决
- java CPU 占用过高
- 2018,人生是一条蛇,它咬着自己的尾巴
- 电子墨水+android+平板,请推荐一款电子墨水屏的安卓平板
- linux huge模式设置,Linux HugePages 配置步骤
- sCrypt 合约中的椭圆曲线算法:第二部分
- 贸易合规服务市场现状研究分析报告 -
- iOS关闭键盘的两种简单方法
- 2020-08《信息资源管理 02378》真卷(独家文字版),圈定章节考点+统计真题分布
- Educoder Java高级特性 - JDBC(上)
- 福昕阅读器文档无法高亮的问题
- JAVA8 Stream的系列操作,Optional使用---- java养成
热门文章
- Visual C# 2008+SQL Server 2005 数据库与网络开发--9.1.1 SQL Server 2005中的XML功能
- Python学习笔记:TypeError: not all arguments converted during string formatting
- Python学习笔记:基础
- Sublime Text
- 安装CCS5时仿真驱动出现问题的解决方法
- 视频分类/动作识别数据库研究现状
- 科大星云诗社动态20210410
- 科大星云诗社动态20210427
- 科大星云诗社动态20210819
- 科大星云诗社动态20201122