Android休眠唤醒机制简介(二)
本文转载自:http://blog.csdn.net/zhaoxiaoqiang10_/article/details/24408911
Android休眠唤醒机制简介(二)
******************************************************************
作者:sean
日期:2012-11-29
修改历史:2014-1
******************************************************************
接上一节,结合code来分析一下:
具体流程
下面我将分别以两条路线(第一:获得wakelock唤醒锁。第二:系统进入睡眠。)来分别说明各自的流程,让读者对android睡眠唤醒机制有更深入的理解!
第一部分:获得wakelock唤醒锁
比如在应用程序中,当获得wakelock唤醒锁的时候,它首先是调用frameworks/base/core/java/android/os/PowerManager.java类中的public void acquire()方法,而该方法通过android特有的通讯机制,会接着调用到PowerManagerService类中的public void acquireWakeLock。
- public void acquire() {
- synchronized (mToken) {
- acquireLocked();
- }
- }
- 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);
- try {
- mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
- } catch (RemoteException e) {
- }
- mHeld = true;
- }
- }
- @Override // Binder call
- public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws) {
- if (lock == null) {
- throw new IllegalArgumentException("lock must not be null");
- }
- if (packageName == null) {
- throw new IllegalArgumentException("packageName must not be null");
- }
- PowerManager.validateWakeLockParameters(flags, tag);
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
- if (ws != null && ws.size() != 0) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, null);
- } else {
- ws = null;
- }
- final int uid = Binder.getCallingUid();
- final int pid = Binder.getCallingPid();
- final long ident = Binder.clearCallingIdentity();
- try {
- acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
acquireWakeLockInternal()->updatePowerStateLocked()->updateSuspendBlockerLocked()->
- private void updateSuspendBlockerLocked() {
- final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
- final boolean needDisplaySuspendBlocker = needDisplaySuspendBlocker();
- // First acquire suspend blockers if needed.
- if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
- mWakeLockSuspendBlocker.acquire();
- mHoldingWakeLockSuspendBlocker = true;
- }
- if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
- mDisplaySuspendBlocker.acquire();
- mHoldingDisplaySuspendBlocker = true;
- }
- // Then release suspend blockers if needed.
- if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
- mWakeLockSuspendBlocker.release();
- mHoldingWakeLockSuspendBlocker = false;
- }
- if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
- mDisplaySuspendBlocker.release();
- mHoldingDisplaySuspendBlocker = false;
- }
- }
acquire()是什么函数?需要看一下frameworks/base/services/java/com/android/server/PowerManagerService.java类的构造过程。
- public PowerManagerService() {
- synchronized (mLock) {
- mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
- mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
- mDisplaySuspendBlocker.acquire();
- mHoldingDisplaySuspendBlocker = true;
- mScreenOnBlocker = new ScreenOnBlockerImpl();
- mDisplayBlanker = new DisplayBlankerImpl();
- mWakefulness = WAKEFULNESS_AWAKE;
- }
- nativeInit();
- nativeSetPowerState(true, true);
- }
- private SuspendBlocker createSuspendBlockerLocked(String name) {
- SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name);
- mSuspendBlockers.add(suspendBlocker);
- return suspendBlocker;
- }
于是frameworks/base/services/java/com/android/server/PowerManagerService.java类的SuspendBlockerImpl类中的acquire(),便是我们要找的acquire()。
- @Override
- public void acquire() {
- synchronized (this) {
- mReferenceCount += 1;
- if (mReferenceCount == 1) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\".");
- }
- nativeAcquireSuspendBlocker(mName);
- }
- }
- }
而该方法调用了com_android_server_power_PowerManagerService.cpp中的nativeAcquireSuspendBlocker。
- static JNINativeMethod gPowerManagerServiceMethods[] = {
- /* name, signature, funcPtr */
- { "nativeInit", "()V",
- (void*) nativeInit },
- { "nativeSetPowerState", "(ZZ)V",
- (void*) nativeSetPowerState },
- { "nativeAcquireSuspendBlocker", "(Ljava/lang/String;)V",
- (void*) nativeAcquireSuspendBlocker },
- { "nativeReleaseSuspendBlocker", "(Ljava/lang/String;)V",
- (void*) nativeReleaseSuspendBlocker },
- { "nativeSetInteractive", "(Z)V",
- (void*) nativeSetInteractive },
- { "nativeSetAutoSuspend", "(Z)V",
- (void*) nativeSetAutoSuspend },
- };
- static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
- ScopedUtfChars name(env, nameStr);
- acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
- }
函数 acquire_wake_lock()的实现在 power.c中,其定义如下:
- int
- acquire_wake_lock(int lock, const char* id)
- {
- initialize_fds();
- // ALOGI("acquire_wake_lock lock=%d id='%s'\n", lock, id);
- 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));
- }
到现在为止,我们的代码流程已经走了一大半了,我们一开始介绍的android的上面几层Framework层、JNI层、HAL层都已经介绍了。下面就应该是和kernel层进行交互了。
但是在android/hardware/libhardware_legacy/power/power.c中的acquire_wake_lock()函数似乎没法和kernel层进行通信啊?最后的返回语句return write(fd, id, strlen(id))是一个系统调用,这里就实现了与kernel的交互。
kernel/power/main.c中的power_attr宏很多地方用到:
- #define power_attr(_name) \
- static struct kobj_attribute _name##_attr = { \
- .attr = { \
- .name = __stringify(_name), \
- .mode = 0644, \
- }, \
- .show = _name##_show, \
- .store = _name##_store, \
- }
- #ifdef CONFIG_USER_WAKELOCK
- power_attr(wake_lock);
- power_attr(wake_unlock);
- #endif
default y
User-space wake lock api. Write "lockname" or "lockname timeout"
to /sys/power/wake_lock lock and if needed create a wake lock.
Write "lockname" to /sys/power/wake_unlock to unlock a user wake
lock.
- #ifdef CONFIG_PM_WAKELOCKS
- power_attr(wake_lock);
- power_attr(wake_unlock);
- #endif
default n
Allow user space to create, activate and deactivate wakeup source
objects with the help of a sysfs-based interface.
宏展开,等价于:
- static struct kobj_attribute wake_lock_attr = {
- .attr = {
- .name = “wake_lock”,
- .mode = 0644,
- },
- .show = wake_lock_show,
- .store = wake_lock_store,
- }
- static struct kobj_attribute wake_unlock_attr = {
- .attr = {
- .name = “wake_unlock”,
- .mode = 0644,
- },
- .show = wake_unlock_show,
- .store = wake_unlock_store,
- }
show和store函数的源码位于kernel/power/userwakelock.c。
- static struct attribute * g[] = {
- &state_attr.attr,
- #ifdef CONFIG_PM_TRACE
- &pm_trace_attr.attr,
- &pm_trace_dev_match_attr.attr,
- #endif
- #ifdef CONFIG_PM_SLEEP
- &pm_async_attr.attr,
- &wakeup_count_attr.attr,
- #ifdef CONFIG_USER_WAKELOCK
- &wake_lock_attr.attr,
- &wake_unlock_attr.attr,
- #endif
- #ifdef CONFIG_PM_AUTOSLEEP
- &autosleep_attr.attr,
- #endif
- #ifdef CONFIG_PM_WAKELOCKS
- &wake_lock_attr.attr,
- &wake_unlock_attr.attr,
- #endif
- #ifdef CONFIG_PM_DEBUG
- &pm_test_attr.attr,
- #endif
- #ifdef CONFIG_PM_SLEEP_DEBUG
- &pm_print_times_attr.attr,
- #endif
- #endif
- #ifdef CONFIG_FREEZER
- &pm_freeze_timeout_attr.attr,
- #endif
- NULL,
- };
- static struct attribute_group attr_group = {
- .attrs = g,
- };
pm_init()->
error = sysfs_create_group(power_kobj, &attr_group);
好了,我们该回到原来我们产生疑问的地方了这时我们还得关注其中的另一个函数acquire_wake_lock()->initialize_fds()。
- initialize_fds(void)
- {
- // XXX: should be this:
- //pthread_once(&g_initialized, open_file_descriptors);
- // XXX: not this:
- if (g_initialized == 0) {
- if(open_file_descriptors(NEW_PATHS) < 0)
- open_file_descriptors(OLD_PATHS);
- g_initialized = 1;
- }
- }
其实这个函数中最核心的步骤就是open_file_descriptors(NEW_PATHS),顺序打开NEW_PATHS[ ]中的文件:
- 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);
- if (fd < 0) {
- fprintf(stderr, "fatal error opening \"%s\"\n", paths[i]);
- g_error = errno;
- return -1;
- }
- g_fds[i] = fd;
- }
- g_error = 0;
- return 0;
- }
- const char * const NEW_PATHS[] = {
- "/sys/power/wake_lock",
- "/sys/power/wake_unlock",
- };
总之经过着一系列的步骤后,最终我们将在 return write(fd, id, strlen(id));时调用android/kernel/kernel/power/userwakelock.c 中的 wake_lock_store()函数。
- ssize_t wake_lock_store(
- struct kobject *kobj, struct kobj_attribute *attr,
- const char *buf, size_t n)
- {
- long timeout;
- struct user_wake_lock *l;
- mutex_lock(&tree_lock);
- l = lookup_wake_lock_name(buf, 1, &timeout);
- if (IS_ERR(l)) {
- n = PTR_ERR(l);
- goto bad_name;
- }
- if (debug_mask & DEBUG_ACCESS)
- pr_info("wake_lock_store: %s, timeout %ld\n", l->name, timeout);
- if (timeout)
- wake_lock_timeout(&l->wake_lock, timeout);
- else
- wake_lock(&l->wake_lock);
- bad_name:
- mutex_unlock(&tree_lock);
- return n;
- }
- struct rb_root user_wake_locks;
- static struct user_wake_lock *lookup_wake_lock_name(
- const char *buf, int allocate, long *timeoutptr)
- {
- struct rb_node **p = &user_wake_locks.rb_node;
- struct rb_node *parent = NULL;
- struct user_wake_lock *l;
- int diff;
- u64 timeout;
- int name_len;
- const char *arg;
- /* Find length of lock name and start of optional timeout string */
- arg = buf;
- while (*arg && !isspace(*arg))
- arg++;
- //lock name的长度
- name_len = arg - buf;
- if (!name_len)
- goto bad_arg;
- while (isspace(*arg))
- arg++;
- /* Process timeout string */
- if (timeoutptr && *arg) {
- //(char **)&arg存储的是解析string的结束字符
- timeout = simple_strtoull(arg, (char **)&arg, 0);
- while (isspace(*arg))
- arg++;
- //如果解析string的结束字符不是’\0’
- if (*arg)
- goto bad_arg;
- /* convert timeout from nanoseconds to jiffies > 0 */
- timeout += (NSEC_PER_SEC / HZ) - 1;
- //do_div(a,b)的返回值是余数,商保存到a中
- do_div(timeout, (NSEC_PER_SEC / HZ));
- if (timeout <= 0)
- timeout = 1;
- *timeoutptr = timeout;
- } else if (*arg)
- //timeoutptr为NULL
- goto bad_arg;
- else if (timeoutptr)
- //*arg为0,没有timeout
- *timeoutptr = 0;
- /* Lookup wake lock in rbtree */
- //对于一颗空的红黑树,略过while。wake lock按照name从小到大的顺序存储到user_wake_locks红黑树中
- while (*p) {
- parent = *p;
- l = rb_entry(parent, struct user_wake_lock, node);
- diff = strncmp(buf, l->name, name_len);
- //如果buf是l->name的子串,那么l->name[name_len]就不会为0,但是buf[name_len]会为0
- if (!diff && l->name[name_len])
- diff = -1;
- if (debug_mask & DEBUG_ERROR)
- pr_info("lookup_wake_lock_name: compare %.*s %s %d\n",
- name_len, buf, l->name, diff);
- if (diff < 0)
- p = &(*p)->rb_left;
- else if (diff > 0)
- p = &(*p)->rb_right;
- else
- return l;
- }
- /* Allocate and add new wakelock to rbtree */
- //allocate为0,表示不需要分配新的wakelock,只在rbtree上查找,找不到就出错了
- if (!allocate) {
- if (debug_mask & DEBUG_ERROR)
- pr_info("lookup_wake_lock_name: %.*s not found\n",
- name_len, buf);
- return ERR_PTR(-EINVAL);
- }
- l = kzalloc(sizeof(*l) + name_len + 1, GFP_KERNEL);
- if (l == NULL) {
- if (debug_mask & DEBUG_FAILURE)
- pr_err("lookup_wake_lock_name: failed to allocate "
- "memory for %.*s\n", name_len, buf);
- return ERR_PTR(-ENOMEM);
- }
- memcpy(l->name, buf, name_len);
- if (debug_mask & DEBUG_NEW)
- pr_info("lookup_wake_lock_name: new wake lock %s\n", l->name);
- wake_lock_init(&l->wake_lock, WAKE_LOCK_SUSPEND, l->name);
- //插入结点,并染成红色
- rb_link_node(&l->node, parent, p);
- rb_insert_color(&l->node, &user_wake_locks);
- return l;
- bad_arg:
- if (debug_mask & DEBUG_ERROR)
- pr_info("lookup_wake_lock_name: wake lock, %.*s, bad arg, %s\n",
- name_len, buf, arg);
- return ERR_PTR(-EINVAL);
- }
wake_lock_store()执行的基本流程为:首先调用lookup_wake_lock_name()来获得指定的唤醒锁,若延迟参数timeout为零的话,就调用 wake_lock()否则就调用wake_lock_timeout(),但不管调用哪个最后都会调用到android/kernel/kernel/power/wakelock.c中的函数static void wake_lock_internal()。
- static void wake_lock_internal(
- struct wake_lock *lock, long timeout, int has_timeout)
- {
- int type;
- unsigned long irqflags;
- long expire_in;
- spin_lock_irqsave(&list_lock, irqflags);
- type = lock->flags & WAKE_LOCK_TYPE_MASK;
- //检查type是否合法
- BUG_ON(type >= WAKE_LOCK_TYPE_COUNT);
- //检查是否初始化过
- BUG_ON(!(lock->flags & WAKE_LOCK_INITIALIZED));
- #ifdef CONFIG_WAKELOCK_STAT
- if (type == WAKE_LOCK_SUSPEND && wait_for_wakeup) {
- if (debug_mask & DEBUG_WAKEUP)
- pr_info("wakeup wake lock: %s\n", lock->name);
- wait_for_wakeup = 0;
- lock->stat.wakeup_count++;
- }
- if ((lock->flags & WAKE_LOCK_AUTO_EXPIRE) &&
- (long)(lock->expires - jiffies) <= 0) {
- wake_unlock_stat_locked(lock, 0);
- lock->stat.last_time = ktime_get();
- }
- #endif
- if (!(lock->flags & WAKE_LOCK_ACTIVE)) {
- lock->flags |= WAKE_LOCK_ACTIVE;
- #ifdef CONFIG_WAKELOCK_STAT
- lock->stat.last_time = ktime_get();
- #endif
- }
- //从inactive_locks上删除
- list_del(&lock->link);
- if (has_timeout) {
- if (debug_mask & DEBUG_WAKE_LOCK)
- pr_info("wake_lock: %s, type %d, timeout %ld.%03lu\n",
- lock->name, type, timeout / HZ,
- (timeout % HZ) * MSEC_PER_SEC / HZ);
- lock->expires = jiffies + timeout;
- lock->flags |= WAKE_LOCK_AUTO_EXPIRE;
- list_add_tail(&lock->link, &active_wake_locks[type]);
- } else {
- if (debug_mask & DEBUG_WAKE_LOCK)
- pr_info("wake_lock: %s, type %d\n", lock->name, type);
- lock->expires = LONG_MAX;
- lock->flags &= ~WAKE_LOCK_AUTO_EXPIRE;
- list_add(&lock->link, &active_wake_locks[type]);
- }
- if (type == WAKE_LOCK_SUSPEND) {
- current_event_num++;
- #ifdef CONFIG_WAKELOCK_STAT
- if (lock == &main_wake_lock)
- update_sleep_wait_stats_locked(1);
- else if (!wake_lock_active(&main_wake_lock))
- update_sleep_wait_stats_locked(0);
- #endif
- if (has_timeout)
- expire_in = has_wake_lock_locked(type);
- else
- expire_in = -1;
- if (expire_in > 0) {
- if (debug_mask & DEBUG_EXPIRE)
- pr_info("wake_lock: %s, start expire timer, "
- "%ld\n", lock->name, expire_in);
- mod_timer(&expire_timer, jiffies + expire_in);
- } else {
- if (del_timer(&expire_timer))
- if (debug_mask & DEBUG_EXPIRE)
- pr_info("wake_lock: %s, stop expire timer\n",
- lock->name);
- if (expire_in == 0)
- queue_work(suspend_work_queue, &suspend_work);
- }
- }
- spin_unlock_irqrestore(&list_lock, irqflags);
- }
第二部分:系统进入睡眠
假如现在我们按了PAD上的power睡眠键,经过一些列的事件处理后,它会调用到PowerManager类中的
- public void goToSleep(long time) {
- try {
- mService.goToSleep(time, GO_TO_SLEEP_REASON_USER);
- } catch (RemoteException e) {
- }
- }
而该函数会调用到PowerManagerService类中的public void goToSleep()方法;
- @Override // Binder call
- public void goToSleep(long eventTime, int reason) {
- if (eventTime > SystemClock.uptimeMillis()) {
- throw new IllegalArgumentException("event time must not be in the future");
- }
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
- final long ident = Binder.clearCallingIdentity();
- try {
- goToSleepInternal(eventTime, reason);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- private void goToSleepInternal(long eventTime, int reason) {
- synchronized (mLock) {
- if (goToSleepNoUpdateLocked(eventTime, reason)) {
- updatePowerStateLocked();
- }
- }
- }
goToSleepNoUpdateLocked是goToSleep功能的计算者,来决定是否要休眠,而updatePowerStateLocked函数算是功能的执行者,而且这个执行者同时负责执行了很多其他的功能。其实goToSleepNoUpdateLocked并没有真正地让device进行sleep,仅仅只是把PowerManagerService中一些必要的属性进行了赋值,等会在分析updatePowerStateLocked的时候,再给出解释。在PowerManagerService的代码中,有很多的方法的名字中都含有xxxNoUpdateLocked这样的后缀,我觉得这样做大概是因为,都类似于goToSleepNoUpdateLocked方法,并没有真正地执行方法名字所描述的功能,仅仅是更新了一些必要的属性。 所以在Android系统中可以把多个power state属性的多个变化放在一起共同执行的,而真正的功能执行者就是updatePowerStateLocked。
- private void updatePowerStateLocked() {
- if (!mSystemReady || mDirty == 0) {//如果系统没有准备好,或者power state没有发生任何变化,这个方法可以不用执行的
- return;
- }
- if(!SystemProperties.getBoolean("ro.platform.has.mbxuimode", false)) {
- if (isHdmiPlugged()) {
- return;
- }
- }
- // Phase 0: Basic state updates.
- updateIsPoweredLocked(mDirty);
- updateStayOnLocked(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 dreams and display power state.
- updateDreamLocked(dirtyPhase2);
- updateDisplayPowerStateLocked(dirtyPhase2);
- // Phase 3: Send notifications, if needed.
- if (mDisplayReady) {
- sendPendingNotificationsLocked();
- }
- // Phase 4: Update suspend blocker.
- // Because we might release the last suspend blocker here, we need to make sure
- // we finished everything else first!
- updateSuspendBlockerLocked();
- }
对sys/power/state进行读写操作的时候,(linux/kernel/power/main.c)中的state_store()函数会被调用,在该函数中会分成两个分支:
Android特有的earlysuspend: request_suspend_state(state)
Linux标准的suspend: enter_state(state)
- 顶
- 0
- 踩
Android休眠唤醒机制简介(二)的更多相关文章
- Android休眠唤醒机制简介(一)【转】
本文转载自:http://blog.csdn.net/zhaoxiaoqiang10_/article/details/24408129 Android休眠唤醒机制简介(一) ************ ...
- android 休眠唤醒机制分析(二) — early_suspend
本文转自:http://blog.csdn.net/g_salamander/article/details/7982170 early_suspend是Android休眠流程的第一阶段即浅度休眠,不 ...
- Android休眠唤醒机制
有四种方式可以引起休眠 ①在wake_unlock()中, 如果发现解锁以后没有任何其他的wake lock了, 就开始休眠 ②在定时器到时间以后, 定时器的回调函数会查看是否有其他的wake loc ...
- android 休眠唤醒机制分析(一) — wake_lock
本文转自:http://blog.csdn.net/g_salamander/article/details/7978772 Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一 ...
- android 休眠唤醒机制分析(一) — wake_lock【转】
Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作.wake_lock一般在关闭lcd.tp但系统 ...
- android 休眠唤醒机制分析(三) — suspend
本文转自:http://blog.csdn.net/g_salamander/article/details/7988340 前面我们分析了休眠的第一个阶段即浅度休眠,现在我们继续看休眠的第二个阶段 ...
- android休眠唤醒驱动流程分析【转】
转自:http://blog.csdn.net/hanmengaidudu/article/details/11777501 标准linux休眠过程: l power managemen ...
- Android睡眠唤醒机制--Kernel态
一.简介 Android系统中定义了几种低功耗状态:earlysuspend.suspend.hibernation. 1) earlysuspend: 是一种低功耗的状态,某些设备可以选 ...
- Android包管理机制(二)PackageInstaller安装APK
前言 在本系列上一篇文章Android包管理机制(一)PackageInstaller的初始化中我们学习了PackageInstaller是如何初始化的,这一篇文章我们接着学习PackageInsta ...
随机推荐
- 国内外知名IT科技博客
国内 1.36氪(www.36kr.com): 目前国内做的最风生水起的科技博客,以介绍国内外互联网创业新闻为主的博客网站,自己建立有36Tree互联网创业融投资社区.36氪的名字源于元素周期 表的第 ...
- JS——delete
1.对象属性删除 <script> function fun(){ this.name = 'mm'; } var obj = new fun(); console.log(obj.nam ...
- 几种fullpage用法及demo
jQuery全屏滚动插件fullPage.js https://github.com/alvarotrigo/fullPage.js http://www.dowebok.com/77.html 全屏 ...
- (转)分布式文件存储FastDFS(五)FastDFS常用命令总结
http://blog.csdn.net/xingjiarong/article/details/50561471 1.启动FastDFS tracker: /usr/local/bin/fdfs_t ...
- eclipse常用设置之自动格式化
Eclipse 保存文件时自动格式化代码 很多同学不知道Eclipse有个很有用的功能,就是自动格式源代码的功能,一般大家都是直接Ctrl+Shift+F手动格式化,多浪费时间. 其实Eclips ...
- cvpr2016论文
http://openaccess.thecvf.com/ICCV2017.py http://openaccess.thecvf.com/CVPR2017.py http://www.cv-foun ...
- Linux内核中_IO,_IOR,_IOW,_IOWR宏的用法与解析
ref from : http://blog.csdn.net/zhuxiaoping54532/article/details/49680537 main 在驱动程序里, ioctl() 函数上传送 ...
- cf 337 div2 c
C. Harmony Analysis time limit per test 3 seconds memory limit per test 256 megabytes input standard ...
- Spring框架学习之SpringAOP(二)
AOP概念 AOP(Aspect-Oriented Programming,面向切面编程),AOP是OOP(面向对象编程)的补充和完善 AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用 ...
- mysql登录出现1045错误
这个问题是在window server 2012上安装mysql之后, 远程访问时出现的1045错误 我新建了一个相同的用户用于远程访问, 密码也相同, 但是还是访问不了 参照链接:https://b ...