首页 文章 精选 留言 我的

精选列表

搜索[手动],共10002篇文章
优秀的个人博客,低调大师

速来,镜像库已支持手动推送啦!!!

建木Hub镜像私有库已上线,支持个人私有和组织内共享,具体使用方式如下: 一、打开镜像库 访问镜像库:https://image.jianmuhub.com,点击立即体验。 若尚未开通,登录后勾选协议,点击立即开通。 二、创建镜像仓库 填写仓库信息 归属:可以选择个人个人或组织(组织可以在顶部导航【 组织协作】中创建)。归属代表镜像仓库属于谁。 唯一标识:镜像仓库名称由 归属唯一标识/仓库唯一标识 组成;建议用镜像相关内容命名,方便识别。 描述(选填):详细的描述可以让大家对这个镜像有更明确的了解。 类型:默认公开,其他用户无需登录即可拉取该公开镜像仓库下的所有镜像;除了公开还支持私有(归属为个人)和组织成员可见(归属为组织)。 三、拉取/推送镜像 Docker操作步骤: 登录镜像库 docker login -u {个人唯一标识} -p {访问令牌} docker.jianmuhub.com 构建镜像 docker build -f Dockerfile -t docker.jianmuhub.com/{归属唯一标识}/{仓库唯一标识}:{镜像标签} . 拉取镜像 docker pull docker.jianmuhub.com/{归属唯一标识}/{仓库唯一标识}:{镜像标签} 推送镜像 docker push docker.jianmuhub.com/{归属唯一标识}/{仓库唯一标识}:{镜像标签} 说明: 获取个人唯一标识:https://settings.jianmuhub.com/account; 获取访问令牌:https://settings.jianmuhub.com/token; 归属唯一标识:分为个人或组织,若创建个人私有库为 个人唯一标识,组织成员可见库为 组织唯一标识; 仓库唯一标识:创建镜像仓库时指定; 镜像标签:构建镜像时指定。 建木的小伙伴们,点击链接: https://image.jianmuhub.com,快来体验一下吧!!!

优秀的个人博客,低调大师

JS实现手动和自动轮播效果

开门见山,今天要实现的轮播效果如下图所示:(轮播自动播放;点击左右按钮实现上一张和下一张的功能;鼠标移入轮播暂停;鼠标移出轮播自动播放;小圆点随着图片的变化而变化,点击哪个小圆点回到哪张图片;底部显示第几张图片) 一、献上html及css html: <div class="pic"> <!--上一页--> <a id="prev" href="javascript:;"></a> <!--轮播图--> <img id="img" src="img/img1.jpg" alt=""> <!--小圆点--> <div class="circle"> <span class="active"></span> </div> <!--下一页--> <a id="next" href="javascript:;"></a> </div> <p id="txt">这是第 <span>1</span> 张图片</p> css: *{ margin: 0; padding: 0; }a{ text-decoration: none; }.wrap{ width: 1000px; height: 800px; background: url(img/bg.jpg) no-repeat; position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); background-size: 100% 100%; } prev,#next{ position: absolute; width: 25px; height: 45px; background: url(img/ar.png) no-repeat; top: 155px; } prev{ left: 13px; } next{ transform: rotate(180deg); right: 13px; }.pic{ width: 536px; height: 356px; position: absolute; top: 170px; left: 50%; transform: translateX(-50%); }img{ vertical-align: top; width: 536px; height: 356px; } txt{ width: 536px; height: 71px; position: absolute; left: 50%; bottom: 185px; text-align: center; font: 20px/71px "微软雅黑"; color: #666; transform: translateX(-50%); }.circle{ text-align: center; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); }.circle span{ border: 1px solid #fff; width: 5px; height: 5px; border-radius: 50%; display: inline-block; margin: 0 5px; cursor: pointer; }.circle span.active{ background-color: #fff; } 二,javaScript的实现 1,获取元素 let pic = document.querySelector('.pic');let prev = document.querySelector('#prev');let next = document.querySelector('#next');let txt = document.querySelector('#txt span');//轮播图数组let arr = ['img/img1.jpg','img/abc.jpg','img/img3.jpg','img/img4.jpg','img/img5.jpg'];let num = 0 ;let circle = document.querySelector('.circle');let img = document.querySelector('#img');let timer = null; 2,渲染并获取小圆点 //渲染轮播图上的小圆点for(let i = 0;i circle.innerHTML+=`<span></span>` }let circleAll = document.querySelectorAll('.circle span'); 3,轮播时需要调用的函数 //轮播函数let loop = (addNum,flag)=>{ num = num+addNum; if(flag){ if(num == arr.length){ num = 0; } }else{ if(num < 0){ num = arr.length-1; } } for(let i = 0;i<circleAll.length;i++){ circleAll[i].classList.remove('active'); } circleAll[num].classList.add('active'); img.src = arr[num]; txt.innerHTML= num+1; }; 4,进入页面时自动播放轮播 //定时器let auto = ()=>{ timer = setInterval(()=>{ let addNum =1; let flag = true; loop(addNum,flag); },1000); };//进入页面时自动轮播auto(); 5,鼠标的移入移出 //鼠标移入清除定时器pic.onmouseover=function () { clearInterval(timer); };//鼠标移出启动定时器pic.onmouseout =function () { auto(); }; 6,实现上一张及下一张功能 //下一张next.onclick = function () { let addNum = 1; let flag = true; loop(addNum,flag); };//上一张prev.onclick = function () { let addNum = -1; let flag = false; loop(addNum,flag); }; 7,实现点击小圆点切换图片的功能 //点击圆点切换图片circleAll.forEach((item,index)=>{ item.onclick=function () { img.src = arr[index]; txt.innerHTML= index+1; for(let i = 0;i<circleAll.length;i++){ circleAll[i].classList.remove('active'); } circleAll[index].classList.add('active'); //num赋值为index,使鼠标移出后图片播放从当前的图片开始 继续自动轮播 num = index; } }) 整个轮播的功能就实现,欢迎大家交流

优秀的个人博客,低调大师

Java并发编程 -- 手动实现可重入Lock

Lock就像synchronized块一样是一个线程同步机制。 然而,Lock定比synchronized更灵活、更复杂。 Lock和synchronized块 的区别 同步块不保证等待输入它的线程被授予访问权限的顺序。 不能将任何参数传递给同步块的条目。 同步块必须完全包含在单个方法中。 一个Lock可以在不同的方法中调用lock()和unlock()。 简单例子 Lock lock = new ReentrantLock(); lock.lock(); //要保证线程安全的代码 lock.unlock(); 其中,你应该能够猜到,lock() 方法是加锁,unlock()方法是解锁。 Lock接口含有的方法 lock() lockInterruptibly() tryLock() tryLock(long timeout, TimeUnit timeUnit) unlock() lock()方法锁定Lock实例。 如果锁定实例已被锁定,则线程调用锁定()将被锁定,直到解锁锁定。 lockInterruptibly()方法锁定Lock,除非调用该方法的线程已被中断。如果一个线程被阻塞,等待通过此方法锁定Lock,该线程将被中断,并退出此方法调用。(获取锁的时候可以被中断) tryLock()方法立即尝试锁定Lock实例。 如果锁定成功则返回true;如果Lock已经被锁定,则返回false。 这个方法永远不会阻塞 tryLock(long timeout,TimeUnit timeUnit)的工作方式与tryLock()方法相似,只是它对超时时间有所规定。 unlock()方法解锁Lock实例。 通常,Lock实现将只允许已锁定Lock的线程调用此方法。 调用此方法的其他线程可能会导致未经检查的异常(RuntimeException)。 ReentrantLock实例 ReentrantLock 可重入锁,是Lock的一个子类。我们这里来使用它实现线程安全编程。 package com.lock; import com.thread.security.Task; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 重入锁 * Created by Fant.J. * 2018/3/6 20:09 */ public class ReentrantLockTest { public int value = 0; //实例化重入锁锁 Lock lock = new ReentrantLock(); public int getValue() { //加锁 lock.lock(); int a = value++; //消除锁 lock.unlock(); return a; } public static void main(String[] args) { ReentrantLockTest task = new ReentrantLockTest(); new Thread(){ @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " " + task.getValue()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread(){ @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " " + task.getValue()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } 手写自己的Lock实现类 如果有特殊业务需求,我们也可以重写Lock接口,来打造一个自己的lock锁。 package com.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; /** * Created by Fant.J. * 2018/3/6 20:12 */ public class MyLock implements Lock { //声明一个判断锁的布尔值 private boolean isLocked = false; /** * 必须声明 synchronized 原自行操作,不然jvm不会识别是哪个线程的wait方法,notify也一样 */ @Override public synchronized void lock() { while (isLocked){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } isLocked = true; } @Override public synchronized void unlock() { isLocked = false; notify(); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } } 然后我们做测试 package com.lock; /** * Created by Fant.J. * 2018/3/6 20:24 */ public class MyLockTest { public int value = 0; MyLock myLock = new MyLock(); public int getValue(){ myLock.lock(); value++; myLock.unlock(); return value; } public static void main(String[] args) { MyLockTest task = new MyLockTest(); new Thread(){ @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " " + task.getValue()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread(){ @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " " + task.getValue()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } 结果没有出现线程安全问题,这里不做截图了,自己可以试试。但是我们写的方法还有一定的问题,就是MyLock这个类不支持 可重入锁,意思就是如果有两个锁嵌套,如果相同的线程先调用a方法,再调用带锁的b方法,则就会进入自旋锁。 测试方法源码 package com.lock; /** * Created by Fant.J. * 2018/3/6 20:24 */ public class MyLockTest2 { public int value = 0; MyLock myLock = new MyLock(); public void a(){ myLock.lock(); System.out.println("a"); b(); myLock.unlock(); } public void b(){ myLock.lock(); System.out.println("b"); myLock.unlock(); } public static void main(String[] args) { MyLockTest2 task = new MyLockTest2(); new Thread(){ @Override public void run() { task.a(); } }.start(); new Thread(){ @Override public void run() { task.a(); } }.start(); } } 执行该方法后,我们会发现,线程停止在打印出"a"后,一直在等待。这就是因为该锁不是可重入锁。 可重入锁的设计 我在这里只贴和上面代码不同的部分。 public class MyLock implements Lock { //声明一个判断锁的布尔值 private boolean isLocked = false; Thread lockBy = null; int lockCount = 0; @Override public synchronized void lock() { Thread currentThread = Thread.currentThread(); //获取到当前线程 while (isLocked && currentThread != lockBy){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } isLocked = true; lockBy = currentThread; //将currentThread线程指向 lockBy线程 lockCount++;//计数器自增 } @Override public synchronized void unlock() { if (lockBy == Thread.currentThread()){ lockCount--; if (lockCount ==0 ){ notify(); isLocked = false; } } } } 第一个线程执行a()方法,得到了锁,使lockedBy等于当前线程,也就是说,执行的这个方法的线程获得了这个锁,执行add()方法时,同样要先获得锁,因不满足while循环的条件,也就是不等待,继续进行,将此时的lockedCount变量,也就是当前获得锁的数量加一,当释放了所有的锁,才执行notify()。如果在执行这个方法时,有第二个线程想要执行这个方法,因为lockedBy不等于第二个线程,导致这个线程进入了循环,也就是等待,不断执行wait()方法。只有当第一个线程释放了所有的锁,执行了notify()方法,第二个线程才得以跳出循环,继续执行。

优秀的个人博客,低调大师

[Android Memory] 手动回收ImageVIew的图片资源

ImageView默认是不进行图片资源的回收的,需要我们自己在activity或者fragment中进行回收: public static void releaseImageViewResouce(ImageView imageView) { if (imageView == null) return; Drawable drawable = imageView.getDrawable(); if (drawable != null && drawable instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; Bitmap bitmap = bitmapDrawable.getBitmap(); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } } 分类: Android Memory 本文转自demoblog博客园博客,原文链接http://www.cnblogs.com/0616--ataozhijia/p/3954402.html如需转载请自行联系原作者 demoblog

优秀的个人博客,低调大师

Android 手动显示和隐藏软键盘

1、方法一(如果输入法在窗口上已经显示,则隐藏,反之则显示) [java] view plain copyprint? InputMethodManagerimm=(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS); 2、方法二(view为接受软键盘输入的视图,SHOW_FORCED表示强制显示) [java] view plain copyprint? InputMethodManagerimm=(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(view,InputMethodManager.SHOW_FORCED); [java] view plain copyprint? imm.hideSoftInputFromWindow(view.getWindowToken(),0);//强制隐藏键盘 3、调用隐藏系统默认的输入法 [java] view plain copyprint? ((InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(WidgetSearchActivity.this.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);(WidgetSearchActivity是当前的Activity) 4、获取输入法打开的状态 [java] view plain copyprint? InputMethodManagerimm=(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); booleanisOpen=imm.isActive();//isOpen若返回true,则表示输入法打开 InputMethodManager类 Android中软键盘的管理主要是通过InputMethodManager类来完成的。 InputMethodManager对象的获取方法如下。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 1 1 获取到InputMethodManager对象后就可以通过调用其成员方法来对软键盘进行操作。不过在使用InputMethodManager对象前通常都需要判断其是否为null,避免运行时异常。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { ... } 1 2 3 4 1 2 3 4 显示软键盘 Android中可以通过InputMethodManager的showSoftInput方法来显示软键盘。 InputMethodManager的showSoftInput方法原型为 public boolean showSoftInput(View view, int flags); 1 1 它有两个参数,第一个参数表示当前要接收软键盘输入的View,第二个参数是软键盘显示时的控制参数。 使用InputMethodManager的showSoftInput方法来显示软键盘有如下注意事项。 第一个参数中view必须是EditText,或者EditText的子类,如果是其他类型的View,如Button,TextView等,showSoftInput()方法不起作用。 第一个参数中的view必须是可以获取焦点的(即view.isFocusable()返回true),如果不能获取焦点,则showSoftInput()方法不起作用。EditText默认是可获取焦点的,所以此条件一般都可以满足。如果不满足,可以通过view.setFocusable(true);将其设置为可获取焦点的View。 第一个参数中的view当前必须已经获取到焦点(即view.isFocused()返回true),如果当前焦点不在该view上,则showSoftInput()方法不起作用。虽然EditText默认是可获取焦点的,但由于一个布局中可能会有多个控件可以获取焦点,焦点位置不一定会恰好在EditText上,所以此条件不一定满足。为了让showSoftInput()可以起作用,必须在之前showSoftInput()前先通过view.requestFocus();获取焦点。然后再执行showSoftInput()。 第一个参数中的view必须是可见的,即view.getVisibility()等于View.VISIBLE,如果view是不可见的,无论view.getVisibility()是View.INVISIBLE还是View.GONE,showSoftInput()方法都不起作用。如果view是不可见的,可以先通过view.setVisibility(View.VISIBLE)将其设置为可见。 当前布局必须已经完成加载,如果还未绘制完成,则showSoftInput()方法不起作用。特别的,在Activity的onCreate()中执行showSoftInput()是不起作用的。如果要再布局文件加载后就显示软键盘,可以通过postDelayed的方式来延迟执行showSoftInput()。延迟时间不能太短,一般要在50ms以上。代码示例如下。 getWindow().getDecorView().postDelayed(new Runnable() { @Override public void run() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { view.requestFocus(); imm.showSoftInput(view, 0); } } }, 100); 1 2 9 10 InputMethodManager类中提供了另外一个显示软键盘的方法showSoftInputFromInputMethod,和showSoftInput不同的是,第一个参数传入的不是一个View对象,而是一个View对象的windowToken(对一个view对象可以通过getWindowToken()的方法获取到其windowToken)。不过实际情况是,即使上述所有条件都满足,通过showSoftInput(view,flag)已经可以显示软键盘,但是通过showSoftInputFromInputMethod(view.getWindowToken(), flag)仍然无法显示软键盘。 参照Android官方文档,第二个参数提供一些额外的操作标记(additional operating flags),可以取0或者SHOW_IMPLICIT,0表示什么含义没有说明,SHOW_IMPLICIT表示本次显示软键盘的请求不是来自用户的直接请求,而是隐式的请求。且不说一会用数字,一会用常量名,光SHOW_IMPLICIT的说明恐怕除了这个接口的开发人员,没人能看懂这句话是什么意思。实际上这个参数还可以取第三个值SHOW_FORCED,直接在文档中被遗忘了。经过试验,这个参数的取值对软键盘的显示没有任何影响,无论取哪一个值软键盘都能够正常显示(即使随便输入一个整数,软键盘都可以显示)。实际上这个参数影响的并不是软键盘的显示,而是软键盘的隐藏,会在后文中讲到。 隐藏软键盘 Android在InputMethodManager类中并没有提供一个和showSoftInput对应的hideSoftInput方法来隐藏软键盘,而是提供了一个showSoftInputFromInputMethod相对应的hideSoftInputFromWindow方法来隐藏软键盘。幸运的是,虽然showSoftInputFromInputMethod不能正常显示软键盘,hideSoftInputFromWindow倒是能够隐藏软键盘。 InputMethodManager的hideSoftInputFromWindow方法原型为 public boolean hideSoftInputFromWindow(IBinder windowToken, int flags); 1 它同样有两个参数,第一个参数是一个View的windowToken。第二个参数是软键盘隐藏时的控制参数。 使用InputMethodManager的hideSoftInputFromWindow方法来隐藏软键盘有如下注意事项。 第一个参数并不是指定一个View,而是一个View的windowToken。对一个view可以通过getWindowToken()的方法获取到其windowToken。 按照官方文档,第一个参数中的windowToken应当是之前请求显示软键盘的View的windowToken,也就是执行showSoftInput()时第一个参数中的View的windowToken。但是实际情况是,用任意一个当前布局中的已经加载的View的windowToken都可以隐藏软键盘,哪怕这个View被设置为INVISIBLE或GONE。因此,如果不知道之前是谁请求显示的软键盘,可以随便传入一个当前布局中存在的View的windowToken。特别的,可以传入一个Activity的顶层View的windowToken,即getWindow().getDecorView().getWindowToken(),来隐藏当前Activity中显示的软键盘,而不用管之前调用showSoftInput()的究竟是哪个View。示例代码如下。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0); } 这里还要注意的是,可以随便传入一个当前布局中存在的View的windowToken,并不代表可以传入任意一个View的windowToken,如下代码不能实现隐藏软键盘。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(new View(this).getWindowToken(), 0); } 1 2 3 4 对新创建的view,必须将其加入到当前布局中后才可以用来隐藏软键盘。如下代码可以实现隐藏软键盘。当然,这里只是为了演示这个原理,实际使用时没有必要绕这样一个弯。 InputMethodManager imm1 = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm1 != null) { View v = new View(this); ViewGroup g1 = (ViewGroup)getWindow().getDecorView(); ViewGroup g2 = (ViewGroup)g1.getChildAt(0); g2.addView(v); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } 1 2 3 4 5 6 参照Android官方文档,第二个参数同样是提供了一些额外的操作标记(additional operating flags),可以取0或者HIDE_IMPLICIT_ONLY,0表示什么含义同样是没有说明,HIDE_IMPLICIT_ONLY表示当前的软键盘应当只在其不是被用户显式的显示的时候才隐藏(the soft input window should only be hidden if it was not explicitly shown by the user)。这句话虽然很拗口,但总算是有点有用的信息了。在显示软键盘时,可以使用的flag有0,SHOW_IMPLICIT和SHOW_FORCED,参照之前的描述,当显示软键盘指定flag为SHOW_IMPLICIT时表示隐式的显示,也就是这里非用户显式的显示,再参照这里的描述,如果隐藏软键盘时使用的flag为HIDE_IMPLICIT_ONLY,那么软键盘只有在非用户显式的显示的时候才隐藏,这意味着如果隐藏软键盘时使用的flag为HIDE_IMPLICIT_ONLY,那么只有当显示软键盘时指定的flag为SHOW_IMPLICIT时,软键盘才会隐藏,如果显示软键盘时指定的flag不是SHOW_IMPLICIT,而是0或者SHOW_FORCED,那么软键盘就不会隐藏。为了更完整的看出不同flag对隐藏软键盘的影响(再声明下,无论是显示软键盘时指定的flag,还是隐藏软键盘时指定的flag都只对隐藏软键盘有影响,对显示软键盘无影响), 分别在调用showSoftInput()时使用三个不同的标记,以及在调用hideSoftInputFromWindow()是使用三个不同的标记(隐藏软键盘时同样还有一个HIDE_NOT_ALWAYS标记,在官方文档中被遗忘了),对是否能够隐藏软键盘进行测试,测试结果如下。T表示可以隐藏,F表示不能隐藏。 参数 0 SHOW_IMPLICIT SHOW_FORCED 0 T T T HIDE_IMPLICIT_ONLY F T F HIDE_NOT_ALWAYS T T F 可以看到,在隐藏软键盘时使用HIDE_IMPLICIT_ONLY标记,确实只有在显示软键盘时使用SHOW_IMPLICIT时才会隐藏。此外,当隐藏软键盘时使用0作为标记,无论showSoftInput()时使用的是哪个参数,都可以隐藏软键盘。 切换软键盘状态 在InputMethodManager类中还提供了一个toggleSoftInput的方法来在显示和隐藏软键盘之间切换,也就是说,如果当前软键盘是隐藏的,那么执行toggleSoftInput方法时会显示软键盘,如果当前软键盘是显示的,那么执行toggleSoftInput方法时会隐藏软键盘。 InputMethodManager的toggleSoftInput方法原型为 public void toggleSoftInput(int showFlags, int hideFlags); 1 它同样有两个参数,第一个参数是显示软键盘时使用的标记,第二个参数是隐藏软键盘时使用的标记。 使用InputMethodManager的toggleSoftInput方法来切换软键盘显示状态有如下注意事项。 showFlags为显示软键盘时使用的标记,只有当前软键盘处于隐藏状态时才会使用。hideFlags是隐藏软键盘时使用的标记,只有当前软键盘处于显示状态时才会使用。 showFlags和hideFlags取值范围,以及不同取值的影响和上文分析的一样。即showFlags和hideFlags都只影响软键盘的隐藏,不影响软键盘的显示。不同取值对软键盘隐藏的影响参见上文中的表格。 当显示软键盘时,并不要求当前界面布局中有一个已经获取焦点的EditText,即使当前布局是完全空白的,一个View也没有(除了最外层的Layout),toggleSoftInput也能够显示软键盘。不过如果没有一个已经获取焦点的EditText,那么软键盘中的按键输入都是无效的。就像这样。 显示软键盘时,要求当前布局必须已经加载完成,如果还未绘制完成,则toggleSoftInput()方法不起作用。这点和之前调用showSoftInput()显示软键盘时描述的第5点要求是一样的。特别的,在Activity的onCreate()中执行toggleSoftInput()必须通过postDelayed的方式来延迟执行。延迟时间一般要在50ms以上。 当隐藏软键盘时,不需要知道之前触发软键盘显示的View是哪一个或获取当前布局中任何一个View的windowToken,只要hideFlags能够隐藏就可以。由于hideFlags为0时总是能够隐藏的,因此,使用toggleSoftInput(0, 0)应当是最方便的无条件隐藏软键盘的方法,前提是知道当前软键盘确实是处于显示状态。不过遗憾的是Android没有任何API可以直接获取到软键盘的状态是显示还是隐藏的。 写到这里不得不吐槽下,Android在软键盘管理上,简直就是一坨屎,不仅API参数含义不明,文档混乱,很多API根本就没有效果。这点在下一篇关于获取软键盘状态和软键盘高度的文章中会有更深的体会。 部分源码解读 以上的分析都是从现象上入手,要想知道原因,还是要分析Android源码。 显示和隐藏软键盘的源码大都在InputMethodManagerService.Java文件里。这个文件有3000多行代码,还有很多是其他类的交互,我也没有仔细研究,这里只是将其在一些片段贴出来,做一个大概的分析。 showSoftInput showSoftInput会进入到showCurrentInputLocked()方法中,这里有这样一段。 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { mShowExplicitlyRequested = true; } if ((flags&InputMethodManager.SHOW_FORCED) != 0) { mShowExplicitlyRequested = true; mShowForced = true; } 1 2 3 4 5 6 7 可以看到这里只是将showSoftInput的第二个参数flag和SHOW_IMPLICIT,SHOW_FORCED相与,根据结果对mShowExplicitlyRequested和mShowForced赋值。在showCurrentInputLocked()方法没有其他用到flags的地方,也没有用到mShowExplicitlyRequested和mShowForced。这就解释了,执行showSoftInput时传入任意的flags都不会影响软键盘的显示。 hideSoftInputFromWindow hideSoftInputFromWindow会进入hideCurrentInputLocked()方法,在hideCurrentInputLocked()方法的开头有这样一段。 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { return false; } if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { return false; } 1 2 3 4 5 6 这里的判断用到了隐藏软键盘时使用的flags,以及mShowExplicitlyRequested和mShowForced的值,如果不满足条件就直接返回了。可以看到当flags为HIDE_IMPLICIT_ONLY时,如果mShowExplicitlyRequested和mShowForced任意一个为true,都会返回false。当flags为HIDE_NOT_ALWAYS时,如果mShowForced为true,也会返回false,当flags为0时,两个if条件都不满足。这就解释了显示软键盘时使用的flags影响的是后面隐藏软键盘是否成功,以及隐藏软键盘时使用0作为flags总是能够隐藏软键盘。 结论 显示软键盘最可靠的方法如下。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { view.requestFocus(); imm.showSoftInput(view, 0); } 1 2 3 4 5 view必须是VISIBLE的EditText,如果不是VISIBLE的,需要先将其设置为VISIBLE。 当前界面必须已经加载完成,不能直接在Activity的onCreate(),onResume(),onAttachedToWindow()中使用,可以在这些方法中通过postDelayed的方式来延迟执行showSoftInput()。 隐藏软键盘最方便的方法如下。 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } 1 2 3 4 view可以当前布局中已经存在的任何一个View,如果找不到可以用getWindow().getDecorView()。 本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/6861128.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

请求分级与限流——手动打造服务网关

转载本文需注明出处:微信公众号EAWorld,违者必究。 项目背景 雪崩一词指的是山地积雪由于底部溶解等原因而突然大块塌落的现象,具有很强的破坏力。在系统架构中提到的雪崩,就是由于一台服务器或者一台服务器中的某个模块发生故障进而引起连锁反应,最后导致大量的服务器或者软件模块无法正常工作,这种现象也较做“急剧变化”现象。 在某通信集团统一流程平台项目上线初期就发生过几次服务雪崩的事故,由于业务系统接入持续增多,业务系统的查询请求数量大大高于了前期的设计。当集群中的一台节点宕机下线后,请求压力迅速传导给了整个集群,从而引发了集群整体宕机。 解决方案 为了保障平台整体能稳定运行,在重新对服务器负载进行估算、增加集群冗余后,项目组增加了在部署架构、压力负载分流方面考虑,以持续提升平台性能。 我们对现有平台集群的逻辑架构进行了调整,将业务访问请求按系统ID分流到各子集群,由各集群分开处理,通过对业务请求的细分,达到子集群隔离,提升系统的稳定性与可扩展性。 具体到实施层面,项目组预备在负载均衡上增加服务网关的功能,使得nginx集群不再单纯的做反向代理,而是扩展为服务网关。 服务网关 服务网关基于openresty开发,OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。 我们在其基础上实现了以下功能: 流量限制:设立限流名单制度,对于非VIP且请求量大的用户进行限流。对名单中用户的请求进行计数,限制用户每分钟的请求次数,避免重复调用或短期内大量无效请求。 请求分域:根据请求来源的租户实现请求分域,将不同的租户请求分发到该租户的专用集群。 健康检查:提供一个简单的查询页面,可以查询当前集群节点的健康状况。 部署架构调整 在改造网关之前,我们首先要对集群的物理架构进行调整。通过对统一平台的业务量以及调用量的统计,以及对业务请求的监控我们发现,无论从业务数量以及调用量来看,业务压力主要来自于其中的两个业务系统。 于是我们对现有架构进行了调整,将负载均衡随机分发请求到集群,调整为按系统分发,将一个大集群切割成若干个小集群,按请求所在的系统将请求分发到各自响应的集群中。 请求分域 首先我们已经通过调整集群的部署,将集群进行了物理分割,接下来我们只需要将相关系统的请求转发到对应的集群中即可。 首先通过获取每个请求的请求头,来获得该请求的所属系统、所属用户等关键信息。 map $host $fmt_localtime { default '';}map $host $bpm_method { default '';}map $host $bpm_tenantid { default '';}map $host $bpm_province { default '';}map $host $bpm_userid { default '';}map $host $bpm_provinceFlag { default '';}map$host$RequestId{default'';} (左右滑动查看全部代码) 然后我们在脚本中定义好每个系统的节点地址 #报账upstream bpm_cluster_rbs {server 10.24.20.45:8080;server 10.24.20.46:8080;……server 10.24.20.52:8080; }#合同upstream bpm_cluster_cms {server 10.24.20.53:8080;……server 10.24.20.60:8080;}#其他upstream bpm_cluster_other {server 10.24.20.10:8080;……server 10.24.20.16:8080;} (左右滑动查看全部代码) 最后当收到请求时,将对应请求转发到对应的upstream就可以了 functionforwardUpstream(tenantid)iftenantid=="CMS"--对于合同系统而且是查询待办相关操作thenngx.var.upstream="bpm_cluster_cms"elseiftenantid=="RBS"--对于报账系统而且是查询待办相关操作thenngx.var.upstream="bpm_cluster_rbs"elsengx.var.upstream="bpm_cluster_other"enden (左右滑动查看全部代码) 限流 lua-resty-limit-traffic是一个openresty中用于限制和控制流量的Lua库,使用这个库可以方便的对用户、IP进行限流 https://github.com/openresty/lua-resty-limit-traffic (左右滑动查看全部代码) lua-resty-limit-traffic模块限流分为两种: 第一种限制某用户每分钟只能调用120次(允许在时间段开始的时候一次性放过120个请求) local limit_count = require "resty.limit.count"local lim, err = limit_count.new("my_limit_count_store", 120, 60) (左右滑动查看全部代码) 第二种限制每分钟处理120个请求(平滑处理,每秒钟只放过两个请求) local limit_req = require "resty.limit.req"local lim, err = limit_req.new("my_limit_req_store", 2, 0) (左右滑动查看全部代码) 实战中,我们的策略是将流量大的用户加入到限流名单中,名单内的用户会在redis中维护一份配置表,包括 在请求进来后,我们先从请求头中获取当前请求的用户 map $host $bpm_userid { default '';} (左右滑动查看全部代码) 然后判断该用户是否在限流名单内,如果确定为限流用户则读取该用户的配置信息 local cache_ngx = ngx.shared.my_ngx_redis_cachelocal user_conf = cache_ngx:get(userid)local redis_josn = cjson.decode(user_conf)local maxReq = redis_josn['maxReq']local nextReqTime = redis_josn['nextReqTime'] (左右滑动查看全部代码) 根据配置调用限流方法,设置某用户每分钟只能调用XX次请求(允许一次性放过) local limit_count = require "resty.limit.count"local maxReq = redis_josn['maxReq']local nextReqTime = redis_josn['nextReqTime']local lim, err = limit_count.new("my_limit_count_store", maxReq, nextReqTime)if not lim then ngx.log(ngx.ERR, "failed to instantiate a resty.limit.count object: ", err) return ngx.exit(500)endlocal delay, err = lim:incoming(key, true)-- 如果发生错误则返回500,如果请求数超过了 count 限制则返回403if not delay then if err == "rejected" then return ngx.exit(403) end ngx.log(ngx.ERR, "failed to limit count:", err) return ngx.exit(500)end (左右滑动查看全部代码) 健康检查 upstream.healthcheck本质上是个定时器,它会定期发送指定的http请求并解析响应码,去探测upstream中每个peer的存活状态,再结合历史请求记录来判断并标记其状态。 模块的源码见下面的页面: https://github.com/openresty/lua-resty-upstream-healthcheck (左右滑动查看全部代码) 实战中我们的代码如下: local ok, err = hc.spawn_checker{ shm = "healthcheck", -- defined by "lua_shared_dict" upstream = "bpm_cluster_rbs", -- defined by "upstream" type = "http", http_req = "GET /default/engineState.jsp HTTP/1.0\r\nHost: bpm_cluster_rbs\r\n\r\n", -- raw HTTP request for checking interval = 2000, -- 每两秒检查一次 timeout = 1000, -- 1 sec is the timeout for network operations fall = 3, -- # 连续3次失败才认定为down rise = 2, -- # 对down状态的节点,连续2次成功认定为UP valid_statuses = {200, 302}, -- 状态正常的code concurrency = 10, -- 检查线程数} (左右滑动查看全部代码) 关于作者:李云涛,普元高级开发工程师,擅长性能调优、微服务、容器、消息队列等技术。先后参与邮储银行Java开发平台、中移总ERP流程平台、中煤信息技术中台等平台的的架构设计与平台研发工作。 关于EAWorld:使能数字转型,共创数智未来,长按二维码关注! 本文分享自微信公众号 - EAWorld(eaworld)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

基于CentOS 6.8的OpenStack手动安装-环境配置

1 环境 1.1 主机网络 1.1.1 系统的架构 1)节点分为控制节点、计算节点、块存储节点、对象存储节点 2)所有管理网络(10.168.0.0/24)和虚拟网络都使用私网地址连接共有网络(互联网203.0.113.x/8) 3)物理网络与虚拟网络(非10.168.0.0/24段)应当处于不同的网段 4)本架构NAT中的网关地址为10.168.0.1 5)物理节点使用NAT保持时间同步 1.1.2 部署信息 1)控制节点 hostname=rodm.cmdschool.org ip address=10.168.0.125 OS=CentOS 6.8 2)计算节点 hostname=rod0[1-2].cmdschool.org ip address=10.168.0.[126-127] OS=CentOS 6.8 1.1.3 名称服务(可选) 1 vim /etc/hosts 输入如下信息: 1 2 3 10.168.0.125rodm.cmdschool.org 10.168.0.126rod01.cmdschool.org 10.168.0.127rod02.cmdschool.org 1.2 安全 1.2.1 安装涉及如下密码 Password name Description Database password (no variable used) Root password for the database ADMIN_PASS Password of user admin CEILOMETER_DBPASS Database password for the Telemetry service CEILOMETER_PASS Password of Telemetry service user ceilometer CINDER_DBPASS Database password for the Block Storage service CINDER_PASS Password of Block Storage service user cinder DASH_DBPASS Database password for the dashboard DEMO_PASS Password of user demo GLANCE_DBPASS Database password for Image service GLANCE_PASS Password of Image service user glance HEAT_DBPASS Database password for the Orchestration service HEAT_DOMAIN_PASS Password of Orchestration domain HEAT_PASS Password of Orchestration service user heat KEYSTONE_DBPASS Database password of Identity service NEUTRON_DBPASS Database password for the Networking service NEUTRON_PASS Password of Networking service user neutron NOVA_DBPASS Database password for Compute service NOVA_PASS Password of Compute service user nova RABBIT_PASS Password of user guest of RabbitMQ SWIFT_PASS Password of Object Storage service user swift 1.2.2 使用随机密码部署 1 opensslrand-hex10 注:以上命令可生成2.1表所需的随机密码 1.3 网络时间协议 1.3.1 控制节点 1)yum安装chrony服务 1 yum install -ychrony 2)配置chrony服务 1 vim /etc/chrony .conf 修改同步ntp地址 1 serverntp10.168.0.xiburst 注:内网的NTP服务器或外网NTP服务器(其实默认亦可) 3)允许非控制节点访问ntp服务 1 2 3 vim /etc/chrony .conf 修改同步ntp地址 allow10.168.0.0 /24 4)启动NTP服务 1 2 chkconfigchronydon /etc/init .d /chronyd start 1.3.2 其他节点 1)yum安装chrony服务 1 yum install -ychrony 2)配置chrony服务 1 vim /etc/chrony .conf 修改如下: 1 2 3 4 5 #server0.rhel.pool.ntp.orgiburst #server1.rhel.pool.ntp.orgiburst #server2.rhel.pool.ntp.orgiburst #server3.rhel.pool.ntp.orgiburst server10.168.0.125iburst 3)启动NTP服务 centos6: 1 2 chkconfigchronydon /etc/init .d /chronyd start centos7: 1 2 systemctl enable chronyd.service systemctlstartchronyd.service 1.3.3 验证操作 1)控制节点 1 chronycsources 显示如下: 1 2 3 4 5 6 7 210Numberofsources=4 MSName /IP addressStratumPollReachLastRxLastsample =============================================================================== ^-59.46.44.25328377159-3305us[-3305us]+ /- 66ms ^+dns1.synet.edu.cn28377165-1231us[-951us]+ /- 31ms ^*time5.aliyun.com28377160-30us[+250us]+ /- 27ms ^+time7.aliyun.com28377162+1348us[+1628us]+ /- 35ms 2)其他节点 1 chronycsources 显示如下: 1 2 3 4 210Numberofsources=1 MSName /IP addressStratumPollReachLastRxLastsample =============================================================================== ^?10.168.0.12507010y+0ns[+0ns]+ /- 0ns 1.4 配置OpenStack包 1.4.1 启用OpenStack库 1 yum install -ycentos-release-openstack.noarch 1.4.2 完成安装 1)更新系统 1 yum-yupgrade 2)安装OpenStack客户端 1 yum install -ypython-openstackclient 1.5 关系型数据库的安装 1.5.1 配置MariaDB的yum源(可选) 1 vim /etc/yum .repos.d /MariaDB .repo 输入内容如下: 1 2 3 4 5 [MariaDB] name=MariaDB baseurl=http: //yum .mariadb.org /10 .0 /centos6-amd64/ gpgcheck=1 gpgkey=http: //yum .mariadb.org /RPM-GPG-KEY-MariaDB 1.5.2 安装MariaDB 1 yum install -yMariaDB-clientMariaDB-serverMySQL-python 1.5.3 配置MariaDB 1 vim /etc/my .cnf.d /mariadb_openstack .cnf 加入如下内容: 1 2 3 4 5 6 7 8 9 [mysqld] bind-address=10.168.0.125 default-storage-engine=innodb innodb_file_per_table collation-server=utf8_general_ci init-connect= 'SETNAMESutf8' character- set -server=utf8 1.5.4 完成安装 1)启动服务并配置默认启动 1 2 /etc/init .d /mysql start chkconfigmysqlon 2)初始化数据库 1 mysql_secure_installation 配置向导如下: 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 [...] Setrootpassword?[Y /n ]y Newpassword: Re-enternewpassword: Passwordupdatedsuccessfully! Reloadingprivilegetables.. ...Success! [...] Removeanonymous users ?[Y /n ]y ...Success! [...] Disallowrootloginremotely?[Y /n ]n ...skipping. [...] Remove test databaseandaccesstoit?[Y /n ]y -Dropping test database... ...Success! -Removingprivilegeson test database... ...Success! [...] Reloadprivilegetablesnow?[Y /n ]y ...Success! Cleaningup... [...] 1.6 非关系型数据库的安装 1.6.1 配置MongoDB的yum源 1 vim /etc/yum .repos.d /MongoDB .repo 输入如下内容: 1 2 3 4 [MongoDB] name=MongoDB baseurl=http: //downloads-distro .mongodb.org /repo/redhat/os/x86_64/ gpgcheck=0 1.6.2 安装MongoDB 1 yum install -ymongodb-orgmongodb-org-server 1.6.3 配置MongoDB 1)配置管理地址 1 vim /etc/mongod .conf 修改如下参数 1 bind_ip=10.168.0.125 2)限制日志文件大小 1 vim /etc/mongod .conf 修改如下参数 1 smallfiles= true 1.6.4 完成安装 1 2 /etc/init .d /mongod start chkconfigmongodon 1.7 安装消息队列服务 1.7.1 配置yum源 1)配置ERLang的yum源 1 vimerlang-solutions.repo 输入如下内容: 1 2 3 4 5 6 [erlang-solutions] name=Centos$releasever-$basearch-ErlangSolutions baseurl=https: //packages .erlang-solutions.com /rpm/centos/ $releasever/$basearch gpgcheck=1 gpgkey=https: //packages .erlang-solutions.com /rpm/erlang_solutions .asc enabled=1 2)解决SOCat的源问题 1 2 wgethttp: //pkgs .repoforge.org /rpmforge-release/rpmforge-release-0 .5.3-1.el6.rf.x86_64.rpm rpm-ivhrpmforge-release-0.5.3-1.el6.rf.x86_64.rpm 1.7.2 安装RabbitMQ 1 yum install -yhttp: //www .rabbitmq.com /releases/rabbitmq-server/v3 .6.2 /rabbitmq-server-3 .6.2-1.noarch.rpm 1.7.3 启动并配置服务开机自启动 1 2 /etc/init .d /rabbitmq-server start chkconfigrabbitmq-serveron 1.7.4 增加OpenStack用户 1 rabbitmqctladd_useropenstackRABBIT_PASS 显示如下: 1 Creatinguser "openstack" ... 1.7.5 允许配置和读写访问权限 1 rabbitmqctlset_permissionsopenstack ".*" ".*" ".*" 显示如下: 1 Settingpermissions for user "openstack" in vhost "/" ... 注:文章为项目的安装测试文档,可能会根据后面的章节修改,如果有兴趣请持续关注。 本文转自 tanzhenchao 51CTO博客,原文链接:http://blog.51cto.com/cmdschool/1792158,如需转载请自行联系原作者

优秀的个人博客,低调大师

基于CentOS 6.8的OpenStack手动安装-集中认证

注:本章以上一章节为基础,详细配置请参阅: http://cmdschool.blog.51cto.com/2420395/1792158 2 增加身份服务 2.1 OpenStack的认证服务介绍 2.1.1 认证服务的组件 1)服务 - 集中认证服务器提供使用RESTful接口的验证和授权服务 2)驱动 - 驱动程序或后端服务器集成到后端的集中认证服务 3)模块 - 2.2 前提条件 2.2.1 登录数据库 1 mysql-uroot-p 2.2.2 创建数据库 1 CREATEDATABASEkeystone; 2.2.3 数据库授权并即时生效 1 2 3 GRANTALLPRIVILEGESONkeystone.*TO 'keystone' @ 'localhost' IDENTIFIEDBY 'KEYSTONE_DBPASS' ; GRANTALLPRIVILEGESONkeystone.*TO 'keystone' @ '%' IDENTIFIEDBY 'KEYSTONE_DBPASS' ; FLUSHPRIVILEGES; 注:基于安全考量,以上密码可使用以下命令生成随机密码 1 opensslrand-hex10 2.3 OpenStack认证组件的安装 注:由于官方尚未完成ROD-Juno版的编译,本次测试暂停更新,详细请参阅: https://wiki.centos.org/zh/Cloud/OpenStack/JunoEL6QuickStart 本文转自 tanzhenchao 51CTO博客,原文链接:http://blog.51cto.com/cmdschool/1794300,如需转载请自行联系原作者

优秀的个人博客,低调大师

openstack 租户ip 手动配置 openstack静态租户ip

作者:【吴业亮】云计算开发工程师博客:http://blog.csdn.net/wylfengyujiancheng1、综述:在日常开发和生产环境中经常需要将OpenStack虚拟机配置一个静态Ip,但配置完成后发现外面无法访问。原因是openstack每个端口默认只允许一个Ip的数据报文通过。下面介绍如何配置openstack使虚拟机静态IP地址能访问。2、需求将下面虚拟机配置一个172.16.8.200的Ip,且外部可以访问。3、查询该网卡所在的端口 # neutron port-list | grep '10.168.10.13' 1 1 4、检查该IP未被占用 # ping 172.16.8.200 1 1 5、修改该端口,允许通过IP # neutron port-update 1effb238-d49b-4679-91b9-d843a8ff3e30 --allowed-address-pairs type=dict list=true ip_address=172.16.8.200 1 1 6、也可修改通过一个网段 # neutron port-update 1effb238-d49b-4679-91b9-d843a8ff3e30 --allowed-address-pairs type=dict list=true ip_address=172.16.8.0/24 1 1 7、修改虚拟机网卡IP……略过……8、测试 参考:http://docs.openstack.org/

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Apache Tomcat

Apache Tomcat

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

JDK

JDK

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。