首页 文章 精选 留言 我的

精选列表

搜索[全场景互联],共10008篇文章
优秀的个人博客,低调大师

Python 系统编程 (全)

进程 1.进程 简单的说就是实现唱歌跳舞同时进行,那么就需要一个新的方法,叫做:多任务 2.多任务的概念 ①简单地说,就是操作系统可以同时运行多个任务 ②并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行 ③就是说当cpu核心数量大于任务数量就是并行,反过来,就是并发 ④依照的规则有时间片轮转,优先级调度 3.进程的创建-fork ①程序:编写完毕的代码,在没有运行的时候,称之为程序 ②进程:正在运行着的代码,还有需要运行的环境等 ③fork( ): Python的os模块封装了常见的系统调用,其中就包括fork importos #注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以 pid=os.fork() ifpid==0: print('哈哈1') else: print('哈哈2') #程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中 #然后父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的id号 #普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回 #getpid()是获取当前进程(主进程或子进程)的id、getppid()获取父进程的id 4.多进程修改全局变量 多进程中,每个进程中所有数据(包括全局变量)都各有拥有一份,互不影响,所以在修改全局变量的时候,两个变量相互独立 5.多次fork问题 下次遇到多进程就画图,再如: os.fork() os.fork() os.fork()#就变成了8个进程 在while True中,如果有os.fork(),程序一定崩,这就相当于fork炸弹,死循环创建进程 6.multiprocessing模块 ①multiprocessing模块提供了一个Process类来代表一个进程对象 ②跨平台的操作,fork只在linux下才有效,所以平时应该使用process,它是一个类 程序如下: frommultiprocessingimportProcess importtime deftest(): whileTrue: print("---test---") time.sleep(1) P=Process(target=test) P.start()#让这个进程开始执行test函数里的代码 whileTrue: print("---main---") time.sleep(1) 说明: #创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动 #这样创建进程比fork()还要简单 #join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步,这就与fork不同,需要等子进程结束,主进程才可以结束 7.Process语法结构 Process([group[,target[,name[,args[,kwargs]]]]]) ①target:表示这个进程实例所调用对象; ②args:表示调用对象的位置参数元组; ③kwargs:表示调用对象的关键字参数字典; ④name:为当前进程实例的别名; ⑤group:大多数情况下用不到; 8.Process类常用方法 ①is_alive():判断进程实例是否还在执行; ②join([timeout]):是否等待进程实例执行结束,或等待多少秒; ③start():启动进程实例(创建子进程); ④run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中 的run()方法; ⑤terminate():不管任务是否完成,立即终止; 9.Process类常用属性 ①name:当前进程实例别名,默认为Process-N,N为从1开始递增的整数; ②pid:当前进程实例的PID值 ③进程的创建-Process子类: 创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象 如果想知道程序的运行时间,可以用开始和结束的time.time()两个时间差,就代表运行时间 10.进程池pool ①当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程 ②但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法 P0=Pool(3)#定义一个进程池,最大进程数3 foriinrange(0,10): #Pool.apply_async(要调用的目标,(传递给目标的参数元组,))(非堵塞式) #Pool.apply(worker,(i,))(堵塞式),主进程卡在这里,需要等子进程完成才能添加 #每次循环将会用空闲出来的子进程去调用目标 P0.apply_async(worker,(i,))#work是一个函数 P0.close()#关闭进程池,关闭后po不再接收新的请求 P0.join()#等待po中所有子进程执行完成,必须放在close语句之后 11.多种创建进程的方式比较 os.fork()中,子进程和父进程可以都执行,而且父进程可以不必等待子进程结束 p=process(target=xxx) p.start() #子进程和父进程都可执行 pool=Pool(3) pool.apply_async(xxx) #主进程一般用来等待,真正的任务都在子进程中执行 12.进程间通信-Queue ①Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。 ②Queue的使用: 可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序 队列:先进先出 栈:先进后出 初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头) frommultiprocessingimportQueue q=Queue(3)#初始化一个Queue对象,最多可接收三条put消息 try: q.put_nowait("消息4") except: print("消息列队已满,现有消息数量:%s"%q.qsize()) Queue.qsize():返回当前队列包含的消息数量; Queue.empty():如果队列为空,返回True,反之False; Queue.full():如果队列满了,返回True,反之False; Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True Queue.get_nowait():相当Queue.get(False);应该把它放在try里面 Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True ③进程池中的Queue: 如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue() 13.孤儿进程和僵尸进程 ①孤儿进程:是指父进程结束,但子进程还未结束,通常的情况下父进程可以清除子进程 的垃圾,表示子进程没人收尸了 ②僵尸进程:是指子进程结束了,父进程还未结束 ③一般在操作系统中,0号进程负责切换任务,1号进程负责生子进程,并负责打理孤儿进程 线程 1.多线程threading python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用 2.进程和线程的关系 ①线程是进程里面一种真正执行代码的东西,类似进程里面的箭头 ②进程是资源分配的单位,线程是cpu调度的单位 ③进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ ④线程,能够完成多任务,比如 一个QQ中的多个聊天窗口 ⑤定义的不同: 进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈), 但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源 ⑥区别: 一个程序至少有一个进程,一个进程至少有一个线程 线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率 线程不能够独立执行,必须依存在进程中 ⑦优缺点: 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反 3.多线程执行 importthreading importtime defsaySorry(): print("Python才是最好的语言") time.sleep(1) if__name__=="__main__": foriinrange(5): t=threading.Thread(target=saySorry) t.start()#启动线程,即让线程开始执行 说明: #可以明显看出使用了多线程并发的操作,花费时间要短很多 #创建好的线程,需要调用start()方法来启动 #主线程会等待所有的子线程结束后才结束 4.线程执行代码的封装 ①通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写 run方法 ②python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python 虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程 ③多线程程序的执行顺序与多进程类似,都是不确定的 5.总结 ①每个线程一定会有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。 ②当线程的run()方法结束时该线程完成。 ③无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式 6.多线程-共享全局变量 ①在一个进程内的所有线程共享全局变量,能够在不适用其他方式的前提下完成多线程之间的数据共享(这点要比多进程要好) ②缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全),在线程中,不能同时对全局变量进行修改,解决办法是轮流让线程进行修改 7.同步 ①多线程开发可能遇到的问题 ②假设两个线程t1和t2都要对num=0进行增1运算,t1和t2都各对num修改10次, num的最终的结果应该为20。但是由于是多线程访问,答案可能不一样,所以在修改时, 就要让其修改完再轮到下一个线程来修改 ③什么是同步: 同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说,"同"字从字面上 容易理解为一起动作其实不是,"同"字应是指协同、协助、互相配合 ④解决问题的思路: 系统调用t1,然后获取到num的值为0,此时上一把锁,即不允许其他现在操作num 对num的值进行+1解锁,此时num的值为1,其他的线程就可以使用num了,而且是 num的值不是0而是1 同理其他线程在对num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程 中不允许其他线程访问,就保证了数据的正确性 8.互斥锁 ①当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制 ②某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定,其他线程不能更改;直到该线程释放资源,将资源的状态变成非锁定,其他的线程才能再次锁定该资源。互斥锁保证了每次 只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性 ③threading模块中定义了Lock类,可以方便的处理锁定: #创建锁 mutex=threading.Lock() #锁定 mutex.acquire([blocking]) #释放 mutex.release() ④如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为止(如果没有指定,那么默认为True)如果设定blocking为False,则当前线程不会堵塞 ⑤上锁解锁过程: 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为 &ldquo;blocked&rdquo; 状态,称为&ldquo;阻塞&rdquo;,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进 入 &ldquo;unlocked&rdquo;状态。 线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行( running)状态。 ⑥总结: 锁的好处: 确保了某段关键代码只能由一个线程从头到尾完整地执行 锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时, 可能会造成死锁 9.多线程-非共享数据 ①对于全局变量,在多线程中要格外小心,否则容易造成数据错乱的情况发生 ②在多线程开发中,全局变量是多个线程都共享的数据,而局部变量等是各自线程的,是非共享的 10.死锁 ①举个例子:就好比是现实社会中,男女双方都在等待对方先道歉 ②在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁 ③避免死锁: #程序设计时要尽量避免(银行家算法) #添加超时时间等 ifmutex.acquire(2): 11.同步应用 可以使用互斥锁完成多个任务,有序的进程工作,这就是线程的同步 12.生产者与消费者模式 ①Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列 Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue ②这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用 ③可以使用队列来实现线程间的同步 ④Queue的说明: 对于Queue,在多线程通信之间扮演重要的角色 添加数据到队列中,使用put()方法 从队列中取数据,使用get()方法 判断队列中是否还有数据,使用qsize()方法 队列就是用来给生产者和消费者解耦的 13.ThreadLocal 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁一个thread.local()变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。thread.local()解决了参数在一个线程中各个函数之间互相传递的问题 14.异步 ①同步调用就是你喊你朋友吃饭,你朋友在忙,你就一直在那等,等你朋友忙完了,你们一起去 ②异步调用就是你喊你朋友吃饭,你朋友说知道了,待会忙完去找你 ,你就去做别的了 pool.apply_async(func=test,callback=test2)#callback是回调 进程整理代码 1. getpid()、getppid() importos rpid=os.fork() ifrpid<0: print("fork调用失败") elifrpid==0: print("我是子进程(%s),我的父进程是(%s)"%(os.getpid(),os.getppid())) x+=1 else: print("我是父进程(%s),我的子进程是(%s)"%(os.getpid(),rpid)) print("父子进程都可以执行这里的代码") 2.多进程修改全局变量 importos importtime num=0 #注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以 pid=os.fork() ifpid==0: num+=1 print('哈哈1---num=%d'%num) else: time.sleep(1) num+=1 print('哈哈2---num=%d'%num) 3.进程的创建-Process子类 frommultiprocessingimportProcess importtime importos #继承Process类 classProcess_Class(Process): #因为Process类本身也有__init__方法,这个子类相当于重写了这个方法, #但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个类继承的一些方法和属性, #最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作 def__init__(self,interval): Process.__init__(self) self.interval=interval #重写了Process类的run()方法 defrun(self): print("子进程(%s)开始执行,父进程为(%s)"%(os.getpid(),os.getppid())) t_start=time.time() time.sleep(self.interval) t_stop=time.time() print("(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start)) if__name__=="__main__": t_start=time.time() print("当前程序进程(%s)"%os.getpid()) p1=Process_Class(2) #对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法,所以这里会执行p1.run() p1.start() p1.join() t_stop=time.time() print("(%s)执行结束,耗时%0.2f"%(os.getpid(),t_stop-t_start)) 4.进程池pool frommultiprocessingimportPool importos,time,random defworker(msg): t_start=time.time() print("%s开始执行,进程号为%d"%(msg,os.getpid())) #random.random()随机生成0~1之间的浮点数 time.sleep(random.random()*2) t_stop=time.time() print(msg,"执行完毕,耗时%0.2f"%(t_stop-t_start)) po=Pool(3)#定义一个进程池,最大进程数3 foriinrange(0,10): #Pool.apply_async(要调用的目标,(传递给目标的参数元祖,)) #每次循环将会用空闲出来的子进程去调用目标 po.apply_async(worker,(i,)) print("----start----") po.close()#关闭进程池,关闭后po不再接收新的请求 po.join()#等待po中所有子进程执行完成,必须放在close语句之后 print("-----end-----") 5.进程Queue的读写 frommultiprocessingimportProcess,Queue importos,time,random #写数据进程执行的代码: defwrite(q): forvaluein['A','B','C']: print'Put%stoqueue...'%value q.put(value) time.sleep(random.random()) #读数据进程执行的代码: defread(q): whileTrue: ifnotq.empty(): value=q.get(True) print'Get%sfromqueue.'%value time.sleep(random.random()) else: break if__name__=='__main__': #父进程创建Queue,并传给各个子进程: q=Queue() pw=Process(target=write,args=(q,)) pr=Process(target=read,args=(q,)) #启动子进程pw,写入: pw.start() #等待pw结束: pw.join() #启动子进程pr,读取: pr.start() pr.join() #pr进程里是死循环,无法等待其结束,只能强行终止: print''" print'所有数据都写入并且读完' 线程整理代码 1.多线程 importthreading fromtimeimportsleep,ctime defsing(): foriinrange(3): print("正在唱歌...%d"%i) sleep(1) defdance(): foriinrange(3): print("正在跳舞...%d"%i) sleep(1) if__name__=='__main__': print('---开始---:%s'%ctime()) t1=threading.Thread(target=sing) t2=threading.Thread(target=dance) t1.start() t2.start() #sleep(5)#屏蔽此行代码,试试看,程序是否会立马结束? print('---结束---:%s'%ctime()) 2.多线程-共享全局变量 fromthreadingimportThread importtime g_num=100 defwork1(): globalg_num foriinrange(3): g_num+=1 print("----inwork1,g_numis%d---"%g_num) defwork2(): globalg_num print("----inwork2,g_numis%d---"%g_num) print("---线程创建之前g_numis%d---"%g_num) t1=Thread(target=work1) t1.start() #延时一会,保证t1线程中的事情做完 time.sleep(1) t2=Thread(target=work2) t2.start() 3.死锁 importthreading importtime classMyThread1(threading.Thread): defrun(self): ifmutexA.acquire(): print(self.name+'----do1---up----') time.sleep(1) ifmutexB.acquire(): print(self.name+'----do1---down----') mutexB.release() mutexA.release() classMyThread2(threading.Thread): defrun(self): ifmutexB.acquire(): print(self.name+'----do2---up----') time.sleep(1) ifmutexA.acquire(): print(self.name+'----do2---down----') mutexA.release() mutexB.release() mutexA=threading.Lock() mutexB=threading.Lock() if__name__=='__main__': t1=MyThread1() t2=MyThread2() t1.start() t2.start() 4.生产者和消费者模式 importthreading importtime #python2中 fromQueueimportQueue #python3中 #fromqueueimportQueue classProducer(threading.Thread): defrun(self): globalqueue count=0 whileTrue: ifqueue.qsize()<1000: foriinrange(100): count=count+1 msg='生成产品'+str(count) queue.put(msg) print(msg) time.sleep(0.5) classConsumer(threading.Thread): defrun(self): globalqueue whileTrue: ifqueue.qsize()>100: foriinrange(3): msg=self.name+'消费了'+queue.get() print(msg) time.sleep(1) if__name__=='__main__': queue=Queue() foriinrange(500): queue.put('初始产品'+str(i))

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

传送网系列笔记【全】

【申明:本系列为读书笔记,仅供自我学习】 目录 传送网基础知识 刚性通道时代——PDH 刚性通道时代——SDH 刚性通道时代——保护 刚性通道时代——MSTP 刚性通道时代——架构 光通信高速干道——波分复用 光通信高速干道——OTN(上) 光通信高速干道——OTN(下) MPLS VS MSTP 分组传送网——PTN/IPRAN 分组传送网——LTE/QoS/保护/时钟同步 本文转自Grodd51CTO博客,原文链接:http://blog.51cto.com/juispan/1970711,如需转载请自行联系原作者

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

Android Fragment使用全解析

Fragment的使用可谓是老生常谈了~~~ 1、概述 自API 11引入Fragment之后,Fragment可谓风靡一时,现在大部分项目都或多或少的用到了Fragment,其更轻量级,更加适用屏幕,更加方便UI设计等优势。说了这么多什么是Fragment呢? Fragment:碎片,碎片是一个应用程序的用户界面和行为能够被放置在一个活动上。在其核心,它代表了一个特定的操作或界面,运行在一个更大的活动上。代表界面是因为可作为View在布局中进行使用,代表特定操作是因为包含生命周期可进行逻辑操作。简言之,Fragment就是一个带生命周期的组件。(若有问题恳请指正!) Fragment的特点: 生命周期必须依赖于Activity,当Activity被销毁,所有的碎片将被摧毁。(自己曾经踩坑) 轻量级,轻量切换。 方便处理平板、Phone的界面差异。 2、继承结构和生命周期 继承结构: Fragment直接继承Object,有四个直接子类,我个人对它的子类使用甚少。 生命周期: Fragment的生命周期在图上标注的很清楚了就不赘述了。该图是很久之前收藏的,已忘记原出处,在此感谢原作者! 3、基本使用 1).静态使用 静态使用就是Fragment相当于控件一样在布局中使用。 TestFragment.java 继承Fragment重写onCreateView方法 /** *Createdbymagicon2016年9月27日. */ publicclassTestFragmentextendsFragment{ @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState){ Viewview=inflater.inflate(R.layout.fragment_main,container); ImageViewimg=(ImageView)view.findViewById(R.id.img); img.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewv){ Toast.makeText(getActivity(),"这是一个fragment",Toast.LENGTH_SHORT).show(); } }); returnview; } } fragment_main.xml <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/img"/> </RelativeLayout> MainActivity.java 里面其实什么也没干。 /** *Createdbymagicon2016年9月27日. */ publicclassMainActivityextendsActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } activity_main.xml <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/id_fragment" android:layout_width="match_parent" android:layout_height="match_parent" class="com.magic.test_fragment.TestFragment"/> </RelativeLayout> 使用 fragment 标签添加碎片,通过class指定碎片的完整类名。 运行效果: 2).动态使用 动态使用就是向Fragment布局容器中动态添加、替换、移除、隐藏、显示Fragment。 CommonFragment.java /** *Createdbymagicon2016年9月27日.通用Fragment */ @SuppressLint("ValidFragment") publicclassCommonFragmentextendsFragment{ Stringdesc; publicCommonFragment(Stringdesc){ super(); this.desc=desc; } @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState){ Viewview=inflater.inflate(R.layout.fragment_common,container,false); TextViewtev=(TextView)view.findViewById(R.id.tev); System.out.println(desc); tev.setText(desc); returnview; } } 通过构造方法传递数据的形式向TextView上设置内容。 fragment_common.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tev" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textColor="@color/mainOrange"/> </LinearLayout> MainActivity.java /** *Createdbymagicon2016年9月27日.底部tab+fragment */ publicclassMainActivityextendsActivityimplementsOnClickListener{ TextViewtev_tab1,tev_tab2,tev_tab3,tev_tab4; //fragment事务类 FragmentTransactionft; //fragment CommonFragmenttabFragment1,tabFragment2,tabFragment3,tabFragment4; @SuppressLint("CommitTransaction") @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); initView(); ft=getFragmentManager().beginTransaction(); tabFragment1=newCommonFragment("Tab1"); //替换 ft.replace(R.id.container,tabFragment1); //提交 ft.commit(); } //初始化控件 privatevoidinitView(){ tev_tab1=(TextView)findViewById(R.id.tev_tab1); tev_tab2=(TextView)findViewById(R.id.tev_tab2); tev_tab3=(TextView)findViewById(R.id.tev_tab3); tev_tab4=(TextView)findViewById(R.id.tev_tab4); tev_tab1.setOnClickListener(this); tev_tab2.setOnClickListener(this); tev_tab3.setOnClickListener(this); tev_tab4.setOnClickListener(this); } @Override publicvoidonClick(Viewv){ FragmentTransactionft=getFragmentManager().beginTransaction(); switch(v.getId()){ caseR.id.tev_tab1: ft.replace(R.id.container,tabFragment1); break; caseR.id.tev_tab2: if(tabFragment2==null){ tabFragment2=newCommonFragment("Tab2"); } ft.replace(R.id.container,tabFragment2); break; caseR.id.tev_tab3: if(tabFragment3==null){ tabFragment3=newCommonFragment("Tab3"); } ft.replace(R.id.container,tabFragment3); break; caseR.id.tev_tab4: if(tabFragment4==null){ tabFragment4=newCommonFragment("Tab4"); } ft.replace(R.id.container,tabFragment4); break; } //提交 ft.commit(); } } activity_main2.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/mainTextBlack" android:orientation="horizontal"> <TextView android:id="@+id/tev_tab1" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="Tab1" android:textColor="@color/white"/> <TextView android:id="@+id/tev_tab2" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="Tab2" android:textColor="@color/white"/> <TextView android:id="@+id/tev_tab3" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="Tab3" android:textColor="@color/white"/> <TextView android:id="@+id/tev_tab4" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="Tab4" android:textColor="@color/white"/> </LinearLayout> </LinearLayout> 通过 FrameLayout 标签创建Fragment的容器,底部四个Tab添加监听事件用于动态更换FrameLayout容器中的Fragment。 运行效果: 4、相关类及主要方法 FragmentManager碎片管理器,抽象类,具体实现在Android-support-v4.jar中的FragmentManagerImpl类中。 //获取FragmentManager对象 FragmentManagermanager=getFragmentManager(); FragmentTransaction碎片事务类,抽象类,具体实现在BackStackRecord类中。添加、删除、替换等操作其实最终的实现还是在FragmentManagerImpl类中。 //获取FragmentTransaction对象 FragmentTransactiontransaction=manager.beginTransaction(); //添加fragment transaction.add(); transaction.add(containerViewId,fragment,tag); //将被添加到容器的现有fragment替换 transaction.replace(); //删除一个现有的fragment transaction.remove(); //保存当前fragment数据,避免视图重绘 transaction.hide(); //显示以前隐藏的fragment transaction.show(); //这两个方法会触发fragment中的onHiddenChanged(booleanhidden)回调 //显示之前数据实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView transaction.addToBackStack(null); //事务提交 transaction.commit(); Fragment 碎片类 Fragmentfragment=newFragment(); //返回此fragment当前关联的Activity fragment.getActivity(); //设置数据 fragment.setArguments(newBundle()); //获取数据 fragment.getArguments(); //返回与该fragment作用的FragmentManager fragment.getFragmentManager(); //获取标签名 fragment.getTag(); //当隐藏状态改变的时候回调 //onHiddenChanged(true); 有兴趣大家可以去Read The Fucking Source,反正我看的比较头大……. 5、其它 未完待续…… 本文作者:佚名 来源:51CTO

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

Android DataBinding 实战全解

2015年的Google IO大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),官方原生支持 MVVM 模型。数据绑定的概念并不陌生,Web开发中已经很是普遍,因此DataBinding或多或少地都借鉴了Web端开成熟的经验,其语法与使用方式都和JSP中的EL表达式非常类似。经过不断地强化,到了2016年,DataBinding已经可以支持数据双向绑定。 1. DataBinding简介 DataBinding解决了Android UI 编程的一个痛点,其主要优势在于: 对于MVVM的支持:官方原生支持 MVVM 模型可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性。 提高开发效率: 去掉Acitivity和Fragments中更新UI数据的代码,让业务逻辑和UI代码分离; XML成为UI数据的唯一真实来源; 减少定义view id 和使用findViewById(); 性能高、功能强: 充分考虑了性能因素,高效的绑定和更新数据; 更安全,在编译时会发现由于错误的ID而引起的Errors; 保证代码在主线程经常; 在DataBinding推出之前,市面上已经有类似的第三方库在被使用,比如: ButterKnife; Android Annotations; RoboBinding; DataBinding让这些依赖注入框架会慢慢地失去市场,因为在 Java代码中直接使用View变量的情况会越来越少。 但是DataBinding毕竟还在发展阶段,有些地方还值得完善: IDE的支持不足善:虽然DataBinding是Google亲生的,但是Android Studio中的支持还不是不尽如人意,尤其是在XML文件中代码提示有待加强; 报错信息不直接:这个是老生常谈的问题,虽然DataBinding在编译时能发现数据绑定的错误,但是报错信息往往不直观,需要用户在很多错误信息中自己去找错误点。 没有重构的支持:这可以说是所有数据绑定框架都会面临的问题,因为数据的绑定都是在XML中,一旦用户需要继承原来的代码,就压面临这将所有数据绑定代码重新编写。 2. DataBinding的基本使用 准备工作 确保Android stuido的版本一定要大于1.3。 Android的Gradle插件版本不低于 1.5.0-alpha1: classpath 'com.android.tools.build:gradle:1.5.0' 然后修改对应模块(Module)的 build.grade: android { .... dataBinding { enabled = true } } 工程创建完成后,我们通过一个最简单的例子来说明 Data Binding 的基本用法。 布局文件 使用DataBinding之后,xml的布局文件就不再用于单纯地展示UI元素,还需要定义UI元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了layout,并且新增了一个节点data,用来定义绑定的数据。 <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <!--添加要绑定的数据--> </data> <!--下面的布局文件就是原先的根节点(Root Element)--> <LinearLayout> .... </LinearLayout> </layout> data节点的作用就像一个桥梁,Activity中将data需要数据传入,XML定义data数据绑定的位置,搭建了 View 和 Model 之间的通路,也就是实现MVVM的ViewModel。 下面说明下如何在XML定义data节点:我们先在 xml 布局文件的data节点中声明variable节点,这个节点将会定义变量的名称、类型,为UI元素提供数据(例如将String绑定到TextView的android:text属性上)。 <data> <!--java.lang.*包是自动导入的不需要你再次的导入--> <!--当出现了import相同的类名的时候需要为其指定外号alias加以区分--> <import type="com.example.administrator.databindingdemo.Student"/> <import type="com.example.administrator.databindingdemo.MainActivity.Presenter"/> <variable name="student" type="Student"/> <variable name="presenter" type="Presenter"/> <variable name="visibility" type="Boolean"/> </data> 其中Presenter类时用来绑定方法的类,我们稍后再说;Student类是我们定义的一个类,用来呈现数据,其定义如下: public class Student extends BaseObservable{ private String firstName; private String lastName; public void setFirstName(String firstName) { this.firstName = firstName; //提示该属性刷新了 notifyPropertyChanged(BR.firstName); //提示所有的属性都刷新 //notifyAll(); } //注解来提示Binding生成这个字段 @Bindable public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } @Bindable public String getFirstName() { return firstName; } public Student(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Override public String toString() { return "Student{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '}'; } } Studnet类继承了BaseObservable,这个是数据更新时所需要的,我们稍后在再来讨论。 当我们设置DataBinding生效后,如果一个xml文件的被如上这般设置了Data节点,编译器就会自动生成一个继承自 ViewDataBinding 的类(build 目录下)。其命名规则也是固定的:如果 xml 的文件名叫 activity_main.xml,那么生成的类就是 ActivityMainBinding。 绑定数据变量 下面需要修改MainActivity的onCreate方法,用 DatabindingUtil.setContentView()来替换掉setContentView(),然后创建一个Student对象,通过mBinding.setStudent(mStudent)与 variable 进行绑定。 Student mStudent = new Student("guo","chengqian"); private ActivityMainBinding mBinding; Boolean visiblity = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this,R.layout.activity_main); //绑定变量 mBinding.setStudent(mStudent); mBinding.setPresenter(new Presenter()); mBinding.setVisibility(visiblity); } ActivityMainBinding类中所有的set方法是根据variable名称生成的。我们在XML文件中定义了三个变量,所以会生成三个set方法。 在XML中使用变量 当数据传入ViewDataBinding与Variable绑定之后,xml的UI元素就可以直接使用了。 <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{student.firstName}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="@{visibility?View.VISIBLE:View.GONE}" android:text="@{student.lastName}" /> 直接在对应属性上@{variable}就可以使用该变量的值以及对应的属性。 现在运行程序,就会看到我们传入Student的姓名已经显示出来了 1500001896236.png 3. 事件方法绑定 仅仅是在UI上绑定数据还远远不够,UI上的响应事件也需要被处理,因此需要要把响应事件的方法绑定到View上。 事件方法的绑定有两种方法:第一种是方法引用,这种方法要求函数的参数个数,类型以及顺序都会原生的事件方法相同;第二种方法是使用lambda表达式,这种方法更为灵活,可以自定义参数。 我们来看下例子: 这是一个方法类的实现,之前在xml的data节点中已经声明了变量,在Activity中也进行了绑定。 //方法类 public class Presenter{ //方法引用 参数要求符合接口定义 public void onTextChanged(CharSequence s, int start, int before, int count){ mStudent.setFirstName(s.toString()); //如果已经为该类继承了BaseObservable 就不需要再赋值了; //mBinding.setStudent(mStudent); } //方法引用,参数要求符合接口定义; public void onClick(View view){ Toast.makeText(MainActivity.this,"onClick!",Toast.LENGTH_SHORT).show(); } //自定义方法 public void onClickListenerBinding(Student student){ Toast.makeText(MainActivity.this, student.getLastName(),Toast.LENGTH_SHORT).show(); } } 首先在EidtText控件中使用,将onTextChanged方法绑定。 <EditText android:id="@+id/first_name_ed_txt" android:onTextChanged="@{presenter.onTextChanged}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="First Name"/> 这样一旦输入的内容改变,变量student中对应的firstName属性也会跟着改变。 同样,我们将onClick方法绑定到一个TextView上 <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{student.firstName}" android:onClick="@{presenter.onClick}" /> 但是这种方法不够灵活,绑定的方法必须和View所要求的接口参数一致。为了更为灵活的绑定方法,还可以使用Lambda表达式。 <TextView android:id="@+id/textView2" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="@{visibility?View.VISIBLE:View.GONE}" android:text="@{student.lastName}" android:onClick="@{()-> presenter.onClickListenerBinding(student)}"/> lambda表达式实际上也是创建了匿名函数,然后再这个匿名函数中调用了Presenter中的方法,只不过省去了匿名函数的申明,只保留最为核心的逻辑部分。 此外,lambd表达式支持传入Context参数,比如Presenter中定义这样的函数 public void onStudentLongClick(Student student, Context context){ Toast.makeText(LambdaActivity.this, "LongClick: "+student.toString(), Toast.LENGTH_SHORT).show(); } Button中可以这样调用,Context参数会被自动生成: <Button ...... android:longClickable="true" android:onClick="@{() -> presenter.onStudentLongClick(student,context)}" ...... /> 4. DataBinding所支持的表达式 为了扩展更为强大的功能,DataBinding在使用变量时支持一下表达式: 运算符:Mathematical + - / * % 字符串连接:String concatenation + 逻辑运算:Logical && || 位操作: Binary & | ^ Unary + - ! ~ 移位操作:Shift >> >>> << 比较操作:Comparison == > < >= <= 类型判断:instanceof Grouping () Literals - character, String, numeric, null 类型转化:Cast Method calls Field access Array access [] Ternary operator ?: 但不支持的表达式: this super new Explicit generic invocation Null Coalescing 运算符 DataBinding对于引用变量是否为空,也有特殊的支持,使用符号??。 android:text="@{student.firstName ??student.lastName}" 就等价于 android:text="@{student.firstName != null ? student.firstName : student.lastName}" 5. 在include文件里使用BataBinding 为了让Xml布局文件更为简洁易懂,对于重复使用的布局可以单独建立布局文件,然后使用include导入。DataBinding也考虑到了这种使用情况。可以使用bind:variable_name导入数据变量。 <!--当你使用include的时候,你可以使用命名空间和属性中的变量名将数据传送到另一个布局中去--> <include layout="@layout/include_show_name" bind:presenter="@{presenter}" bind:student="@{student}" bind:visibility="@{visibility}" /> 需要注意的是: 一定要使用app命名空间xmlns:app="http://schemas.android.com/apk/res-auto" 必须在ViewGroup的根布局中导入布局文件,如果在非根节点的ViewGroup中使用include会导致错误; 6. 在SubView中的使用 在SubView的使用方式和在Include中使用是类似的。 <ViewStub bind:visibility="@{visibility}" android:id="@+id/view_stub" android:layout_width="368dp" android:layout_height="wrap_content" android:layout="@layout/viewstub" /> 然后在Activity中通过DataBindingUtil获得SubView对象,然后填充View; //viewstub inflate; View inflate = mBinding.viewStub.getViewStub().inflate(); 7. 双向绑定 上面提的内容都是单向绑定,也就是把后台的数据绑定到前台显示出来。DataBinding如果只有如此功能,也不足为奇,最重要的功能点还是还在于双向绑定,前台显示的数据改变也会自动影响后台的数据。 首先我们新建一个数据对象类 public class FormModel extends BaseObservable { private String name; private String password; @Bindable //该注解表示数据绑定 public String getName() { return name; } public void setName(String name) { this.name = name; //通知name属性发生改变 notifyPropertyChanged(BR.name); } //注解标志该属性是绑定的 @Bindable public String getPassword() { return password; } public void setPassword(String password) { this.password = password; //通知有属性发生改变 notifyPropertyChanged(BR.password); } public FormModel(String name, String password) { this.name = name; this.password = password; } } 该类继承了BaseObservable,在getter方法之前使用@Bindable,标记该属性就是需要绑定的,会在BR类中生成对应的ID;在setter方法中notifyPropertyChanged(BR.ID),通知对应ID的属性发生了改变。 然后在XML中使用“@={variable}”表示数据绑定,如下。 <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="model" type="com.example.administrator.databindingdemo.FormModel"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.administrator.databindingdemo.TowWayBindingActivity"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Name" android:text="@={model.name}"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="pwd" android:text="@={model.password}"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{model.name}"/> </LinearLayout> </layout> 最后在Activity中传入变量。 public class TowWayBindingActivity extends AppCompatActivity { ActivityTowWayBindingBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil. setContentView(this,R.layout.activity_tow_way_binding); FormModel model = new FormModel("user", "12345"); mBinding.setModel(model); //属性改变监听器 model.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { if(BR.name == propertyId) Toast.makeText(TowWayBindingActivity.this, "姓名改变", Toast.LENGTH_SHORT).show(); } }); } } 这是运行程序就会发现,在前端输入姓名时,其他绑定name属性的地方也会改变,说明后台数据也响应改变了。 8. 表达式链与隐式更新 当多个View的属性相互关联式时,可以通过表达式链实现隐式更新,比如: <CheckBox android:id="@+id/check_box"/> <ImageView android:visibility="check_box.checked?View.VISIBILE:View.GONE"/> 9. 添加动画 数据绑定必然涉及到UI的变化,默认情况下往往都是很生硬的UI变化,没有任何过度。DataBinding也提供办法来插入动画。 实现方法也很简单,就是设置一个重新绑定时的回调函数addOnRebindCallback,在函数中设置当前View的UI变化时开启一定延时。 public class AnimationActivity extends AppCompatActivity { ActivityAnimationBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this,R.layout.activity_animation); mBinding.setPresenter(new AnimatorPresenter()); //设置重绑定回调函数 mBinding.addOnRebindCallback(new OnRebindCallback() { @Override public boolean onPreBind(ViewDataBinding binding) { ViewGroup viewGroup = (ViewGroup) binding.getRoot(); //开启延时位移动画 TransitionManager.beginDelayedTransition(viewGroup); return true; } }); } public class AnimatorPresenter{ public void onCheckedChanged(boolean isChecked){ mBinding.setShowImage(isChecked); } } } 在XML中正常使用DataBinding <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <import type="android.view.View"/> <variable name="presenter" type="com.example.administrator.databindingdemo.AnimationActivity.AnimatorPresenter"/> <variable name="showImage" type="Boolean"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.administrator.databindingdemo.AnimationActivity"> <ImageView android:contentDescription="Animation Show" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/default_avatar" android:visibility="@{showImage?View.VISIBLE:View.GONE}"/> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="showImage" android:onCheckedChanged = "@{(v,isChecked)->presenter.onCheckedChanged(isChecked)}"/> </LinearLayout> </layout> 这样图片在显示或者出现的过程中都会添加一个CheckBox位移匀速渐变的动画效果。 需要说明的是,虽然布局文件中CheckBox原本并没有onCheckedChanged属性,但是CheckBox类有对应的属性,并且设有setOnCheckedChanged方法来设置该属性,所以也是可以进行绑定的。 按照惯例,给出Demo源码:https://github.com/sunrongxin7666 欢迎大家指正批评 需要说明的是,由于篇幅的限制,还有动态绑定和在RecyclerView中使用没有介绍,如果感兴趣请读者参考一下两篇文章,基于以上的内容,相信读者不会很难理解。Android DataBinding完全解析精通 Android Data Binding

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

Docker 容器测试全探索

当我们构建好Docker镜像并利用多套容器共同组合成应用程序,建立起持续交付通道,了解了如何将新创建的镜像纳入到生产或者测试环境当中之后,新的问题来了——我们该如何测试自己的Docker容器? 测试的策略多种多样,反映了各种各样的测试性格:天真型,懒人省事型,超前理想主义型,完美主义处女座型……那么你是哪一型? 下面我们就对其各自的方案利弊进行逐一分析。 “天真”型方案 大多数人会将此作为默认方案。其利用CI服务器实现任务执行。在这项方案中,开发人员利用Docker作为软件包管理器,其实际效果优于jar/rpm/deb方案。CI服务器对应用程序代码进行编译,而后执行测试(包括单元、服务及功能等)。Docker中的build可复用以生成新的镜像,由此生成的镜像不仅包含应用程序的“二进制代码”,同时亦拥有运行时所必需的依赖性及配置。 不过为了实现应用程序的可移植性,我们需要放弃开发与测试的可移植能力。在这种情况下,我们无法在CI之外重新建立同样的开发与测试环境。为了创始这样一套新的测试环境,我们需要设置测试工具(正确版本与插件)、配置运行时与操作系统设定,同时获取相同版本的测试脚本与测试数据。 为了解决上述难题,我们需要考虑以下方案。 应用&测试容器方案 现在我们尝试创建单一捆绑包,其中应用程序“二进制代码”中包含全部必需的软件包、测试工具(包括对应版本)、测试工具插件、测试脚本以及其它各类测试环境元素。 这套方案的优势包括: 测试环境自身拥有可重复性——我们能够在CI、开发、分段或者生产环境当中使用完全相同的测试工具实现完全相同的测试效果。 我们能够立足特定时间点捕捉测试脚本,所以可将其复用于任意环境。 我们不需要对测试工具进行二次设置与配置——其已经成为镜像中的组成部分。 但这种方式也存在着显著弊端: 镜像体积直线增长——这是因为其中包含有测试工具、必要软件包、测试脚本甚至是测试数据。 特定测试配置可能对镜像运行时环境造成污染,甚至引入不必要的依赖性(集成测试中需要用到)。 我们还需要考虑如何处理测试结果与记录日志;如何将其导出以及向哪里导出。 通过以下经过简化的 Dockerfile,我们可以了解上述方案的整个流程。 FROM "<bases image>":"<version>" WORKDIR "<path>" # install packages required to run app and tests RUN apt-get update && apt-get install -y \ "<app runtime> and <dependencies>" \ # add app runtime and required packages "<test tools> and <dependencies>" \ # add testing tools and required packages && rm -rf /var/lib/apt/lists/* # copy app files COPY app app COPY run.sh run.sh # copy test scripts COPY tests tests # copy "main" test command COPY test.sh test.sh # ... EXPOSE, RUN, ADD ... for app and test environment # main app command CMD [run.sh, "<app arguments>"] # it's not possible to have multiple CMD commands, but this is the "main" test command # CMD [/test.sh, "<test arguments>"] 点此下载Dockerfile。 毫无疑问,应该有更好的容器内测试方案可供选择。 测试感知型容器方案 目前,Docker承诺以“Build -> Ship -> Run”这一简单操作完成镜像构建、发布至注册表并在其它位置运行等任务。不过恕我直言,其中还缺少了Test这一重要环节。正确且完整的流程应该是Build -> Test -> Ship -> Run。 ​ 下面让我们看看能够为Docker命令提供“测试友好”型语法与扩展的Dockerfile是如何建立而成的。当然,其中并不涉及真正的语法,只是我个人更偏好这样表述。我定义出了自己的“理想”版本(真的只是理想版本,仅供大家借鉴其中的思想,小数注),大家应该能够看出其中可用于实践的指导思路。 ONTEST [INSTRUCTION] 首先定义一条特殊的ONTEST指令,其与现有ONBUILD指令非常相似。ONTEST指令会向镜像添加一条触发指令,其在随后镜像接受测试时自动执行。任意build指令都可被注册为触发条件。 ONTEST指令可由一条新的docker test命令进行识别。 docker test [OPTIONS] IMAGE [COMMAND] [ARG...] 事实上,docker test命令的语法与docker run命令非常相似,二者只存在一项区别:前者会自动生成一套新的“可测试”镜像,甚至为其提供:-test 标签(向原始镜像标签中添加‘test’标签)。此可测试镜像将由初始应用镜像生成,执行全部build操作,于ONTEST命令后进行定义并执行ONTEST CMD(或者ONTEST ENTRYPOINT)。其中若测试发生错误,docker test命令应当返回一段非零代码。此测试结果应当被写入至自动生成且指向/var/tests/results文件夹的VOLUME。 下面我们来看看经过修改的Dockerfile——其中包含新的ONTEST指令。 FROM "<base image>":"<version>" WORKDIR "<path>" # install packages required to run app RUN apt-get update && apt-get install -y \ "<app runtime> and <dependencies>" \ # add app runtime and required packages && rm -rf /var/lib/apt/lists/* # install packages required to run tests ONTEST RUN apt-get update && apt-get install -y \ "<test tools> and <dependencies>" \ # add testing tools and required packages && rm -rf /var/lib/apt/lists/* # copy app files COPY app app COPY run.sh run.sh # copy test scripts ONTEST COPY tests tests # copy "main" test command ONTEST COPY test.sh test.sh # auto-generated volume for test results # ONTEST VOLUME "/var/tests/results" # ... EXPOSE, RUN, ADD ... for app and test environment # main app command CMD [run.sh, "<app arguments>"] # main test command ONTEST CMD [/test.sh, "<test arguments>"] 点此下载Dockerfile。 如何实现“测试感知容器” 我们相信,Docker应该会将docker-test作为容器管理生命周期中的组成部分。当下我们都需要更为简便的解决方案,因此我在这里提出一套接近于理想情况的实现办法。 如之前所提到,Docker拥有ONBUILD这样一条非常实用的指令。该指令允许我们在已经成功的build之上触发另一build指令。其基本思路是在运行docker-test命令的同时,使用ONBUILD指令。 以下为docker-test命令的执行流程: docker-test将在应用程序Dockerfile当中搜索ONBUILD指令,而后…… 利用初始Dockerfile生成一条临时的Dockerfile.test 执行docker build -f Dockerfile.test [OPTIONS] PATH,其中包含受docker build命令支持的其它选项:-test将自动被添加至tag选项当中。 如果构建成功,则执行 docker run -v ./tests/results:/var/tests/results [OPTIONS] IMAGE:TAG-test [COMMAND] [ARG...] 移除Dockerfile.test文件 那么,为什么不创建一个无需配合ONBUILD指令的Dockerfile.test文件? 这是因为,为了测试正确的镜像(及标签),我们需要保证FROM始终在测试目标的image:tag中得到更新。 不过前面提到的方案仍然存在局限——其不适用于“onbuild”镜像(即用于自动化构建应用的镜像),例如Maven:onbuild。 下面来看一条简单的docker-test命令实现流程。其中强调了一大重要概念:docker-test命令应当能够处理build与run命令选项,同时能够妥善处理错误状况。 #!/bin/bash image="app" tag="latest" echo "FROM ${image}:${tag}" > Dockerfile.test && docker build -t "${image}:${tag}-test" -f Dockerfile.test . && docker run -it --rm -v $(pwd)/tests/results:/var/tests/results "${image}:${tag}-test" && rm Dockerfile.test 让我们把注意力集中在最值得关注的重要部分。 集成测试型容器方案 假设我们的应用程序由数十甚至数百项微服务构建而成,同时假设我们已经拥有一套自动化CI/CD通道,其中每项微服务都由CI进行构建与测试,并在之后被部署到某种环境当中(例如测试、分段或者生产环境)。听起来不错,对吧?我们的CI会对各项微服务进行分别测试——运行单元与服务测试(或者API合同测试)。甚至有可能进行微集成测试——即将测试运行在特设的子系统之上(例如使用docker compose)。 但这又会带来一些新问题: • 实际集成测试或者长期运行测试该如何完成(例如性能与压力测试)? • 弹性测试该如何实现(例如‘混乱猴子’测试)? • 如何实现安全扫描? • 那些需要耗费较长时间且运行在完整操作系统之上的测试与扫描要如何完成? 应该有一种更好的办法来替代这种直接将新的微服务版本交付至生产环境的作法,我们也需要更为密切的监控手段。 应当存在一类特殊的集成测试容器。这些容器将仅包含测试工具与测试元素:测试脚本、测试数据、测试环境配置等等。为了简化此类容器的编排与自动化流程,我们应当定义并遵循某些约定并使用元数据标签(Dockerfile中的LABEL指令)。 集成测试标签 test.type – 测试类型,负责定义integration; 可属于 integration, performance, security, chaos 或者其它任意文本之一; 此标签代表其属于一套集成测试容器 test.results – 用于存放测试结果的VOLUME ; 默认位置为 /var/tests/results test.XXX -任何其它相关元数据,仅使用test.后缀名作为标签名称 集成测试容器 集成测试容器其实就是一种常规Docker容器,其中不包含任何应用程序逻辑及代码。它的惟一用途就是创建可重复且可移植的测试流程。以下为建议纳入集成测试容器的内容: 测试工具 - Phantom.js, Selenium, Chakram, Gatling, … 测试工具运行时 - Node.js, JVM, Python, Ruby, … 测试管理配置 – 环境变量, 配置文件, 引导脚本, … 测试 -作为经过编译的软件包或者脚本文件存在 测试数据 – 任何用于测试的数据文件类型: json, csv, txt, xml, … 测试启动脚本 -用于运行测试的部分“main”启动脚本,仅负责创建test.sh并借此启动该测试工具。 集成测试容器应当运行在全部微服务都已经部署到位的运营环境之下,包括测试、分段或者生产环境。这些容器可与其它服务采取完全一致的部署方式。其利用同样的网络层,因此能够访问多项其它服务;使用选定的服务发现方法(通常为DNS)。在实际集成测试当中,我们必须对多项服务进行访问——旨在模拟并验证自身系统是否能够在多种不同环境下正常运行。将集成测试纳入应用服务容器不仅会增加容器自身体积,同时亦会在各服务之间带来不必要的依赖性。因此,我们将所有依赖性都限制在集成测试容器当中。一旦测试(以及相关测试工具)被打包在该容器内,我们亦可在包括开发者设备在内的任意环境下重复运行同样的测试流程。大家还能够实现回滚,即根据需要运行任意集成测试容器版本。 原文发布时间为:2016-08-17 本文来自云栖社区合作伙伴“Linux中国”

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

hadoop 2.6全分布安装

环境:centos 6.6 + hadoop2.6 虚拟机:(vmware fusion 7.0.0) 虚拟机hostname / IP地址 master / 192.168.187.102 slave01 / 192.168.187.103 slave02 / 192.168.187.104 注:前三个步骤都是准备工作,如果已经完成,可直接进入步骤四 步骤一: 先在3台虚拟机上,设置hostname及修改hosts文件 1.1 先正确设置各虚拟机的hostname sudo vi /etc/sysconfig/network 将HOSTNAME=后的内容,改成想要的机器名 1.2 修改hosts文件 sudo vi /etc/hosts 192.168.187.102 master 192.168.187.103 slave01 192.168.187.104 slave02 这样,就不用记IP了 这一步完成后,最好重启一次系统,以便生效。然后可以用ping master(或slave01、slave02)试下,正常的话,应该能ping通 注:hostname不要命名为“xxx.01,xxx.02”之类以“.数字”结尾,否则到最后hadoop的NameNode服务将启动失败。 步骤二:在3台虚拟机上安装jdk yum install java 注:hadoop是java写的,jdk是必须的。上述命令,会安装openjdk最基本的运行时,没有源码和javac等其它工具。如果要安装javac等编译工具及源码 yum install java-1.7.0-openjdk-devel yum install java-1.7.0-openjdk-src 另外openjdk安装后,不会默许设置JAVA_HOME环境变量,要查看安装后的目录,可以用命令 sudo update-alternatives --config java 看到 默认jre目录为:/usr/lib/java/jvm/jre-1.7.0-openjdk.x86_64 如要设置环境变量,可用命令 sudo vi /etc/profile 在最后添加 export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75.x86_64 export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/jre/lib/dt.jar:$JAVA_HOME/jre/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin 注:如果不喜欢openjdk,想安装oracle官方的jdk,可按下面步骤操作 a) rpm -qa | grep java 即:查看当前已经安装的跟java相关的包,如果显示有openjdk在其中 b) yum -y remove java 一般这样就会卸载干净,执行完以后,再运行 a)中的rpm -qa | grep java看看,如果还有残留,可运行 rpm -e --nodeps tzdata-java-2015a-1.el6.noarch (最后的部分为要删除的包名,即刚才查出来的结果) 重新安装sun jdk,先从官网下载安装文件 a) 上传rpm安装文件到虚拟机,参考下面的命令 scp jdk-7u51-linux-x64.rpm root@master:/home/hadoop/ b) 到虚拟机上,执行安装 rpm -ivh jdk-7u51-linux-x64.rpm c) 然后修改环境变量 sudo vi /etc/profile,在最后加下面三行 export JAVA_HOME=/usr/java/jdk1.7.0_51 export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin 保存退出,然后输入 source /etc/profile 以便让新环境变量生效 注:如果机器上之前用 yum install hadoop之类的命令安装过低版本的hadoop,也必须先卸载干净 步骤三:在3台虚拟机上创建专门用户hadoop useradd hadoop (创建用户) passwd hadoop (设置密码,为简单起见,3台机器上的hadoop密码最好设置成一样,比如hadoop123) 为了方便,建议将hadoop加入root用户组,操作方法: 先以root身份登录,然后输入 usermod -g root hadoop ,执行完后hadoop即归属于root组了,可以再输入 id hadoop 查看输出验证一下,如果看到类似下面的输出: uid=502(hadoop) gid=0(root) 组=0(root) 就表示OK了 步骤四:配置ssh免密码登录 hadoop工作时,各节点要相互通讯,正常情况下linux之间通讯要提供用户名、密码(目的是保证通讯安全),如果需要人工干预输入密码,显然不方便,做这一步的目的,是让各节点能自动通过安全认证,不影响正常通讯。 4.1 先在master上,生成公钥、私钥对 以hadoop身份登录到系统 cd (进入个人主目录,默认为/home/hadoop) ssh-keygen -t rsa -P '' (注:最后是二个单引号) 即:以rsa算法,生成公钥、私钥对,-P ''表示空密码。该命令运行完后,会在个人主目录下生成.ssh目录,里面会有二个文件id_rsa(私钥) ,id_rsa.pub(公钥) 4.2 导入公钥 cat .ssh/id_rsa.pub >> .ssh/authorized_keys 执行完以后,可以在本机上测试下,用ssh连接自己,即:ssh localhost (或ssh master),如果不幸还是提示要输入密码,说明还没起作用,还有一个关键的操作 chmod 600 .ssh/authorized_keys (修改文件权限,否则不起作用) 然后再测试下 ssh localhost ,如果不需要输入密码,就连接成功,表示ok,一台机器已经搞定了。 4.3 在其它机器上生成公钥、密钥,并将公钥文件复制到master a) 以hadoop身份登录其它二台机器 slave01、slave02,执行 ssh-keygen -t rsa -P '' 生成公钥、密钥 b) 然后用scp命令,把公钥文件发放给master(即:刚才已经搞定的那台机器) slave01上: scp .ssh/id_rsa.pub hadoop@master:/home/hadoop/id_rsa_01.pub slave02上: scp .ssh/id_rsa.pub hadoop@master:/home/hadoop/id_rsa_02.pub 这二行执行完后,回到master中,查看下/home/hadoop目录,应该有二个新文件id_rsa_01.pub、id_rsa_02.pub,然后在master上,导入这二个公钥 cat id_rsa_01.pub >> .ssh/authorized_keys cat id_rsa_02.pub >> .ssh/authorized_keys 这样,master这台机器上,就有所有3台机器的公钥了。 4.4 将master上的“最全”公钥,复制到其它机器 a) 继续保持在master上, scp .ssh/authorized_keys hadoop@slave01:/home/hadoop/.ssh/authorized_keys scp .ssh/authorized_keys hadoop@slave02:/home/hadoop/.ssh/authorized_keys b) 修改其它机器上authorized_keys文件的权限 slave01以及slave02机器上,均执行命令 chmod 600 .ssh/authorized_keys 4.5 验证 在每个虚拟机上,均用 ssh 其它机器的hostname 验证下,如果能正常无密码连接成功,表示ok 小结:该步骤非常重要,主要思路是在各节点上生成公钥、私钥,然后将公钥发放其它所有节点。RSA算法是非对称加密算法,仅公布“公钥”,只要私钥不外泄,还是不能解密的,所以安全性依然有保障。 如果本步骤失败,根据我个人经验,多半是权限问题,请检查hadoop是否具有足够权限,建议将hadoop加入sudoers列表及root用户组。另外,这里也整理了一些SSH免密码设置失败的原因,请移步ssh 免密码设置失败原因总结 步骤五:上传并解压hadoop2.6 a)在本机上,用scp命令上传hadoop2.6到master scphadoop-2.6.0.tar.gz hadoop@master:/home/hadoop/ b) 以hadoop身份登录到master,运行以下命令解压 tar -zxvfhadoop-2.6.0.tar.gz 步骤六:修改配置 一共有7个文件要修改: $HADOOP_HOME/etc/hadoop/hadoop-env.sh $HADOOP_HOME/etc/hadoop/yarn-env.sh $HADOOP_HOME/etc/hadoop/core-site.xml $HADOOP_HOME/etc/hadoop/hdfs-site.xml $HADOOP_HOME/etc/hadoop/mapred-site.xml $HADOOP_HOME/etc/hadoop/yarn-site.xml $HADOOP_HOME/etc/hadoop/slaves 其中$HADOOP_HOME表示hadoop根目录,本文中默认为/home/hadoop/hadoop-2.6.0 a) hadoop-env.sh 、yarn-env.sh 这二个文件主要是修改JAVA_HOME后的目录,改成实际本机jdk所在目录位置 vi etc/hadoop/hadoop-env.sh (及 vi etc/hadoop/yarn-env.sh) 找到下面这行的位置,改成(jdk目录位置,大家根据实际情况修改) export JAVA_HOME=/usr/java/jdk1.7.0_51 另外 hadoop-env.sh中 , 建议加上这句: export HADOOP_PREFIX=/home/hadoop/hadoop-2.6.0 b) core-site.xml 参考下面的内容修改: 1 <?xml version="1.0" encoding="UTF-8"?> 2 <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> 3 <configuration> 4 <property> 5 <name>fs.defaultFS</name> 6 <value>hdfs://master:9000</value> 7 </property> 8 <property> 9 <name>hadoop.tmp.dir</name> 10 <value>/home/hadoop/tmp</value> 11 </property> 12 </configuration> 注:/home/hadoop/tmp 目录如不存在,则先mkdir手动创建 core-site.xml的完整参数请参考 http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-common/core-default.xml c) hdfs-site.xml 1 <?xml version="1.0" encoding="UTF-8"?> 2 <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> 3 <configuration> 4 <property> 5 <name>dfs.datanode.ipc.address</name> 6 <value>0.0.0.0:50020</value> 7 </property> 8 <property> 9 <name>dfs.datanode.http.address</name> 10 <value>0.0.0.0:50075</value> 11 </property> 12 <property> 13 <name>dfs.replication</name> 14 <value>2</value> 15 </property> 16 </configuration> 注:dfs.replication表示数据副本数,一般不大于datanode的节点数。 hdfs-site.xml的完整参数请参考 http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml d)mapred-site.xml 1 <?xml version="1.0"?> 2 <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> 3 <configuration> 4 <property> 5 <name>mapreduce.framework.name</name> 6 <value>yarn</value> 7 </property> 8 </configuration> mapred-site.xml的完整参数请参考 http://hadoop.apache.org/docs/r2.6.0/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml e)yarn-site.xml 1 <?xml version="1.0"?> 2 <configuration> 3 <property> 4 <name>yarn.nodemanager.aux-services</name> 5 <value>mapreduce_shuffle</value> 6 </property> 7 </configuration> yarn-site.xml的完整参数请参考 http://hadoop.apache.org/docs/r2.6.0/hadoop-yarn/hadoop-yarn-common/yarn-default.xml 另外,hadoop 1.x与2.x相比, 1.x中的很多参数已经被标识为过时,具体可参考 http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-common/DeprecatedProperties.html 最后一个文件slaves暂时不管(可以先用mv slaves slaves.bak将它改名),上述配置弄好后,就可以在master上启用 NameNode测试了,方法: $HADOOP_HOME/bin/hdfs namenode –format 先格式化 15/02/12 21:29:53 INFO namenode.FSImage: Allocated new BlockPoolId: BP-85825581-192.168.187.102-1423747793784 15/02/12 21:29:53 INFO common.Storage: Storage directory /home/hadoop/tmp/dfs/name has been successfully formatted. 等看到这个时,表示格式化ok $HADOOP_HOME/sbin/start-dfs.sh 启动完成后,输入jps查看进程,如果看到以下二个进程: 5161 SecondaryNameNode 4989 NameNode 表示master节点基本ok了 再输入$HADOOP_HOME/sbin/start-yarn.sh ,完成后,再输入jps查看进程 5161 SecondaryNameNode 5320 ResourceManager 4989 NameNode 如果看到这3个进程,表示yarn也ok了 f) 修改slaves 如果刚才用mv slaves slaves.bak对该文件重命名过,先运行 mv slaves.bak slaves 把名字改回来,再 vi slaves 编辑该文件,输入 slave01 slave02 保存退出,最后运行 $HADOOP_HOME/sbin/stop-dfs.sh $HADOOP_HOME/sbin/stop-yarn.sh 停掉刚才启动的服务 步骤七:将master上的hadoop目录复制到slave01,slave02 仍然保持在master机器上 cd 先进入主目录 scp -r hadoop-2.6.0 hadoop@slave01:/home/hadoop/ scp -r hadoop-2.6.0 hadoop@slave02:/home/hadoop/ 注:slave01、slave02上的hadoop临时目录(tmp)及数据目录(data),仍然要先手动创建。 步骤八:验证 master节点上,重新启动 $HADOOP_HOME/sbin/start-dfs.sh $HADOOP_HOME/sbin/start-yarn.sh 顺利的话,master节点上有几下3个进程: 7482 ResourceManager 7335 SecondaryNameNode 7159 NameNode slave01、slave02上有几下2个进程: 2296 DataNode 2398 NodeManager 同时可浏览: http://master:50070/ http://master:8088/ 查看状态 另外也可以通过 bin/hdfs dfsadmin -report 查看hdfs的状态报告 其它注意事项: a) master(即:namenode节点)若要重新格式化,请先清空各datanode上的data目录(最好连tmp目录也一起清空),否则格式化完成后,启动dfs时,datanode会启动失败 b) 如果觉得master机器上只运行namenode比较浪费,想把master也当成一个datanode,直接在slaves文件里,添加一行master即可 c) 为了方便操作,可修改/etc/profile,把hadoop所需的lib目录,先加到CLASSPATH环境变量中,同时把hadoop/bin,hadoop/sbin目录也加入到PATH变量中,可参考下面的内容: export HADOOP_HOME=/home/hadoop/hadoop-2.6.0 export JAVA_HOME=/usr/java/jdk1.7.0_51 export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$HADOOP_HOME/share/hadoop/common/hadoop-common-2.6.0.jar:$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.6.0.jar:$HADOOP_HOME/share/hadoop/common/lib/commons-cli-1.2.jar export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/sbin:$HADOOP_HOME/bin 参考文档:Hadoop MapReduce Next Generation - Cluster Setup

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册