从Android 6.0开始,位于frameworks/bases/packages/Keyguard的Keyguard开始被编译为一个jar包,被SystemUI静态导入,相当于SystemUI的一个界面,这样Keyguard就可以复用SystemUI里关于通知的那一部分代码,这个在Keyuard的Makefile里可以看到

 LOCAL_PATH:= $(call my-dir)
17include $(CLEAR_VARS) 19LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files) 21LOCAL_MODULE := Keyguard

23LOCAL_CERTIFICATE := platform 25LOCAL_JAVA_LIBRARIES := SettingsLib 27LOCAL_PRIVILEGED_MODULE := true 29LOCAL_PROGUARD_FLAG_FILES := proguard.flags 31LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res 33include $(BUILD_STATIC_JAVA_LIBRARY) #include $(call all-makefiles-under,$(LOCAL_PATH))

Keyguard分为两个界面,不用输入密码的一级锁屏界面(SystemUI认为是PhoneStatusBar)与相应源码文件包含Security字样的二级锁屏界面(SystemUI认为是Bouncer)。

一级锁屏界面

二级锁屏界面

各个部件调用关系是下边这张图

可以看到,Keyguard第一个涉及到的是KeyguardDisplayManager,其由KeyguardViewMediator这个界面中介调用。

首先,由SystemServer的startSystemUi方法里的StartServiceAsUser连接到SystemUIService。再由SystemUIService(SystemUI/src/com/android/systemui/SystemUIService.java)里的onCreate函数调用(就这一个有用的函数)startServicesIfNeeded方法,开始SystemUI的初始化

1     static final void startSystemUi(Context context) {
2 Intent intent = new Intent();
3 intent.setComponent(new ComponentName("com.android.systemui",
4 "com.android.systemui.SystemUIService"));
5 intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
6 //Slog.d(TAG, "Starting service: " + intent);
7 context.startServiceAsUser(intent, UserHandle.SYSTEM);
8 }
1 public class SystemUIService extends Service {
2
3 @Override
4 public void onCreate() {
5 super.onCreate();
6 ((SystemUIApplication) getApplication()).startServicesIfNeeded();
7 }

KeyguardViewMediator是SystemUIApplication名为SERVICES数组里的一员,这个数组的东西都是SystemUI要load的Service的class,这个框架流程在start的时候,调用这些类的start方法,并在bootcompleted的时候调用这些类的onbootcompleted方法。

  /**
44 * The classes of the stuff to start.
45 */
46 private final Class<?>[] SERVICES = new Class[] {
47 com.android.systemui.tuner.TunerService.class,
48 com.android.systemui.keyguard.KeyguardViewMediator.class,
49 com.android.systemui.recents.Recents.class,
50 com.android.systemui.volume.VolumeUI.class,
51 Divider.class,
52 com.android.systemui.statusbar.SystemBars.class,
53 com.android.systemui.usb.StorageNotification.class,
54 com.android.systemui.power.PowerUI.class,
55 com.android.systemui.media.RingtonePlayer.class,
56 com.android.systemui.keyboard.KeyboardUI.class,
57 com.android.systemui.tv.pip.PipUI.class,
58 com.android.systemui.shortcut.ShortcutKeyDispatcher.class,
59 com.android.systemui.VendorServices.class
60 };

可以看到,在SystemUIApplication这个类的startServicesIfNeeded里,会依次调用SERVICES里的start函数,这里会先调用com.android.systemui.keyguard.KeyguardViewMediator的start方法

 final int N = services.length;
156 for (int i=0; i<N; i++) {
157 Class<?> cl = services[i];
158 if (DEBUG) Log.d(TAG, "loading: " + cl);
159 try {
160 Object newService = SystemUIFactory.getInstance().createInstance(cl);
161 mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
162 } catch (IllegalAccessException ex) {
163 throw new RuntimeException(ex);
164 } catch (InstantiationException ex) {
165 throw new RuntimeException(ex);
166 }
167
168 mServices[i].mContext = this;
169 mServices[i].mComponents = mComponents;
170 if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
171 mServices[i].start();
172
173 if (mBootCompleted) {
174 mServices[i].onBootCompleted();
175 }
176 }
177 mServicesStarted = true;

下面看下KeyguardViewMediator及其start方法

这个类位于SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java,比较长。第三方客户端也可以通过调用KeyguardManager这个类来获取和修改锁屏的信息、状态,这个类是锁屏操作的binder server基础类。

KeyguardViewMediator是抽象类SystemUI的一个具体实现子类,SystemUI这个类的主要方法是putComponent和getComponent,保存和获取相应类对应的实际组件的映射。还用mContext和mComponents保存相应的SystemUIApplication实例和其中名为component的hashmap。

KyeguardViewMediator总体负责所有的锁屏状态,并根据状态来决定调用哪些组件。

KeyguardViewMediator的start方法很简单,初始化锁屏状态,把KeyguardViewMediator的class和KeyguardViewMediator建立映射。

  @Override
699 public void start() {
700 synchronized (this) {
701 setupLocked();
702 }
703 putComponent(KeyguardViewMediator.class, this);
704 }

初始化的过程在setupLocked方法里完成,首先获取系统的PowerManagerService,WindowManagerService,TrustManagerService并初始化一把partial wakelock锁

            mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
635 mWM = WindowManagerGlobal.getWindowManagerService();
636 mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
637
638 mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
639 mShowKeyguardWakeLock.setReferenceCounted(false);

随后注册DELAYED_KEYGUARD_ACTION和DELAYED_LOCK_PROFILE_ACTION这两个Intent的broadcastreceiver

 641        mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
642 mContext.registerReceiver(
643 mBroadcastReceiver, new IntentFilter(DELAYED_LOCK_PROFILE_ACTION));

然后创建Keyguard包里的KeyguardDisplayManager和KeyguardUpdateMonitor,还有锁屏模式工具类,获取AlarmManagerService,给KeyguardUpdateMonitor设置当前的用户。

 645        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
646
647 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
648
649 mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
650
651 mLockPatternUtils = new LockPatternUtils(mContext);
652 KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());

然后设置锁屏状态的变量并调用锁屏状态改变回调函数表,通知TrustManager

 654        // Assume keyguard is showing (unless it's disabled) until we know for sure...
655 setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled(
656 KeyguardUpdateMonitor.getCurrentUser()));
657 updateInputRestrictedLocked();
658 mTrustManager.reportKeyguardShowingChanged();

然后设置视图显示,通过SystemUIFactory获取StatusBarKeyguardViewManager,并把视图中介回调(mViewMediatorCallback)和锁屏模式工具(mLockPatternUtils)传入。

 660        mStatusBarKeyguardViewManager =
661 SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
662 mViewMediatorCallback, mLockPatternUtils);

SystemUIFactory里的code如下

 84    public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
85 ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
86 return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
87 }

StatusBarKeyguardViewManager是SystemUI中的一个状态栏组件,是锁屏的视图。

最后设置锁屏和解锁的声音的文件和音量,获取设备交互状态,加载锁屏隐藏的动画 com.android.internal.R.anim.lock_screen_behind_enter 。


  663        final ContentResolver cr = mContext.getContentResolver();
664
665 mDeviceInteractive = mPM.isInteractive();
 1 667        mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
668 String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND);
669 if (soundPath != null) {
670 mLockSoundId = mLockSounds.load(soundPath, 1);
671 }
672 if (soundPath == null || mLockSoundId == 0) {
673 Log.w(TAG, "failed to load lock sound from " + soundPath);
674 }
675 soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND);
676 if (soundPath != null) {
677 mUnlockSoundId = mLockSounds.load(soundPath, 1);
678 }
679 if (soundPath == null || mUnlockSoundId == 0) {
680 Log.w(TAG, "failed to load unlock sound from " + soundPath);
681 }
682 soundPath = Settings.Global.getString(cr, Settings.Global.TRUSTED_SOUND);
683 if (soundPath != null) {
684 mTrustedSoundId = mLockSounds.load(soundPath, 1);
685 }
686 if (soundPath == null || mTrustedSoundId == 0) {
687 Log.w(TAG, "failed to load trusted sound from " + soundPath);
688 }
689
690 int lockSoundDefaultAttenuation = mContext.getResources().getInteger(
691 com.android.internal.R.integer.config_lockSoundVolumeDb);
692 mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20);
693
694 mHideAnimation = AnimationUtils.loadAnimation(mContext,
695 com.android.internal.R.anim.lock_screen_behind_enter);
696 }

然后SystemUIApplication会调用  mServices[i].onBootCompleted 方法,会在KeyguardViemMediator的start方法后调用,来发出Intent ACTION_BOOT_COMPLETED,通知其他组件锁屏初始化完成

上边是锁屏的初始化过程,然后就是锁屏的加载过程。锁屏界面的加载有两个地方,第一个是第一次开机的时候;第二个是在灭屏后,这个时候会预加载锁屏界面加速亮屏显示。

第一次开机时,,

在按住Power键灭屏的时候,流程如下

可以看到,KeyguardViewMediator里有两个回调函数被涉及

第一个是onStartedGoingToSleep。这个方法里做锁屏的一些预处理,并发出锁屏通知给KeyguardUpdateMonitor(这里的状态太多了)

 723    /**
724 * Called to let us know the screen was turned off.
725 * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
726 * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
727 */
728 public void onStartedGoingToSleep(int why) {
729 if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
730 synchronized (this) {
731 mDeviceInteractive = false;
732 mGoingToSleep = true;
733
734 // Lock immediately based on setting if secure (user has a pin/pattern/password).
735 // This also "locks" the device when not secure to provide easy access to the
736 // camera while preventing unwanted input.
737 int currentUser = KeyguardUpdateMonitor.getCurrentUser();
738 final boolean lockImmediately =
739 mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
740 || !mLockPatternUtils.isSecure(currentUser);
741 long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
742 mLockLater = false;
743 if (mExitSecureCallback != null) {
744 if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
745 try {
746 mExitSecureCallback.onKeyguardExitResult(false);
747 } catch (RemoteException e) {
748 Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
749 }
750 mExitSecureCallback = null;
751 if (!mExternallyEnabled) {
752 hideLocked();
753 }
754 } else if (mShowing) {
755 mPendingReset = true;
756 } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
757 || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
758 doKeyguardLaterLocked(timeout);
759 mLockLater = true;
760 } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
761 mPendingLock = true;
762 }
763
764 if (mPendingLock) {
765 playSounds(true);
766 }
767 }
768 KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedGoingToSleep(why);
769 notifyStartedGoingToSleep();
770 }

第二个是onFinishedGoingToSleep,可以看到核心方法是doKeyguardLocked和doKeyguardForChildProfilesLocked

 772    public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) {
773 if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + why + ")");
774 synchronized (this) {
775 mDeviceInteractive = false;
776 mGoingToSleep = false;
777
778 resetKeyguardDonePendingLocked();
779 mHideAnimationRun = false;
780
781 notifyFinishedGoingToSleep();
782
783 if (cameraGestureTriggered) {
784 Log.i(TAG, "Camera gesture was triggered, preventing Keyguard locking.");
785
786 // Just to make sure, make sure the device is awake.
787 mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
788 "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
789 mPendingLock = false;
790 mPendingReset = false;
791 }
792
793 if (mPendingReset) {
794 resetStateLocked();
795 mPendingReset = false;
796 }
797
798 if (mPendingLock) {
799 doKeyguardLocked(null);
800 mPendingLock = false;
801 }
802
803 // We do not have timeout and power button instant lock setting for profile lock.
804 // So we use the personal setting if there is any. But if there is no device
805 // we need to make sure we lock it immediately when the screen is off.
806 if (!mLockLater && !cameraGestureTriggered) {
807 doKeyguardForChildProfilesLocked();
808 }
809
810 }
811 KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
812 }

doKeyguardLocked 会先判断要不要锁屏,如果需要,则调用方法showLocked

 1192    /**
1193 * Enable the keyguard if the settings are appropriate.
1194 */
1195 private void doKeyguardLocked(Bundle options) {
1196 // if another app is disabling us, don't show
1197 if (!mExternallyEnabled) {
1198 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
1199
1200 // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
1201 // for an occasional ugly flicker in this situation:
1202 // 1) receive a call with the screen on (no keyguard) or make a call
1203 // 2) screen times out
1204 // 3) user hits key to turn screen back on
1205 // instead, we reenable the keyguard when we know the screen is off and the call
1206 // ends (see the broadcast receiver below)
1207 // TODO: clean this up when we have better support at the window manager level
1208 // for apps that wish to be on top of the keyguard
1209 return;
1210 }
1211
1212 // if the keyguard is already showing, don't bother
1213 if (mStatusBarKeyguardViewManager.isShowing()) {
1214 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
1215 resetStateLocked();
1216 return;
1217 }
1218
1219 // In split system user mode, we never unlock system user.
1220 if (!mustNotUnlockCurrentUser()
1221 || !mUpdateMonitor.isDeviceProvisioned()) {
1222
1223 // if the setup wizard hasn't run yet, don't show
1224 final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
1225 final boolean absent = SubscriptionManager.isValidSubscriptionId(
1226 mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.ABSENT));
1227 final boolean disabled = SubscriptionManager.isValidSubscriptionId(
1228 mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED));
1229 final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
1230 || ((absent || disabled) && requireSim);
1231
1232 if (!lockedOrMissing && shouldWaitForProvisioning()) {
1233 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
1234 + " and the sim is not locked or missing");
1235 return;
1236 }
1237
1238 if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
1239 && !lockedOrMissing) {
1240 if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
1241 return;
1242 }
1243
1244 if (mLockPatternUtils.checkVoldPassword(KeyguardUpdateMonitor.getCurrentUser())) {
1245 if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted");
1246 // Without this, settings is not enabled until the lock screen first appears
1247 setShowingLocked(false);
1248 hideLocked();
1249 mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
1250 return;
1251 }
1252 }
1253
1254 if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
1255 showLocked(options);
1256 }

showLocked方法会发送SHOW消息

 1332    /**
1333 * Send message to keyguard telling it to show itself
1334 * @see #handleShow
1335 */
1336 private void showLocked(Bundle options) {
1337 Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock");
1338 if (DEBUG) Log.d(TAG, "showLocked");
1339 // ensure we stay awake until we are finished displaying the keyguard
1340 mShowKeyguardWakeLock.acquire();
1341 Message msg = mHandler.obtainMessage(SHOW, options);
1342 mHandler.sendMessage(msg);
1343 Trace.endSection();
1344 }

handleShow方法就会被调用,显示mStatusBarKeyguardViewManagermKeyguardDisplayManager的show方法。

 1625    /**
1626 * Handle message sent by {@link #showLocked}.
1627 * @see #SHOW
1628 */
1629 private void handleShow(Bundle options) {
1630 Trace.beginSection("KeyguardViewMediator#handleShow");
1631 final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
1632 if (mLockPatternUtils.isSecure(currentUser)) {
1633 mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
1634 }
1635 synchronized (KeyguardViewMediator.this) {
1636 if (!mSystemReady) {
1637 if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
1638 return;
1639 } else {
1640 if (DEBUG) Log.d(TAG, "handleShow");
1641 }
1642
1643 setShowingLocked(true);
1644 mStatusBarKeyguardViewManager.show(options);
1645 mHiding = false;
1646 mWakeAndUnlocking = false;
1647 resetKeyguardDonePendingLocked();
1648 mHideAnimationRun = false;
1649 updateActivityLockScreenState();
1650 adjustStatusBarLocked();
1651 userActivity();
1652
1653 mShowKeyguardWakeLock.release();
1654 }
1655 mKeyguardDisplayManager.show();
1656 Trace.endSection();
1657 }

还有一种情况是超时灭屏,与上边的按住Power键灭屏流程基本一样

基本流程分析完了,下面看看Keyguard里的具体每个类

先看KeyguardDisplayManager这个类,这个类是控制手机远程显示的。如果手机远程连接上了电视这样的设备,就先一个一个KeyguardPresentation对话框,是个时钟

     protected void updateDisplays(boolean showing) {
if (showing) {
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
boolean useDisplay = route != null
&& route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null; if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay());
mPresentation.dismiss();
mPresentation = null;
} if (mPresentation == null && presentationDisplay != null) {
if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay);
mPresentation = new KeyguardPresentation(mContext, presentationDisplay,
R.style.keyguard_presentation_theme);
mPresentation.setOnDismissListener(mOnDismissListener);
try {
mPresentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
Slog.w(TAG, "Invalid display:", ex);
mPresentation = null;
}
}
} else {
if (mPresentation != null) {
mPresentation.dismiss();
mPresentation = null;
}
}
}

Keyguard分析的更多相关文章

  1. SystemUI

    1.Status bars(状态栏) 2.Navigation bars(导航栏) 3.Notification(通知) 4.Keyguard(锁屏) 5.Quick settings(快速设置) 6 ...

  2. Android框架浅析之锁屏(Keyguard)机制原理

    最近终于成功的摆脱了FM收音机,迈向了新的模块:锁屏.状态栏.Launcher---姑且称之为“IDLE”小组,或许叫手机 美容小组,要是能施展下周星星同学的还我漂漂拳,岂不快哉. OK,闲话打住,咱 ...

  3. Android Framework------之Keyguard 简单分析

    前面对于MediaPlayer的系统研究,刚刚开始,由于其他原因现在要先暂停一下.这次要看的模块是android 4.2 系统中的Keyguard模块.在接触之后才发现,android4.2的keyg ...

  4. Android 7.1 屏幕旋转流程分析

    Android 7.1   屏幕旋转流程分析 一.概述 Android屏幕的旋转在framework主要涉及到三个类,结构如图 PhoneWindowManager:为屏幕的横竖屏转换的管理类. Wi ...

  5. Android startActivity原理分析(基于Android 8.1 AOSP)

    应用进程内 如何使用Intent做Activity的跳转 Intnet intent = new Intent(MainActivity.this,TestActivity.class); start ...

  6. Android 5.0 Phone初始化分析

    已经更新至个人blog:http://dxjia.cn/2015/07/android-5-0-phone-init-analysis/ persistent属性 要想了解phone的框架,首先需要了 ...

  7. Android之SystemUI载入流程和NavigationBar的分析

    Android之SystemUI载入流程和NavigationBar的分析 本篇仅仅分析SystemUI的载入过程和SystemUI的当中的一个模块StatusBar的小模块NavigationBar ...

  8. android分析windowManager、window、viewGroup之间关系(二)

    三.接上一节,分析windowManager中添加一个悬浮框的方式,首先看代码 WindowManager.LayoutParams params = new LayoutParams(); para ...

  9. Android的开机流程及对应源码位置分析

    1.系统引导bootloader 1)源码:bootable/bootloader/* 2)说明:加电后,CPU将先执行bootloader程序,此处有三种选择 a)开机按Camera+Power启动 ...

随机推荐

  1. list转datatable,SqlBulkCopy将DataTable中的数据批量插入数据库

    /// <summary> /// 将泛类型集合List类转换成DataTable /// </summary> /// <param name="list&q ...

  2. 安装fedora23后的一些杂项设置

    Boxes是创建虚拟机的技术 tweak: 拧, 捏; 微调 he gave the boy's ear a painful tweak. it's a small tweak over the ra ...

  3. 009-elasticsearch5.4.3【三】搜索概述-查询模型、分页、ES数据类型

    一.概述 1.查询模型 搜索API允许用户执行搜索查询并返回与查询匹配的搜索匹配.它可以跨一个或多个索引以及跨一种或多种类型执行.可以使用查询Java API提供查询.搜索请求的主体是使用Search ...

  4. FutureTask的用法以及两种常用的使用场景

    参考博客:https://blog.csdn.net/linchunquan/article/details/22382487 FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入 ...

  5. 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_08 Map集合_10_练习_计算一个字符串中每个字符出现的次数

  6. seaborn

    Seaborn是基于matplotlib的Python数据可视化库. 它提供了一个高级界面,用于绘制引人入胜且内容丰富的统计图形. 一  风格及调色盘 风格 1 sns.set()  模式格式 2 s ...

  7. clientdataset 读取excel 如果excel 文件不存在的时候 相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据。 这是一个bug 哈哈

    clientdataset 读取excel   如果excel 文件不存在的时候   相应的gird 会不显示数据, 鼠标掠过 gird 格子 才会显示数据.   这是一个bug 哈哈

  8. Eclipse java web项目 ,导入IntelliJ IDEA 完整操作!

    1.如图,这是一个ec项目,是一个ssh框架,搭建一个后台, 我们在idea 新建一个项目:new - project from Existing sources... 要是不放心,你可以做一个文件备 ...

  9. 基于PyQt5的Python-Gui开发

    环境搭建 电脑环境 win10 64位系统 Python3.5安装 从Python官网下载python3.5安装文件,选择windows 64位版本python-3.6.5-amd64.exe.双击安 ...

  10. python+selenium文本框对象以及按钮对象操作

    文本框对象 from selenium import webdriverfrom time import sleep driver = webdriver.Firefox() # 指定和打开浏览器ur ...