内容简介:PowerManagerService是Android系统电源管理的核心服务,它在Framework层建立起一个策略控制方案,向下决策HAL以及kernel层来控制设备待机状态,控制显示屏,背光灯,距离传感器,光线传感器等硬件设备的状态。向上提供给应用程序相应的操作接口来保持系统处于唤醒状态,比如音乐播放时持续保持系统唤醒,应用通知来临唤醒手机屏幕等场景。它的启动方式和AlarmManagerService大同小异,只不过PMS是在SystemServer中的引导服务中启动,AMS是在SystemServe
PowerManagerService是Android系统电源管理的核心服务,它在Framework层建立起一个策略控制方案,向下决策HAL以及kernel层来控制设备待机状态,控制显示屏,背光灯,距离传感器,光线传感器等硬件设备的状态。向上提供给应用程序相应的操作接口来保持系统处于唤醒状态,比如音乐播放时持续保持系统唤醒,应用通知来临唤醒手机屏幕等场景。它的启动方式和AlarmManagerService大同小异,只不过PMS是在SystemServer中的引导服务中启动,AMS是在SystemServer中的其他服务中启动。启动时序图如下:
2.PowerManager
PowerManager是PowerManagerService的代理类,它对外提供了操作PMS的接口函数,真正的处理工作则是在PMS中。获取PowerManager的实例方式和AlarmManager一样,时序图也类似,这里不做过多讲解。在PowerManager中只有部分方法对上层应用开放,相关部分接口如下:
(1)isScreenOn():获取屏幕是否亮起;
(2)reboot(xx):用于重启设备,但是需要REBOOT权限;
(3)isPowerSaveMode():获取系统是否处于省电模式;
(4)isDeviceIdleMode():系统是否处于idle状态;
(5)newWakeLock(xx):生成新的wakelock实例;
(6)wakeUp(xx):用于强制唤醒系统;@hide
(7)shutdown(xx):关机;@hide
(8)userActivity(xx):用于通知PowerMangerService有用户活动发生了,并重新计算自动灭屏时间同时如果系统没有进入睡眠则点亮屏幕,比如用户点击了屏幕等;@hide
(9)goToSleep(xx):强制使系统进入到睡眠状态;当用户按了power键则会调用该方法;@hide
(10)boostScreenBrightness(xx):将屏幕亮度调到最大值;@hide
(11)and so on;
在PowerManager中所有的方法实现最后都是通过Binder的方式调用到PowerManagerService中的函数进行实现的。所以后面对PowerManagerService的实现进行讲解,在讲解PMS之前先来了解一下wakelock机制。
3.WakeLock机制
Wakelock是一种锁的机制,当用户通过PowerManager获取到该锁之后那么系统就不能进入到休眠状态,直到该锁被释放为止。
3.1 生成wakeLock实例
在Android中通过PowerManager对系统电源状态进行管理。而在实际开发过程中,我们使用PowerManager主要是为了通过它获取一个WakeLock锁,用于控制当前设备的一些耗电操作。WakeLock是PowerManager中的内部类,我们需要通过PowerManager中的newWakeLock(int levelAndFlags,String tag)方法生成实例,代码如下:
public WakeLock newWakeLock(int levelAndFlags, String tag) { //判断用户传入的levelAndFlags以及tag是否合法 validateWakeLockParameters(levelAndFlags, tag); return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName()); } 复制代码
3.2 levelAndFlags值讲解
(1)PARTIAL_WAKE_LOCK:使CPU高速运行,屏幕和键盘光关闭;
(2)SCREEN_DIM_WAKE_LOCK:确保屏幕亮起,但是允许它是最低亮度;已经被废弃;
(3)SCREEN_BRIGHT_WAKE_LOCK:保证屏幕亮起,并且是最大亮度;已经被废弃;
(4)FULL_WAKE_LOCK:保证屏幕和键盘背光是以最大亮度亮起;已经被废弃;
(5)PROXIMITY_SCREEN_OFF_WAKE_LOCK:用于和接近传感器配合使用,来实现电话类应用中当手机贴近耳朵的时候将屏幕熄灭,而离开的时候又使屏幕亮起;
(6)DOZE_WAKE_LOCK:系统级别的flag,在DreamManager中被使用;
(7)DRAW_WAKE_LOCK:系统级别的flag,在WindowManager中被使用。
3.3 WakeLock的使用
(1)即使持有多次wakelock锁也只需要一次释放;
(2)持有多少次wakelock锁就需要释放多少次;
(3)设置wakelock超时时间,超时之后自动释放,可以手动释放;
持有wakelock锁代码如下:
private void acquireLocked() { //用于统计调用当前wakelock多少次 mInternalCount++; mExternalCount++; //mRefCounted表示是否通过计数的方式使用wakelock,默认为true: //true:那么acquire了多少次就需要release多少次;实际情况就是在第一次调用的 //时候才会真正去获取wakelock锁,最后一次释放的时候才会真正释放wakelock锁 //false:那么即使调用了多次也只需要释放一次 if (!mRefCounted || mInternalCount == 1) { mHandler.removeCallbacks(mReleaser); Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0); try { //调用到PowerManagerService中 mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource, mHistoryTag); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mHeld = true; } } 复制代码
释放wakelock锁代码如下:
public void release(int flags) { synchronized (mToken) { //将调用次数减1 if (mInternalCount > 0) { mInternalCount--; } //RELEASE_FLAG_TIMEOUT表示是通过设置超时时间的方式持有wakelock锁的 if ((flags & RELEASE_FLAG_TIMEOUT) == 0) { mExternalCount--; } if (!mRefCounted || mInternalCount == 0) { mHandler.removeCallbacks(mReleaser); if (mHeld) { Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0); try { mService.releaseWakeLock(mToken, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mHeld = false; } } //如果是持有多少次wakelock锁就需要释放多少次wakelock锁 //那么释放wakelock之前必须先持有wakelock锁 if (mRefCounted && mExternalCount < 0) { throw new RuntimeException("WakeLock under-locked " + mTag); } } } 复制代码
4.PMS中各个方法讲解
4.1 wakeup
该方法用于强制唤醒系统,通过PowerManager调用到PMS中的wakeUp(xx)方法中,首先检查用户是否添加了DEVICE_POWER权限并获取应用的uid,最后调用wakeUpNoUpdateLocked(xx)函数和updatePowerStateLocked()函数,部分代码如下所示:
mDirty:用于标识哪个状态改变了或者需要被重新计算 private boolean wakeUpNoUpdateLocked(long eventTime, String reason, int reasonUid,String opPackageName, int opUid) { //判断唤醒的时间是否小于上一次睡眠时间或者系统处于睡眠时间或者系统还没有完全启动或者系统还没有ready //则不进行唤醒 if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE || !mBootCompleted || !mSystemReady) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp"); try { ....... //更新上一次唤醒时间 mLastWakeTime = eventTime; //调用链:mNotifier.onWakeUp(xx)-->BatteryStatsService.noteWakeUp(xx)--> //BatteryStatsImpl.noteWakeUpLocke(xx)-->BatteryStatsImpl.addHistoryEventLocked(xx) mNotifier.onWakeUp(reason, reasonUid, opPackageName, opUid); userActivityNoUpdateLocked(eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } 复制代码
4.2 userActivity
用于通知PowerManagerService有用户活动发生,并重新计算自动灭屏时间以及在系统没有进入睡眠的时候重新点亮屏幕;比如用户点击屏幕或者按键等操作都会触发到该函数中。当从PowerManager中通过Binder的方式调用到PowerManagerService中首先会进行权限的检测,然后调用到userActivityNoUpdateLocked和updatePowerStateLocked方法中,部分代码如下:
//更新用户活动上一次触发时间以及mDirty标签等 private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { //如果当前触发时间小于上一次睡眠时间或者小于上一次唤醒时间(时间不合法)等则直接返回false if (eventTime < mLastSleepTime || eventTime < mLastWakeTime || !mBootCompleted || !mSystemReady) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "userActivity"); try { //如果触发时间大于上一次唤醒屏幕时间,更新上一次唤醒屏幕时间 if (eventTime > mLastInteractivePowerHintTime) { powerHintInternal(PowerHint.INTERACTION, 0); mLastInteractivePowerHintTime = eventTime; } mNotifier.onUserActivity(event, uid); if (mUserInactiveOverrideFromWindowManager) { mUserInactiveOverrideFromWindowManager = false; mOverriddenTimeout = -1; } //如果系统处于睡眠或者doze状态那么直接返回false if (mWakefulness == WAKEFULNESS_ASLEEP || mWakefulness == WAKEFULNESS_DOZING || (flags & PowerManager.USER_ACTIVITY_FLAG_INDIRECT) != 0) { return false; } //如果有应用Activity在前台,则更新该应用前台时间 maybeUpdateForegroundProfileLastActivityLocked(eventTime); //如果设置了屏幕已经变暗但是延长屏幕当前亮度的时间flag值 if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { //如果当前触发时间大于上一次延长屏幕当前亮度触发时间以及上一次让屏幕更亮的触发时间 if (eventTime > mLastUserActivityTimeNoChangeLights && eventTime > mLastUserActivityTime) { mLastUserActivityTimeNoChangeLights = eventTime; //给PMS中的标签添加有用户活动发生标签 mDirty |= DIRTY_USER_ACTIVITY; //如果是按钮被点击或者被释放的用户活动 if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; } return true; } } else { //更新上一次用户活动触发时间 if (eventTime > mLastUserActivityTime) { mLastUserActivityTime = eventTime; mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; } return true; } } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return false; } 复制代码
4.3 goToSleep
强制系统进入到休眠状态,并覆盖掉所有正在执行的wakelock,在用户按了power键之后会被触发。在PowerManager中通过Bidner的方式调用到PMS中,首先进行权限检测,然后调用到goToSleepInternal(xx)方法中,最后调用到goToSleepNoUpdateLocked和updatePowerStateLocked方法,部分代码如下:
private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) { //如果触发时间小于上一次唤醒时间或者系统处于睡眠或者doze状态,或者系统没有完全启动或者系统没有准备完全 //直接返回false if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP|| mWakefulness == WAKEFULNESS_DOZING || !mBootCompleted || !mSystemReady) { return false; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep"); try { //更新上一次进入睡眠时间 mLastSleepTime = eventTime; //可以开启屏保 mSandmanSummoned = true; //将当前设置为doze状态 setWakefulnessLocked(WAKEFULNESS_DOZING, reason); //用于记录在系统进入到睡眠状态的时候有多少wakelock会被清除掉 int numWakeLocksCleared = 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.FULL_WAKE_LOCK: case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: case PowerManager.SCREEN_DIM_WAKE_LOCK: numWakeLocksCleared += 1; break; } } EventLogTags.writePowerSleepRequested(numWakeLocksCleared); // 将系统设置为睡眠状态 if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { reallyGoToSleepNoUpdateLocked(eventTime, uid); } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } 复制代码
4.4 acquireWakeLockInternal
private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName, WorkSource ws, String historyTag, int uid, int pid) { synchronized (mLock) { WakeLock wakeLock; //从存储wakelock的列表中查找是否存在当前设置的wakelock int index = findWakeLockIndexLocked(lock); boolean notifyAcquire; if (index >= 0) { wakeLock = mWakeLocks.get(index); //判断设置的wakelock与列表中存储的wakelock属性是否发生了变化 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; //创建一个新的wakelock并添加到wakelocks列表中去 } else { .................. mWakeLocks.add(wakeLock); setWakeLockDisabledStateLocked(wakeLock); notifyAcquire = true; } applyWakeLockFlagsOnAcquireLocked(wakeLock, uid); mDirty |= DIRTY_WAKE_LOCKS; updatePowerStateLocked(); if (notifyAcquire) { // This needs to be done last so we are sure we have acquired the // kernel wake lock. Otherwise we have a race where the system may // go to sleep between the time we start the accounting in battery // stats and when we actually get around to telling the kernel to // stay awake. //发送wakelock已经执行通知 notifyWakeLockAcquiredLocked(wakeLock); } } } private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) { //如果当前wakelock能够唤醒屏幕,且当前wakelock锁设置的flag也能够唤醒屏幕 if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0 && 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); } private boolean wakeUpNoUpdateLocked(long eventTime, String reason, int reasonUid, String opPackageName, int opUid) { //如果系统开机时间小于上一次睡眠时间或者当前系统处于唤醒状态或者系统没有启动完全则直接返回false if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE || !mBootCompleted || !mSystemReady) { return false; } Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0); Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp"); .............. //更新上一次唤醒时间 mLastWakeTime = eventTime; mNotifier.onWakeUp(reason, reasonUid, opPackageName, opUid); //更新用户活动时间 userActivityNoUpdateLocked( eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } 复制代码
4.5 updatePowerStateLocked
private void updatePowerStateLocked() { //如果系统还没有准备好或者没有任何改变则直接返回; if (!mSystemReady || mDirty == 0) { return; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState"); try { //用于更新充电状态,充电方式,剩余电量,是否是低电量,并记录充电状态是否改变以及是否在 //插拔电之后亮屏等 updateIsPoweredLocked(mDirty); //用于更新mStayOn变量,如果为true则表示屏幕长亮 //如果设置中设置了可保持长亮,如果手机正在充电则会将mStayOn设置为true updateStayOnLocked(mDirty); //用于确定屏幕是变亮或者变暗还是屏保状态 updateScreenBrightnessBoostLocked(mDirty); final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; //循环是因为wakelock和user activity的计算会受到Wakefulness的影响 for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; //将对应uid的wakelock的flag值更新到mProfilePowerState列表中对应item的mWakeLockSummary上 //并根据系统状态去除不必要的wakelock的flag值 updateWakeLockSummaryLocked(dirtyPhase1); //根据系统最后一次调用userActivity(xx)方法的时间计算现在是否可以将屏幕状态 //mUserActivitySummary设置成亮屏或者暗屏 updateUserActivitySummaryLocked(now, dirtyPhase1); //返回true表示系统状态发生了变化,那么需要重新计算user Activity以及wakelock的flag值 if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } updateProfilesLocked(now); //更新屏幕显示 final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2); //更新屏保 updateDreamLocked(dirtyPhase2, displayBecameReady); //系统状态更新完成 finishWakefulnessChangeIfNeededLocked(); //可能需要释放最后一个维持cpu唤醒或者屏幕亮灭的blocker,所以需要确保所有的任务都已经完成 //该函数是PMS调用到kernel层设置wakelock的唯一入口 updateSuspendBlockerLocked(); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } private void updateScreenBrightnessBoostLocked(int dirty) { //如果可以将屏幕亮度调节到了最大 if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) { if (mScreenBrightnessBoostInProgress) { final long now = SystemClock.uptimeMillis(); mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT); //如果上一次调节屏幕亮度到最大的时间晚于上一次系统睡眠时间 if (mLastScreenBrightnessBoostTime > mLastSleepTime) { //调节亮度到最大的下一次时间延迟 = 上一次调节亮度到最大 + 5s final long boostTimeout = mLastScreenBrightnessBoostTime + SCREEN_BRIGHTNESS_BOOST_TIMEOUT; //如果调节亮度到最大的时间大于当前时间,则在boostTimeOut时间通过handler重新触发 if (boostTimeout > now) { Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, boostTimeout); return; } } //将是否正在调节亮度到最大置为false mScreenBrightnessBoostInProgress = false; mNotifier.onScreenBrightnessBoostChanged(); //触发用户活动 userActivityNoUpdateLocked(now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); } } } private void updateWakeLockSummaryLocked(int dirty) { //如果wakelock和WAKEFULNESS改变了则继续执行 if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) { mWakeLockSummary = 0; final int numProfiles = mProfilePowerState.size(); //mProfilePowerState:用于存储用户设置的屏幕灭屏时间 for (int i = 0; i < numProfiles; i++) { mProfilePowerState.valueAt(i).mWakeLockSummary = 0; } final int numWakeLocks = mWakeLocks.size(); for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); //根据wakelock的flag值来确定系统的状态(cpu运行、屏幕变量或者变暗等) final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock); //将wakelock的flag值更新到mWakeLockSummary中 mWakeLockSummary |= wakeLockFlags; for (int j = 0; j < numProfiles; j++) { final ProfilePowerState profile = mProfilePowerState.valueAt(j); //将对应uid的wakelock的flag值更新到mProfilePowerState中item对应uid的mWakeLockSummary中 if (wakeLockAffectsUser(wakeLock, profile.mUserId)) { profile.mWakeLockSummary |= wakeLockFlags; } } } //如果系统不处于doze状态则从mWakeLockSummary中移除doze和dream标签 if (mWakefulness != WAKEFULNESS_DOZING) { mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW); } //如果系统处于睡眠状态或者有wakelock有doze标签 if (mWakefulness == WAKEFULNESS_ASLEEP || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) { //从mWakeLockSummary中去掉 mWakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM | WAKE_LOCK_BUTTON_BRIGHT); } //根据系统状态重新调整mWakeLockSummary值(去除不必要的wakelockflag值) mWakeLockSummary = adjustWakeLockSummaryLocked(mWakeLockSummary); for (int i = 0; i < numProfiles; i++) { final ProfilePowerState profile = mProfilePowerState.valueAt(i); profile.mWakeLockSummary = adjustWakeLockSummaryLocked(profile.mWakeLockSummary); } } } //该方法会根据当前的wakelocks以及user Activity判断设备是否进入到屏保状态 private boolean updateWakefulnessLocked(int dirty) { boolean changed = false; //如果系统中有状态变化 if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_DOCK_STATE)) != 0) { //如果系统是唤醒状态且系统需要马上进入到睡眠状态 if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { final long time = SystemClock.uptimeMillis(); //返回true表示手机会自动进入屏保 if (shouldNapAtBedTimeLocked()) { changed = napNoUpdateLocked(time, Process.SYSTEM_UID); } else { //系统进入了深度睡眠 changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); } } } return changed; } 复制代码
4.6 PowerManagerInternal中定义了四种WakeFulness值用于标识系统当前的状态:
(1)WAKEFULNESS_ASLEEP:表示系统当前处于休眠状态,只能被wakeup()唤醒;
(2)WAKEFULNESS_AWAKE:表示系统目前处于正常运行状态;
(3)WAKEFULNESS_DREAMING:表示系统当前处于屏保状态;
(4)WAKEFULNESS_DOZING:表示系统处于doze状态。
参考链接: juejin.im/post/5921ca…
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
松本行弘的程序世界
松本行弘 / 柳德燕、李黎明、夏倩、张文旭 / 人民邮电出版社 / 2011-8 / 75.00元
《松本行弘的程序世界》是探索程序设计思想和方法的经典之作。作者从全局的角度,利用大量的程序示例及图表,深刻阐述了Ruby编程语言的设计理念,并以独特的视角考察了与编程相关的各种技术。阅读《松本行弘的程序世界》不仅可以深入了解编程领域各个要素之间的关系,而且能够学到大师的思考方法。 《松本行弘的程序世界》面向各层次程序设计人员和编程爱好者,也可以供相关技术人员参考。一起来看看 《松本行弘的程序世界》 这本书的介绍吧!
图片转BASE64编码
在线图片转Base64编码工具
HEX CMYK 转换工具
HEX CMYK 互转工具