首页 文章 精选 留言 我的

精选列表

搜索[文档解析],共10000篇文章
优秀的个人博客,低调大师

源码解析Flask的配置文件

在flask里,我们常在主文件中定义某些配置,比如: app.debug = True app.secret_key = 'helloworld!!' 实际上,flask中默认可以进行可选的配置项有很多。 如果在开发的过程中,把所有需要的配置项都定义在主文件中,就会造成整个程序的目录结构不合理, 如果需要重写的flask配置项很多的时候,就可以把配置项用别的方式进行定义,然后导入使用 flask的配置文件是一个flask.config.Config对象 导入Config对象,可以发现Config对象继承字典, Config默认的配置有: default_config = ImmutableDict({ 'DEBUG': get_debug_flag(default=False), 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }) 通过查看Config对象的源码,可以知道flask的配置可以有以下几种方式 1.在主文件中定义(通常使用的方式) app.debug = True app.secret_key = 'helloworld!!' 由于Config对象继承了dict的方法和属性,所以还可以使用app.config.update(配置项)的方式导入配置项 2.从环境变量中导入配置项 导入配置项的方式: app.config.from_envvar("环境变量名称") from_envvar方法的源码: def from_envvar(self, variable_name, silent=False): rv = os.environ.get(variable_name) if not rv: if silent: return False raise RuntimeError('The environment variable %r is not set ' 'and as such configuration could not be ' 'loaded. Set this variable and make it ' 'point to a configuration file' % variable_name) return self.from_pyfile(rv, silent=silent) 可以看到,从环境变量中导入配置项的方法,就是从环境变量中找到并读取对应的py文件名称,然后内部调用from_pyfile方法处理读取到的内容得到配置 3.从python文件中导入 从python文件中获取配置项的方式: app.config.from_pyfile("python文件名称") 例如,创建一个名为setting.py的文件 setting.py文件的内容为: DEBUG=True 然后使用app.config.from_pyfile("setting.py")的方式导入配置项 from_pyfile方法的源码: def from_pyfile(self, filename, silent=False): filename = os.path.join(self.root_path, filename) d = types.ModuleType('config') d.__file__ = filename try: with open(filename, mode='rb') as config_file: exec(compile(config_file.read(), filename, 'exec'), d.__dict__) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False e.strerror = 'Unable to load configuration file (%s)' % e.strerror raise self.from_object(d) return True 从py文件中导入配置项的过程中,读取参数中的python文件的内容,进行编译后exec方法执行,就得到所需要的配置项 需要注意的是: python文件可以是绝对路径或者相对路径,如果是相对路径,则py文件必须放在root_path目录下, 4.从对象中导入配置项 from_object方法的源码: def from_object(self, obj): if isinstance(obj, string_types): obj = import_string(obj) for key in dir(obj): if key.isupper(): self[key] = getattr(obj, key) 从对象中导入配置项的过程中,首先判断所传入的对象名是否是字符串,然后调用import_string方法处理字符串形式的对象名 import_string方法的源码: def import_string(import_name, silent=False): import_name = str(import_name).replace(':', '.') try: try: __import__(import_name) except ImportError: if '.' not in import_name: raise else: return sys.modules[import_name] module_name, obj_name = import_name.rsplit('.', 1) try: module = __import__(module_name, None, None, [obj_name]) except ImportError: module = import_string(module_name) try: return getattr(module, obj_name) except AttributeError as e: raise ImportError(e) except ImportError as e: if not silent: reraise( ImportStringError, ImportStringError(import_name, e), sys.exc_info()[2]) 可以看到,import_string方法,实际上是对字符串形式的对象名执行rsplit方法,得到模块名和对象名 在模块可以被正常导入之前,不停执行import_string方法,最后执行getattr方法从模块中获取对象名 5.from_json:从json字符串中获取配置项 from_json方法的源码: def from_json(self, filename, silent=False): filename = os.path.join(self.root_path, filename) try: with open(filename) as json_file: obj = json.loads(json_file.read()) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False e.strerror = 'Unable to load configuration file (%s)' % e.strerror raise return self.from_mapping(obj) 从json文件中获取配置项,实际上就是对json文件执行json.loads方法,得到对象 然后内部调用from_mapping方法处理所得到的对象 6.from_mapping:从dict字典中获取配置项 from_mapping方法的源码: def from_mapping(self, *mapping, **kwargs): mappings = [] if len(mapping) == 1: if hasattr(mapping[0], 'items'): mappings.append(mapping[0].items()) else: mappings.append(mapping[0]) elif len(mapping) > 1: raise TypeError( 'expected at most 1 positional argument, got %d' % len(mapping) ) mappings.append(kwargs.items()) for mapping in mappings: for (key, value) in mapping: if key.isupper(): self[key] = value return True 把参数字典中的所有键值对添加到列表串,循环遍历列表,读取列表中每个元素的键和值 如果键为大写,则key为配置选项,value为配置选项的值 7.get_namespace:从名称空间中获取配置选项 get_namespace源码: def get_namespace(self, namespace, lowercase=True, trim_namespace=True): rv = {} for k, v in iteritems(self): if not k.startswith(namespace): continue if trim_namespace: key = k[len(namespace):] else: key = k if lowercase: key = key.lower() rv[key] = v return rv get_namespace方法,是从指定的名称空间或前缀中进行匹配,返回包含配置项的子集的字典 迭代当前对象,获取key和v,把key转换为小写格式,然后把key和v包含在一个字典中

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

摩拜单车爬虫源码及解析

为什么爬摩拜的数据 摩拜是最早进入成都的共享单车,每天我从地铁站下来的时候,在APP中能看到很多单车,但走到那里的时候,才发现车并不在那里。有些车不知道藏到了哪里;有些车或许是在高楼的后面,由于有GPS的误差而找不到了;有些车被放到了小区里面,一墙之隔让骑车人无法获得到车。 那么有没有一个办法通过获得这些单车的数据,来分析这些车是否变成了僵尸车?是否有人故意放到小区里面让人无法获取呢? 带着这些问题,我开始了研究如何获取这些数据。 从哪里获得数据 如果你能够看到数据,那么我们总有办法自动化的获取到这些数据。只不过获取数据的方式方法决定了获取数据的效率,对于摩拜单车的数据分析这个任务而言,这个爬虫要能够在短时间内(通常是10分钟左右)获取到更多的数据,对于数据分析才有用处。那么数据来源于哪里? 最直接的来源是摩拜单车的APP。现代的软件设计

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

Unity加载模块深度解析(纹理篇)

资源加载 资源加载是加载模块中最为耗时的部分,其CPU开销在Unity引擎中主要体现在Loading.UpdatePreloading和Loading.ReadObject两项中,相信经常查看Profiler的朋友对这两项肯定毫不陌生了。 Loading.UpdatePreloading,这一项仅在调用类似LoadLevel(Async)的接口处出现,主要负责卸载当前场景的资源,并且加载下一场景中的相关资源和序列化信息等。下一场景中,自身所拥有的GameObject和资源越多,其加载开销越大。 在很多项目中,存在另外一种加载方式,即场景为空场景,绝大部分资源和GameObject都是通过OnLevelWasLoaded回调函数中进行加载、实例化和拼合的。对于这种情况,Loading.UpdatePreloading的加载开销会很小。 Loading.ReadObject,这一项记录的则是资源加载时的真正资源读取性能开销,基本上引擎的主流资源(纹理资源、网格资源、动画片段等等)读取均是通过该项来进行体现。可以说,这一项很大程度上决定了项目场景的切换效率。正因如此,我们就当前项目中所用的主流资源进行了大量的测试和分析,下面我们将分析结果与大家一起分享,希望可以帮到正在进行开发的你。 注意事项:本篇文章的资源性能分析主要是针对移动项目而言,因为目前UWA所测评的项目中,移动游戏/应用占比在90%以上。所以,我们选择首先在移动设备上针对每种资源的加载性能进行分析和总结。 资源加载性能测试代码 以下为我们测试时所使用的测试代码,我们将每种资源均制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到不同硬件设备上的资源加载性能比较。 测试环境 引擎版本:Unity 5.2版本 测试设备:五台不同档次的移动设备(Android:红米2、红米Note2和三星S6,iOS:iPhone 5 和 iPhone 6) 纹理资源 纹理资源是项目加载过程中开销占用最大的资源之一,其加载效率由其自身大小决定。目前,决定纹理资源大小的因素主要有三种:分辨率、格式和Mipmap是否开启。 1. 分辨率 & 格式 分辨率和格式是影响纹理资源加载效率的重要因素,因为这两项的设置对纹理资源的大小影响很大。因此,我们对这两种因素进行了详细的测试: 测试1:相同格式、不同分辨率的加载效率测试 我们选取了两张分辨率为2048x2048的普通纹理资源,并在打成AssetBundle时,将其分辨率最大值分别设置为512x512、1024x1024和2048x20248,纹理格式均设置为ETC1(Android)和PVRTC(iOS)、且关闭Mipmap功能。所以,三组纹理的内存占用分别为256KB、1MB和4MB,其对应AssetBundle大小为156KB、531KB和1.92MB(对于Android平台)、175KB、628KB和2.4MB(对于iOS平台)。Unity 版本为5.2,压缩格式为默认的LZMA压缩。 Android平台测试纹理: 我们在五种不同档次的机型上加载这些纹理资源,为降低偶然性,每台设备上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下表所示。 通过上述测试,我们可以得到以下结论: 1、纹理资源的分辨率对加载性能影响较大,分辨率越高,其加载越为耗时。设备性能越差,其耗时差别越为明显; 2、设备越好,加载效率确实越高。但是,对于硬件支持纹理(ETC1/PVRTC)来说,中高端设备的加载效率差别已经很小,比如图中的红米Note2和三星S6设备,差别已经很不明显。 测试2:不同格式、相同分辨率的加载效率测试 我们选取了两张分辨率为1024x1024的普通纹理资源,并在打成AssetBundle时,根据不同平台将其纹理格式分别设置不同格式用于打包。对于Android平台,我们使用ETC1、ETC2、RGBA16和RGBA32四种格式,对于iOS平台,我们使用PVRTC 4BPP、RGBA16和RGBA32三种格式,同时,对于每张纹理均关闭Mipmap功能。所以,三组纹理的内存占用分别为1MB、1MB、4MB 和 8MB(Android平台)/1MB、4MB 和 8MB(iOS平台)。 Android平台测试纹理: 与测试1相同,我们在五种不同档次的机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示。Android设备:iOS设备: 通过上述测试,我们可以得到以下结论: 1、纹理资源的格式对加载性能影响同样较大,Android平台上,ETC1和ETC2的加载效率最高。同样,iOS平台上,PVRTC 4BPP的加载效率最高。 2、RGBA16格式纹理的加载效率同样很高,与RGBA32格式相比,其加载效率与ETC1/PVRTC非常接近,并且设备越好,加载开销差别越不明显; 3、RGBA32格式纹理的加载效率受硬件设备的性能影响较大,ETC/PVRTC/RGBA16受硬件设备的影响较低。 注意事项:这里需要指出的是测试中所使用的ETC1和ETC2纹理均为RGB 4Bit格式,所以对于半透明纹理贴图,需要两张ETC1格式的纹理进行支持(一张RGB通道,一张Alpha通道)。逐一加载两张ETC1格式的纹理,其加载效率要低于RGBA16格式,但可以通过加载方式来进行弥补。这一点我们将在后续文章中进行详细说明。 2. 开启Mipmap功能 开启Mipmap功能同样会增大一部分纹理大小,一般来说,其内存会增加至原始大小的1.33倍。因此,我们对开启Mipmap功能前后的加载性能进行了详细的测试: 测试3:开启/关闭Mipmap功能的加载效率测试 我们仍然使用两张分辨率为1024x1024的普通纹理资源,分别使用ETC1格式、PVRTC格式、RGBA16格式和RGBA32格式(测试所用纹理与测试2相同),并在打成AssetBundle时,一组开启Mipmap功能,一组关闭Mipmap功能。 与测试1相同,我们在五种不同档次的机型上重复进行十次加载操作并将取其平均值作为最终性能开销。具体测试结果如下图所示。Android平台:iOS平台: 通过上述测试,我们可以看出:开启Mipmap功能会导致资源加载更为耗时,且设备性能越差,其加载效率影响越大。 通过以上性能测试,我们对于纹理资源的加载建议如下: 1、严格控制RGBA32和ARGB32纹理的使用,在保证视觉效果的前提下,尽可能采用“够用就好”的原则,降低纹理资源的分辨率,以及使用硬件支持的纹理格式。 2、在硬件格式(ETC、PVRTC)无法满足视觉效果时,RGBA16格式是一种较为理想的折中选择,既可以增加视觉效果,又可以保持较低的加载耗时。 3、严格检查纹理资源的Mipmap功能,特别注意UI纹理的Mipmap是否开启。在UWA测评过的项目中,有不少项目的UI纹理均开启了Mipmap功能,不仅造成了内存占用上的浪费,同时也增加了不小的加载时间。 4、ETC2对于支持OpenGL ES3.0的Android移动设备来说,是一个很好的处理半透明的纹理格式。但是,如果你的游戏需要在大量OpenGL ES2.0的设备上进行运行,那么我们不建议使用ETC2格式纹理。因为不仅会造成大量的内存占用(ETC2转成RGBA32),同时也增加一定的加载时间。下图为测试2中所用的测试纹理在三星S3和S4设备上加载性能表现。可以看出,在OpenGL ES2.0设备上,ETC2格式纹理的加载要明显高于ETC1格式,且略高于RGBA16格式纹理。因此,建议研发团队在项目中谨慎使用ETC2格式纹理。 正是由于以上加载效率问题,我们在UWA测评报告中加入了对每个检测到的纹理资源参数的详细展示,以方便开发团队可以快速查看资源的使用情况,只需对相关信息列进行排序,即可定位引发性能问题的具体资源。 说明:以上测试数据为我们所用的测试纹理加载数据,需要指出的是,不同纹理的加载效率是不相同的,因为其内容的不同会造成AssetBundle压缩包大小的不同,进而造成最终加载效率的不同。这里我们给出的具体性能比较,其本意是让大家通过数据直观了解到纹理格式、分辨率和Mipmap功能对加载性能的影响。另外,我们后续会进行更多的测试,以期为大家提供更为普遍的测试结果。 以上为纹理资源在加载时的性能测试。关于加载模块的性能问题,我们会不断推出网格、音频等其他资源的加载性能分析、资源卸载性能分析、资源实例化性能分析、不同加载方式的性能分析等一系列技术文章,并对目前UWA所检测过项目的共性问题进行总结,以期让大家对项目的加载效率有更加深入的认知,并提升对加载模块的掌控能力。 原文出处:侑虎科技 转载请与作者联系,同时请务必标明文章原始出处和原文链接及本声明。

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

C++ 虚函数表解析

转自:http://blog.csdn.net/haoel/article/details/1948051 本人修改部分(添加了代码验证) 在此感谢原作者 ,写出这么好的文章 (虽然转的,但是文章确实不错,所以还是脸皮厚点发到首页......,管理员,你看着办吧!!!) 前言 C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。 关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。 当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。 言归正传,让我们一起进入虚函数的世界。 虚函数表 C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。 这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。 听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。 假设我们有这样的一个类: class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; 按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程: typedef void(*Fun)(void); Base b; Fun pFun = NULL; cout << "虚函数表地址:" << (int*)(&b) << endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; // Invoke the first virtual function pFun = (Fun)*((int*)*(int*)(&b)); pFun(); 实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3) 虚函数表地址:0012FED4 虚函数表 — 第一个函数地址:0044F148 Base::f 通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int*强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下: (Fun)*((int*)*(int*)(&b)+0); // Base::f() (Fun)*((int*)*(int*)(&b)+1); // Base::g() (Fun)*((int*)*(int*)(&b)+2); // Base::h() 这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示: 注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。 下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。 一般继承(无虚函数覆盖) 下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系: 请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示: 对于实例:Derive d; 的虚函数表如下: 代码验证如下: // CppTest.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> using namespace std; class Base{ public: virtual void f(){cout<<"Base::f\n";} virtual void g(){cout<<"Base::g\n";} virtual void h(){cout<<"Base::h\n";} }; class Derive:public Base{ public: virtual void f1(){cout<<"Derive:f1\n";} virtual void g1(){cout<<"Derive:g1\n";} virtual void h1(){cout<<"Derive:h1\n";} }; int _tmain(int argc, _TCHAR* argv[]) { typedef void(*FUN)(void); Base b; FUN pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&b)<<endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*(int*)(&b)+i); pFun(); } cout<<"-----------------------------------------"<<endl; Derive d; pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&d)<<endl; cout<<"虚函数表 — 第一个函数地址:"<<(int*)*(int*)(&d)<<endl; for (int i=0;i<6;++i) { pFun=(FUN)*((int*)*(int*)(&d)+i); pFun(); } return 0; } 程序的输出为: 我们可以看到下面几点: 1)虚函数按照其声明顺序放于表中。 2)父类的虚函数在子类的虚函数前面。 一般继承(有虚函数覆盖) 覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。 为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子: 代码验证如下: #include <iostream> using namespace std; class Base{ public: virtual void f(){cout<<"Base::f\n";} virtual void g(){cout<<"Base::g\n";} virtual void h(){cout<<"Base::h\n";} }; class Derive:public Base{ public: virtual void f(){cout<<"Derive:f\n";} virtual void g1(){cout<<"Derive:g1\n";} virtual void h1(){cout<<"Derive:h1\n";} }; int _tmain(int argc, _TCHAR* argv[]) { typedef void(*FUN)(void); Base b; FUN pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&b)<<endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*(int*)(&b)+i); pFun(); } cout<<"-----------------------------------------"<<endl; Derive d; pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&d)<<endl; cout<<"虚函数表 — 第一个函数地址:"<<(int*)*(int*)(&d)<<endl; for (int i=0;i<5;++i) { pFun=(FUN)*((int*)*(int*)(&d)+i); pFun(); } return 0; } 我们从表中可以看到下面几点, 1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。 2)没有被覆盖的函数依旧。 这样,我们就可以看到对于下面这样的程序: Base *b = new Derive(); b->f(); 由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。 多重继承(无虚函数覆盖) 下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。 对于子类实例中的虚函数表,是下面这个样子: 代码验证如下: #include <iostream> using namespace std; class Base1{ public: virtual void f(){cout<<"Base1::f\n";} virtual void g(){cout<<"Base1::g\n";} virtual void h(){cout<<"Base1::h\n";} }; class Base2{ public: virtual void f(){cout<<"Base2::f\n";} virtual void g(){cout<<"Base2::g\n";} virtual void h(){cout<<"Base2::h\n";} }; class Base3{ public: virtual void f(){cout<<"Base3::f\n";} virtual void g(){cout<<"Base3::g\n";} virtual void h(){cout<<"Base3::h\n";} }; class Derive:public Base1,Base2,Base3{ public: virtual void f1(){cout<<"Derive:f1\n";} virtual void g1(){cout<<"Derive:g1\n";} virtual void h1(){cout<<"Derive:h1\n";} }; int _tmain(int argc, _TCHAR* argv[]) { typedef void(*FUN)(void); Derive d; FUN pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&d)<<endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl; for (int i=0;i<6;++i) { pFun = (FUN)*((int*)*(int*)(&d)+i); pFun(); } cout<<"----------------------------"<<endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*((int*)(&d)+1)+i); pFun(); } cout<<"----------------------------"<<endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*((int*)(&d)+2)+i); pFun(); } cout<<"----------------------------"<<endl; return 0; } 结果如下: 多重继承(有虚函数覆盖) 下面我们再来看看,如果发生虚函数覆盖的情况。 下图中,我们在子类中覆盖了父类的f()函数。 下面是对于子类实例中的虚函数表的图: 代码验证如下: #include <iostream> using namespace std; class Base1{ public: virtual void f(){cout<<"Base1::f\n";} virtual void g(){cout<<"Base1::g\n";} virtual void h(){cout<<"Base1::h\n";} }; class Base2{ public: virtual void f(){cout<<"Base2::f\n";} virtual void g(){cout<<"Base2::g\n";} virtual void h(){cout<<"Base2::h\n";} }; class Base3{ public: virtual void f(){cout<<"Base3::f\n";} virtual void g(){cout<<"Base3::g\n";} virtual void h(){cout<<"Base3::h\n";} }; class Derive:public Base1,Base2,Base3{ public: virtual void f(){cout<<"Derive::f\n";} virtual void g1(){cout<<"Derive::g1\n";} virtual void h1(){cout<<"Derive::h1\n";} }; int _tmain(int argc, _TCHAR* argv[]) { typedef void(*FUN)(void); Derive d; FUN pFun=NULL; cout<<"虚函数表的地址为:"<<(int*)(&d)<<endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl; for (int i=0;i<5;++i) { pFun = (FUN)*((int*)*(int*)(&d)+i); pFun(); } cout<<"----------------------------"<<endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*((int*)(&d)+1)+i); pFun(); } cout<<"----------------------------"<<endl; for (int i=0;i<3;++i) { pFun = (FUN)*((int*)*((int*)(&d)+2)+i); pFun(); } cout<<"----------------------------"<<endl; return 0; } 结果为: 我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如: Derive d; Base1 *b1 = &d; Base2 *b2 = &d; Base3 *b3 = &d; b1->f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g( 安全性 每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。 一、通过父类型的指针访问子类自己的虚函数 我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数: Base1 *b1 = new Derive(); b1->f1(); //编译出错 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点) 二、访问non-public的虚函数 另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。 #include <iostream> using namespace std; class Base { private: virtual void f() { cout << "Base::f" << endl; } }; class Derive : public Base{ }; int _tmain(int argc, _TCHAR* argv[]) { typedef void(*Fun)(void); Derive d; Fun pFun = (Fun)*((int*)*(int*)(&d)+0); pFun(); return 0; } 结束语 C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。 附录一:VC中查看虚函数表 我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的) ============================================================================== 本文转自被遗忘的博客园博客,原文链接:http://www.cnblogs.com/rollenholt/archive/2012/04/26/2472050.html,如需转载请自行联系原作者

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

视频监控业务上云方案解析

行业痛点 由于视频监控能最大的记录和还原当被监控的场景,近年来,视频监控逐步从专业领域的应用普及到了各个民用、家用领域,各个摄像相机厂家也纷纷推出各种型号的摄像机和解决方案。由于视频监控数据量大,也会遇到各种问题: 存储资源消耗大且增长迅速。视频监控业务极度消耗存储资源,一个成规模的互联网视频监控服务往往需要PB级甚至更多存储资源,普通IDC或小云服务提供商有限的基础设施很容易成为云视频监控业务爆发增长的瓶颈,一旦出现存储瓶颈,扩容难度大,迁移成本高。 对数据可靠性要求高。监控视频数据是安防监控业务的核心价值数据,必须不能丢失。尤其是在互联网业务模式下,如果能做到视频数据不丢失,安防监控服务提供商就能向其终端用户提供可靠的视频存储服务。 带宽资源消耗巨大,且上行、下行流量不对称。7*24小时写入视频流的视频监控业务是重资产的业务,数万级别

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

sql server中filegroup与partition解析

0.参考文献: SQL SERVER 分区表的总结 SQL Server 2005 分区表实践——建立分区表(partition table) SQL Server中数据库文件的存放方式,文件和文件组(fromCareySon) T-SQL查询进阶--理解SQL SERVER中的分区表(fromCareySon) 1.基础知识 一直对于表分区和filegroup的概念不是很清晰,今天通过具体的实例来学习什么事filegroup和partition,以及他们的作用。 1.1通过文件组来管理文件的特性 对于用户角度来说,需对创建的对象指定存储的文件组只有三种数据对象:表,索引和大对象(LOB) 使用文件组可以隔离用户和文件,使得用户针对文件组来建立表和索引,而不是实际磁盘中的文件。也就是可以指定将表和索引存储在不同的文件上面。 使用文件组来管理文件可以使得同一文件组内的不同文件分布在不同的硬盘中,极大的提高了IO性能. SQL SERVER会根据每个文件设置的初始大小和增长量会自动分配新加入的空间,假设在同一文件组中的文件A设置的大小为文件B的两倍,新增一个数据占用三页(Page),则按比例将2页分配到文件A中,1页分配到文件B中. 1.2文件的分类 首要文件:这个文件是必须有的,而且只能有一个。这个文件额外存放了其他文件的位置等信息.扩展名为.mdf 次要文件:可以建任意多个,用于不同目的存放.扩展名为.ndf,用于存放数据,而不是日志。 日志文件:存放日志,扩展名为.ldf 在SQL SERVER 2008之后,还新增了文件流数据文件和全文索引文件. 我们可以通过sys.database_files这个视图查看数据库中的文件情况: select * from sys.database_files 1.3创建filegroup,并将索引创建在指定的filegroup中 可以通过TSQL语句来创建文件组,也可以通过SSMS来创建文件组,这个在后面会提到。这里不再重复。下面我们重点来介绍如何将索引创建在指定的filegroup中,而不跟数据放在一起。首先来看我创建好的filegroup,已经这些filegroup所对应的files,如下图所示: 然后我们通过如下TSQL语句来测试 View Code 总结: 在分区上创建聚集索引,聚集索引不要放在IndexStorage这个filegroup当中,因为聚集索引就是数据本身。如果将聚集索引on IndexStorage的话,那么所有数据都将会在IndexStorage这个filegroup所对应的文件上。 在创建非聚集索引的时候,通过在创建索引语句的最后加上on [filegroup]指定需要将这个索引放在哪一个filegroup当中,如果不加的话会使用默认filegroup,我们这里的默认filegroup是priamry。 1.4使用多个文件的好处 使用多个文件分布数据到多个硬盘中可以极大的提高IO性能.放在一个磁盘中基本没有效果。 场景描述 应用程序发来大量的并发语句在修改同一张表格里的记录,而表格架构设计以及用户业务逻辑使得这些修改都集中在同一个页面,或者数量不多的几个页面上。这些页面有的时候也被称为Hot Page。这样的瓶颈通常只会发生在并发用户比较多的、典型的OLTP系统上。这种瓶颈是无法通过提高硬件配置解决的,只有通过修改表格设计或者业务逻辑,让修改分散到尽可能多的页面上,才能提高并发性能。 在现实环境里,可以试想下面的情形。一个股票交易系统,每一笔交易都会有一个流水号,是递增且不可重复的。而客户发过来的交易请求,都要存储在同一张交易表里。每一个新的交易,都要插入一条新记录。如果设计者选择在流水号上建聚集索引(这也是很自然的),就容易遇到Hot Page的PAGELATCH资源瓶颈。在同一时间,只能有一个用户插入一笔交易。 怎样才能解决或者缓解这种瓶颈呢? 最简单的方法,是换一个数据列建聚集索引,而不要建在Identity的字段上。这样表格里的数据就按照其他方式排序,同一时间的插入就有机会分散在不同的页面上。 如果实在是一定要在Identity的字段上建聚集索引,建议根据其他某个数据列在表格上建立若干个分区(Partition)。把一个表格分成若干个分区,可以使得接受新数据的页面数目增加。 还是以上面那个股票交易系统为例子。不同的股票属于不同的行业。开发者可以根据股票的行业属性,将一张交易表分成若干个分区。在SQL Server里,已分区表(Partitioned Table)的每个分区都是一个独立的存储单位。分属不同分区的数据行是严格分开存储的。所以同一个时间发生的交易记录,因其行业不同,也会被分别保存在不同的分区里。这样,在同一个时间点,可以插入不同行业的交易记录。每个分区上的Hot Page(接受新数据插入的page)就不那么hot了。 在我的事例中,是有一张SalesOrderDetail表,其数据量很大,我希望按照UnitPrice这个字段进行分区。下面来看具体步骤。 step1:创建filegroup 在sql server中好像没有create filegroup的说法,只是在现成的数据库中添加filegroup而已。下面的代码中首先创建数据库,然后添加四个filegroup,tsql代码如下所示: View Code 执行完以后我们可以在TEST数据库的properties中看到我们添加的四个filegroup,如下图所示: step2:为filegroup添加数据文件 在创建完filegroup以后,我们为每一个filegroup创建一个次要数据文件,因为每一个数据库只能有一个primary datafile,也就是mdf文件,但是可以有多个次要数据文件,也就是.ndf文件。为filegroup创建ndf数据文件的TSQL语句如下图所示: View Code 执行完上述语句以后,我们可以查看TEST数据库的file properties,如下图所示: 我们可以看到四个文件的大小都是2MB。 step3:创建分区函数 在当前数据库中创建一个函数,该函数可根据指定列的值将表或索引的各行映射到分区。使用 CREATE PARTITION FUNCTION 是创建已分区表或索引的第一步。在 SQL Server 2012 中,一张表或一个索引最多可以有 15,000 个分区。 创建分区函数的具体如法如下: View Code 在本事例中,需要有5个分区,本实例创建的分区函数如下所示: View Code 其中RANGE LEFT|RIGHT表示当间隔值由 数据库引擎 按升序从左到右排序时,boundary_value[,...n] 属于每个边界值间隔的哪一侧(左侧还是右侧,就是等于号在哪一边)。如果未指定,则默认值为 LEFT。比如在我的分区函数中指定的间隔是(500,1000,1500,2000),并且是RANGE RIGHT,那么我的范围就是 分区 1 2 3 4 5 值 <500 >=500 and <1000 >=1000 and <1500 >=1500 and <2000 >=2000 step4:建分区架构 创建分区架构的TSQL如下所示: View Code 从上述TSQL中我们可以发现,在创建分区架构的时候关联了分区函数以及具体的5个filegroup。 注意:这里并不一定必须要求有5个filegroup,我们可以填写相同的filegroup,但是需要填写5次filegroup,因为有5个分区。 step5:创建分区表并填充数据 View Code 需要注意的是分区列UnitPrice必须有唯一约束或者是聚集索引。所以在这里我创建聚集索引的时候将UnitPrice列也添加进去了。如果不讲UnitPrice设为聚集索引,也就是让此列唯一,那么在执行上述命令的时候会报如下错误: Msg 1908, Level 16, State 1, Line 2Column 'UnitPrice' is partitioning column of the index 'PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID'. Partition columns for a unique index must be a subset of the index key.Msg 1750, Level 16, State 0, Line 2Could not create constraint. See previous errors. 在执行完上面的操作以后我们再去看看ndf文件有没有变化,如下图所示,我们发现ndf文件大小有增长,这表明已经往这几个分区中写入了数据。 上边的表结构是通过select * into语句来创建表的,这种方式没有普遍性,下面我们通过create table来创建表结构: View Code step5':创建分区表并填充数据 在上面语句中,我们发现: 创建主键约束的时候我们指定使用的是nonclustered index,如果不指明的话那么默认创建的是聚集索引。但是一张表只能有一个聚集索引,而分区列上又必须有聚集索引,所以我们这里要显式声明primary key为nonclustered index。 然后在UnitPrice列上面创建聚集索引,并且指明分区架构,也就是ON [Sch_SalesOrderDetail_UnitPrice]([UnitPrice])。 在创建好表结构以后,我们往里面插入数据。如果一条一条插入数据比较慢的话,我们可以在AdventureWorks2008R2.Sales.SalesOrderDetail表中导入,导入语句如下: View Code 6.查看分区表各分区数据情况(数据行数,最大最小 UnitPrice值) 执行如下查询命令 View Code 其中fn_Partition_SalesOrderDetail_UnitPrice(UnitPrice)是分区函数,UnitPrice是列名。 使用第一种方法,也就是select * into的方法导入数据,其查询结果为: partition rows minval maxval ----------- ----------- --------------------- --------------------- 1 88053 1.3282 469.794 2 12243 539.99 986.5742 3 9582 1000.4375 1466.01 4 939 1700.99 1971.9942 5 10500 2024.994 3578.27 我们可以看到每一个分区上面都有数据。有些总数据量小于2MB,所以ndf文件大小没有改变。如果ndf文件文件大小变化不大,我们可以多执行几次上面的数据导入语句。 使用第二种方法插入数据,一共执行了三次,其最后文件大小如下图所示: 分区上存储的数据统计信息如下: partition rows minval maxval ----------- ----------- --------------------- --------------------- 1 264159 1.3282 469.794 2 36729 539.99 986.5742 3 28746 1000.4375 1466.01 4 2817 1700.99 1971.9942 5 31500 2024.994 3578.27 从上述查询结果我们可以发现在partition4(对应FG_TEST_SalesOrderDetail_UnitPrice_3_data_1.ndf这个次要数据文件)中,数据行只有2817条,这也说明了为什么在上图中只有FG_TEST_SalesOrderDetail_UnitPrice_3_data_1.ndf这个文件大小没有增长,依然是2048KB。 查看partition状态的的四个视图 View Code 本文转自xwdreamer博客园博客,原文链接:http://www.cnblogs.com/xwdreamer/archive/2012/08/30/2664671.html,如需转载请自行联系原作者

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

Android启动过程深入解析【转】

转自:http://www.open-open.com/lib/view/open1403250347934.html 当按下Android设备电源键时究竟发生了什么? Android的启动过程是怎么样的? 什么是Linux内核? 桌面系统linux内核与Android系统linux内核有什么区别? 什么是引导装载程序? 什么是Zygote? 什么是X86以及ARM linux? 什么是init.rc? 什么是系统服务? 当我们想到Android启动过程时,脑海中总是冒出很多疑问。本文将介绍Android的启动过程,希望能帮助你找到上面这些问题的答案。 Android是一个基于Linux的开源操作系统。x86(x86是一系列的基于intel 8086 CPU的计算机微处理器指令集架构)是linux内核部署最常见的系统。然而,所有的Android设备都是运行在ARM处理器(ARM 源自进阶精简指令集机器,源自ARM架构)上,除了英特尔的Xolo设备(http://xolo.in/xolo-x900-features)。Xolo来源自凌动1.6GHz x86处理器。Android设备或者嵌入设备或者基于linux的ARM设备的启动过程与桌面版本相比稍微有些差别。这篇文章中,我将解释Android设备的启动过程。深入linux启动过程是一篇讲桌面linux启动过程的好文。 当你按下电源开关后Android设备执行了以下步骤。 第一步:启动电源以及系统启动 当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行。 第二步:引导程序 引导程序是在Android操作系统开始运行前的一个小程序。引导程序是运行的第一个程序,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如redboot、uboot、qi bootloader或者开发自己的引导程序,它不是Android操作系统的一部分。引导程序是OEM厂商或者运营商加锁和限制的地方。 引导程序分两个阶段执行。第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;第二阶段,引导程序设置网络、内存等等。这些对于运行内核是必要的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。 Android引导程序可以在\bootable\bootloader\legacy\usbloader找到。 传统的加载器包含的个文件,需要在这里说明: init.s初始化堆栈,清零BBS段,调用main.c的_main()函数; main.c初始化硬件(闹钟、主板、键盘、控制台),创建linux标签。 更多关于Android引导程序的可以在这里了解。 第三步:内核 Android内核与桌面linux内核启动的方式差不多。内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。 第四步:init进程 init是第一个进程,我们可以说它是root进程或者说有进程的父进程。init进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行init.rc脚本。 init进程可以在/system/core/init找到。 init.rc文件可以在/system/core/rootdir/init.rc找到。 readme.txt可以在/system/core/init/readme.txt找到。 对于init.rc文件,Android中有特定的格式以及规则。在Android中,我们叫做Android初始化语言。 Android初始化语言由四大类型的声明组成,即Actions(动作)、Commands(命令)、Services(服务)、以及Options(选项)。 Action(动作):动作是以命令流程命名的,有一个触发器决定动作是否发生。 语法 1 2 3 4 on < trigger > < command > < command > < command > Service(服务):服务是init进程启动的程序、当服务退出时init进程会视情况重启服务。 语法 1 2 3 4 service < name > < pathname > [< argument >]* < option > < option > ... Options(选项) 选项是对服务的描述。它们影响init进程如何以及何时启动服务。 咱们来看看默认的init.rc文件。这里我只列出了主要的事件以及服务。 Table Action/Service 描述 on early-init 设置init进程以及它创建的子进程的优先级,设置init进程的安全环境 on init 设置全局环境,为cpu accounting创建cgroup(资源控制)挂载点 on fs 挂载mtd分区 on post-fs 改变系统目录的访问权限 on post-fs-data 改变/data目录以及它的子目录的访问权限 on boot 基本网络的初始化,内存管理等等 service servicemanager 启动系统管理器管理所有的本地服务,比如位置、音频、Shared preference等等… service zygote 启动zygote作为应用进程 在这个阶段你可以在设备的屏幕上看到“Android”logo了。 第五步 在Java中,我们知道不同的虚拟机实例会为不同的应用分配不同的内存。假如Android应用应该尽可能快地启动,但如果Android系统为每一个应用启动不同的Dalvik虚拟机实例,就会消耗大量的内存以及时间。因此,为了克服这个问题,Android系统创造了”Zygote”。Zygote让Dalvik虚拟机共享代码、低内存占用以及最小的启动时间成为可能。Zygote是一个虚拟器进程,正如我们在前一个步骤所说的在系统引导的时候启动。Zygote预加载以及初始化核心库类。通常,这些核心类一般是只读的,也是Android SDK或者核心框架的一部分。在Java虚拟机中,每一个实例都有它自己的核心库类文件和堆对象的拷贝。 Zygote加载进程 加载ZygoteInit类,源代码:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java registerZygoteSocket()为zygote命令连接注册一个服务器套接字。 preloadClassed “preloaded-classes”是一个简单的包含一系列需要预加载类的文本文件,你可以在<Android Source>/frameworks/base找到“preloaded-classes”文件。 preloadResources() preloadResources也意味着本地主题、布局以及android.R文件中包含的所有东西都会用这个方法加载。 在这个阶段,你可以看到启动动画。 第六步:系统服务或服务 完成了上面几步之后,运行环境请求Zygote运行系统服务。系统服务同时使用native以及java编写,系统服务可以认为是一个进程。同一个系统服务在Android SDK可以以System Services形式获得。系统服务包含了所有的System Services。 Zygote创建新的进程去启动系统服务。你可以在ZygoteInit类的”startSystemServer”方法中找到源代码。 核心服务: 启动电源管理器; 创建Activity管理器; 启动电话注册; 启动包管理器; 设置Activity管理服务为系统进程; 启动上下文管理器; 启动系统Context Providers; 启动电池服务; 启动定时管理器; 启动传感服务; 启动窗口管理器; 启动蓝牙服务; 启动挂载服务。 其他服务: 启动状态栏服务; 启动硬件服务; 启动网络状态服务; 启动网络连接服务; 启动通知管理器; 启动设备存储监视服务; 启动定位管理器; 启动搜索服务; 启动剪切板服务; 启动登记服务; 启动壁纸服务; 启动音频服务; 启动耳机监听; 启动AdbSettingsObserver(处理adb命令)。 第七步:引导完成 一旦系统服务在内存中跑起来了,Android就完成了引导过程。在这个时候“ACTION_BOOT_COMPLETED”开机启动广播就会发出去。 原文链接:kpbird翻译:伯乐在线-云海之巅 译文链接:http://blog.jobbole.com/67931/ 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5687411.html,如需转载请自行联系原作者

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

Android Fragment 真正的完全解析(上)

自从Fragment出现,曾经有段时间,感觉大家谈什么都能跟Fragment谈上关系,做什么都要问下Fragment能实现不~~~哈哈,是不是有点过~~~ 本 篇博客力求为大家说明Fragment如何产生,什么是Fragment,Fragment生命周期,如何静态和动态的使用 Fragment,Fragment回退栈,Fragment事务;以及Fragment的一些特殊用途,例如:没有布局的Fragment有何用 处?Fragment如何与Activity交互?Fragment如何创建对话框?Fragment如何与ActionBar集成等等。 1、Fragment的产生与介绍 Android 运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布 局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可 以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是 Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添 加、替换和移除某个Fragment。 2、Fragment的生命周期 Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系: 可以看到Fragment比Activity多了几个额外的生命周期回调方法:onAttach(Activity)当Fragment与Activity发生关联时调用。onCreateView(LayoutInflater, ViewGroup,Bundle)创建该Fragment的视图onActivityCreated(Bundle)当Activity的onCreate方法返回时调用onDestoryView()与onCreateView想对应,当该Fragment的视图被移除时调用onDetach()与onAttach相对应,当Fragment与Activity关联被取消时调用 注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现, 3、静态的使用Fragment 嘿嘿,终于到使用的时刻了~~ 这是使用Fragment最简单的一种方式,把Fragment当成普通的控件,直接写在Activity的布局文件中。步骤: 1、继承Fragment,重写onCreateView决定Fragemnt的布局 2、在Activity中声明此Fragment,就当和普通的View一样 下面展示一个例子(我使用2个Fragment作为Activity的布局,一个Fragment用于标题布局,一个Fragment用于内容布局): TitleFragment的布局文件: [html] view plain copy <?xmlversion="1.0"encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="45dp" android:background="@drawable/title_bar"> <ImageButton android:id="@+id/id_title_left_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="3dp" android:background="@drawable/showleft_selector"/> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:text="我不是微信" android:textColor="#fff" android:textSize="20sp" android:textStyle="bold"/> </RelativeLayout> TitleFragment [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.ImageButton; importandroid.widget.Toast; publicclassTitleFragmentextendsFragment { privateImageButtonmLeftMenu; @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_title,container,false); mLeftMenu=(ImageButton)view.findViewById(R.id.id_title_left_btn); mLeftMenu.setOnClickListener(newOnClickListener() { @Override publicvoidonClick(Viewv) { Toast.makeText(getActivity(), "iamanImageButtoninTitleFragment!", Toast.LENGTH_SHORT).show(); } }); returnview; } } 同理还有ContentFragment的其布局文件: [html] view plain copy <?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:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:text="使用Fragment做主面板" android:textSize="20sp" android:textStyle="bold"/> </LinearLayout> [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.ViewGroup; publicclassContentFragmentextendsFragment { @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { returninflater.inflate(R.layout.fragment_content,container,false); } } MainActivity [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.os.Bundle; importandroid.view.Window; publicclassMainActivityextendsActivity { @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); } } Activity的布局文件: [java] view plain copy <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_title" android:name="com.zhy.zhy_fragments.TitleFragment" android:layout_width="fill_parent" android:layout_height="45dp"/> <fragment android:layout_below="@id/id_fragment_title" android:id="@+id/id_fragment_content" android:name="com.zhy.zhy_fragments.ContentFragment" android:layout_width="fill_parent" android:layout_height="fill_parent"/> </RelativeLayout> 是不是把Fragment当成普通的View一样声明在Activity的布局文件中,然后所有控件的事件处理等代码都由各自的Fragment去处理,瞬间觉得Activity好干净有木有~~代码的可读性、复用性以及可维护性是不是瞬间提升了~~~下面看下效果图: 4、动态的使用Fragment 上面已经演示了,最简单的使用Fragment的方式~下面介绍如何动态的添加、更新、以及删除Fragment 为了动态使用Fragment,我们修改一下Actvity的布局文件,中间使用一个FrameLayout,下面添加四个按钮~~~嘿嘿~~不是微信的按钮- -! [html] view plain copy <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_title" android:name="com.zhy.zhy_fragments.TitleFragment" android:layout_width="fill_parent" android:layout_height="45dp"/> <include android:id="@+id/id_ly_bottombar" android:layout_width="fill_parent" android:layout_height="55dp" android:layout_alignParentBottom="true" layout="@layout/bottombar"/> <FrameLayout android:id="@+id/id_content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@id/id_ly_bottombar" android:layout_below="@id/id_fragment_title"/> </RelativeLayout> 底部四个按钮的布局就不贴了,到时看效果图就明白了~~ 下面主Activity [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.Window; importandroid.widget.LinearLayout; publicclassMainActivityextendsActivityimplementsOnClickListener { privateLinearLayoutmTabWeixin; privateLinearLayoutmTabFriend; privateContentFragmentmWeixin; privateFriendFragmentmFriend; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); //初始化控件和声明事件 mTabWeixin=(LinearLayout)findViewById(R.id.tab_bottom_weixin); mTabFriend=(LinearLayout)findViewById(R.id.tab_bottom_friend); mTabWeixin.setOnClickListener(this); mTabFriend.setOnClickListener(this); //设置默认的Fragment setDefaultFragment(); } privatevoidsetDefaultFragment() { FragmentManagerfm=getFragmentManager(); FragmentTransactiontransaction=fm.beginTransaction(); mWeixin=newContentFragment(); transaction.replace(R.id.id_content,mWeixin); transaction.commit(); } @Override publicvoidonClick(Viewv) { FragmentManagerfm=getFragmentManager(); //开启Fragment事务 FragmentTransactiontransaction=fm.beginTransaction(); switch(v.getId()) { caseR.id.tab_bottom_weixin: if(mWeixin==null) { mWeixin=newContentFragment(); } //使用当前Fragment的布局替代id_content的控件 transaction.replace(R.id.id_content,mWeixin); break; caseR.id.tab_bottom_friend: if(mFriend==null) { mFriend=newFriendFragment(); } transaction.replace(R.id.id_content,mFriend); break; } //transaction.addToBackStack(); //事务提交 transaction.commit(); } } 可以看到我们使用FragmentManager对Fragment进行了动态的加载,这里使用的是replace方法~~下一节我会详细介绍FragmentManager的常用API。 注: 如果使用Android3.0以下的版本,需要引入v4的包,然后Activity继承FragmentActivity,然后通过 getSupportFragmentManager获得FragmentManager。不过还是建议版Menifest文件的uses-sdk的 minSdkVersion和targetSdkVersion都改为11以上,这样就不必引入v4包了。 代码中间还有两个Fragment的子类,ContentFragment上面已经见过,FriendFragment其实类似: [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.ViewGroup; publicclassFriendFragmentextendsFragment { @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { returninflater.inflate(R.layout.fragment_friend,container,false); } } 效果图: 可以看到很好的实现了效果,其实这个效果以前的博客中也出现过,在博客:Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager,有兴趣可以看看。ps:为了代码的简洁,就不添加按钮的点击变化什么的了,主要讲解功能了~~~ 5、Fragment家族常用的API Fragment常用的三个类: android.app.Fragment 主要用于定义Fragment android.app.FragmentManager 主要用于在Activity中操作Fragment android.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词,一定能明白~ a、获取FragmentManage的方式: getFragmentManager() // v4中,getSupportFragmentManager b、主要的操作都是FragmentTransaction的方法 FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务 transaction.add() 往Activity中添加一个Fragment transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。 transaction.replace() 使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~ replace()参数中的布局ID必须是viewgroup子类的ID transaction.hide() 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁 transaction.show() 显示之前隐藏的Fragment detach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。 attach() 重建view视图,附加到UI上并显示。 transatcion.commit()//提交一个事务 注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。 上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。 值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。 a、 比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和 show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。 b、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。 c、 remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结 构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。 上述已经介绍完成了Fragment常用的一些方法,相信看完,大家一定清楚了Fragment的产生理由,以及如何使用Fragment,再根据API的讲解,也能明白,曾经为何觉得Fragment会出现一些列乱七八槽的问题,终究是因为没有弄清楚其生命周期。 由于篇幅原因,剩下的内容留到下一篇了。在下一篇,会介绍: 1、如何管理Fragment回退栈 2、Fragment如何与Activity交互 3、Fragment与Activity交互的最佳实践 4、没有视图的Fragment的用处 5、使用Fragment创建对话框 6、如何与ActionBar,MenuItem集成等~~ 借鉴:http://blog.csdn.net/lmj623565791/article/details/37970961 本文转自 一点点征服 博客园博客,原文链接http://www.cnblogs.com/ldq2016/p/5318747.html:,如需转载请自行联系原作者

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

Android Fragment 真正的完全解析(下)

1、管理Fragment回退栈 类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。 看这样一个效果图: 点击第一个按钮,切换到第二个界面,点击第二个按钮,切换到第三个界面,然后点击Back键依次回退。这像不像初学Android时的Activity跳转,当然了,这里肯定不是,不然我就跪了。这里是Fragment实现的,用户点击Back,实际是Fragment回退栈不断的弹栈。 如何添加一个Fragment事务到回退栈: FragmentTransaction.addToBackStack(String) 下面讲解代码:很明显一共是3个Fragment和一个Activity. 先看Activity的布局文件: [html] view plain copy <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"> <FrameLayout android:id="@+id/id_content" android:layout_width="fill_parent" android:layout_height="fill_parent"> </FrameLayout> </RelativeLayout> 不同的Fragment就在这个FrameLayout中显示。 MainActivity.java [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.Window; publicclassMainActivityextendsActivity { @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.add(R.id.id_content,newFragmentOne(),"ONE"); tx.commit(); } } 很简单,直接将FragmentOne添加到布局文件中的FrameLayout中,注意这里并没有调用FragmentTransaction.addToBackStack(String),因为我不喜欢在当前显示时,点击Back键出现白板。而是正确的相应Back键,即退出我们的Activity. 下面是FragmentOne [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.Button; publicclassFragmentOneextendsFragmentimplementsOnClickListener { privateButtonmBtn; @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_one,container,false); mBtn=(Button)view.findViewById(R.id.id_fragment_one_btn); mBtn.setOnClickListener(this); returnview; } @Override publicvoidonClick(Viewv) { FragmentTwofTwo=newFragmentTwo(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.replace(R.id.id_content,fTwo,"TWO"); tx.addToBackStack(null); tx.commit(); } } 我们在点击FragmentOne中的按钮时,使用了replace方法,如果你看了前一篇博客,一定记得replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用tx.addToBackStack(null);将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryView和onCreateView,证据就是:仔细看上面的效果图,我们在跳转前在文本框输入的内容,在用户Back得到第一个界面的时候不见了。 接下来FragmentTwo [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.Button; publicclassFragmentTwoextendsFragmentimplementsOnClickListener { privateButtonmBtn; @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_two,container,false); mBtn=(Button)view.findViewById(R.id.id_fragment_two_btn); mBtn.setOnClickListener(this); returnview; } @Override publicvoidonClick(Viewv) { FragmentThreefThree=newFragmentThree(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.hide(this); tx.add(R.id.id_content,fThree,"THREE"); //tx.replace(R.id.id_content,fThree,"THREE"); tx.addToBackStack(null); tx.commit(); } } 这里点击时,我们没有使用replace,而是先隐藏了当前的Fragment,然后添加了FragmentThree的实例,最后将事务添加到回退栈。这样做的目的是为了给大家提供一种方案:如果不希望视图重绘该怎么做,请再次仔细看效果图,我们在FragmentTwo的EditText填写的内容,用户Back回来时,数据还在~~~ 最后FragmentThree就是简单的Toast了: [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.Button; importandroid.widget.Toast; publicclassFragmentThreeextendsFragmentimplementsOnClickListener { privateButtonmBtn; @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_three,container,false); mBtn=(Button)view.findViewById(R.id.id_fragment_three_btn); mBtn.setOnClickListener(this); returnview; } @Override publicvoidonClick(Viewv) { Toast.makeText(getActivity(),"iamabtninFragmentthree", Toast.LENGTH_SHORT).show(); } } 好了,经过上面的介绍,应该已经知道Fragment回退栈是怎么一回事了,以及hide,replace等各自的应用的场景。 这里极其注意一点:上面的整体代码不具有任何参考价值,纯粹为了显示回退栈,在后面讲解了Fragment与Activity通信以后,会重构上面的代码! 2、Fragment与Activity通信 因为所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为: a、如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法 b、如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作。 c、在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。 注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。 3、Fragment与Activity通信的最佳实践 因为要考虑Fragment的重复使用,所以必须降低Fragment与Activity的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。 下面我通过两种方式的代码,分别重构,FragmentOne和FragmentTwo的点击事件,以及Activity对点击事件的响应: 首先看FragmentOne [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.Button; publicclassFragmentOneextendsFragmentimplementsOnClickListener { privateButtonmBtn; /** *设置按钮点击的回调 *@authorzhy * */ publicinterfaceFOneBtnClickListener { voidonFOneBtnClick(); } @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_one,container,false); mBtn=(Button)view.findViewById(R.id.id_fragment_one_btn); mBtn.setOnClickListener(this); returnview; } /** *交给宿主Activity处理,如果它希望处理 */ @Override publicvoidonClick(Viewv) { if(getActivity()instanceofFOneBtnClickListener) { ((FOneBtnClickListener)getActivity()).onFOneBtnClick(); } } } 可以看到现在的FragmentOne不和任何Activity耦合,任何Activity都可以使用;并且我们声明了一个接口,来回调其点击事件,想要管理其点击事件的Activity实现此接口就即可。可以看到我们在onClick中首先判断了当前绑定的Activity是否实现了该接口,如果实现了则调用。 再看FragmentTwo [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.view.ViewGroup; importandroid.widget.Button; publicclassFragmentTwoextendsFragmentimplementsOnClickListener { privateButtonmBtn; privateFTwoBtnClickListenerfTwoBtnClickListener; publicinterfaceFTwoBtnClickListener { voidonFTwoBtnClick(); } //设置回调接口 publicvoidsetfTwoBtnClickListener(FTwoBtnClickListenerfTwoBtnClickListener) { this.fTwoBtnClickListener=fTwoBtnClickListener; } @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_two,container,false); mBtn=(Button)view.findViewById(R.id.id_fragment_two_btn); mBtn.setOnClickListener(this); returnview; } @Override publicvoidonClick(Viewv) { if(fTwoBtnClickListener!=null) { fTwoBtnClickListener.onFTwoBtnClick(); } } } 与FragmentOne极其类似,但是我们提供了setListener这样的方法,意味着Activity不仅需要实现该接口,还必须显示调用mFTwo.setfTwoBtnClickListener(this)。 最后看Activity : [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.Window; importcom.zhy.zhy_fragments.FragmentOne.FOneBtnClickListener; importcom.zhy.zhy_fragments.FragmentTwo.FTwoBtnClickListener; publicclassMainActivityextendsActivityimplementsFOneBtnClickListener, FTwoBtnClickListener { privateFragmentOnemFOne; privateFragmentTwomFTwo; privateFragmentThreemFThree; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mFOne=newFragmentOne(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.add(R.id.id_content,mFOne,"ONE"); tx.commit(); } /** *FragmentOne按钮点击时的回调 */ @Override publicvoidonFOneBtnClick() { if(mFTwo==null) { mFTwo=newFragmentTwo(); mFTwo.setfTwoBtnClickListener(this); } FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.replace(R.id.id_content,mFTwo,"TWO"); tx.addToBackStack(null); tx.commit(); } /** *FragmentTwo按钮点击时的回调 */ @Override publicvoidonFTwoBtnClick() { if(mFThree==null) { mFThree=newFragmentThree(); } FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.hide(mFTwo); tx.add(R.id.id_content,mFThree,"THREE"); //tx.replace(R.id.id_content,fThree,"THREE"); tx.addToBackStack(null); tx.commit(); } } 代码重构结束,与开始的效果一模一样。上面两种通信方式都是值得推荐的,随便选择一种自己喜欢的。这里再提一下:虽然Fragment和Activity可以通过getActivity与findFragmentByTag或者findFragmentById,进行任何操作,甚至在Fragment里面操作另外的Fragment,但是没有特殊理由是绝对不提倡的。Activity担任的是Fragment间类似总线一样的角色,应当由它决定Fragment如何操作。另外虽然Fragment不能响应Intent打开,但是Activity可以,Activity可以接收Intent,然后根据参数判断显示哪个Fragment。 4、如何处理运行时配置发生变化 运行时配置发生变化,最常见的就是屏幕发生旋转,如果你不知道如何处理屏幕变化可以参考:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案 这里提一下:很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于FragmentThree界面,然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面FragmentThree与FragmentOne叠加在一起,这就是因为你的Activity重新启动,在原来的FragmentThree上又绘制了一个FragmentOne。 好了,下面看一段代码: Activity: [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.view.Window; publicclassMainActivityextendsActivity { privateFragmentOnemFOne; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mFOne=newFragmentOne(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.add(R.id.id_content,mFOne,"ONE"); tx.commit(); } } Fragment [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.LayoutInflater; importandroid.view.View; importandroid.view.ViewGroup; publicclassFragmentOneextendsFragment { privatestaticfinalStringTAG="FragmentOne"; @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Log.e(TAG,"onCreateView"); Viewview=inflater.inflate(R.layout.fragment_one,container,false); returnview; } @Override publicvoidonCreate(BundlesavedInstanceState) { //TODOAuto-generatedmethodstub super.onCreate(savedInstanceState); Log.e(TAG,"onCreate"); } @Override publicvoidonDestroyView() { //TODOAuto-generatedmethodstub super.onDestroyView(); Log.e(TAG,"onDestroyView"); } @Override publicvoidonDestroy() { //TODOAuto-generatedmethodstub super.onDestroy(); Log.e(TAG,"onDestroy"); } } 很简单的代码,当你运行之后,不断的旋转屏幕,你会发现每旋转一次屏幕,屏幕上就多了一个FragmentOne的实例,并且后台log会打印出许多套生命周期的回调。 类似: [html] view plain copy 07-2008:18:46.651:E/FragmentOne(1633):onCreate 07-2008:18:46.651:E/FragmentOne(1633):onCreate 07-2008:18:46.651:E/FragmentOne(1633):onCreate 07-2008:18:46.681:E/FragmentOne(1633):onCreateView 07-2008:18:46.831:E/FragmentOne(1633):onCreateView 07-2008:18:46.891:E/FragmentOne(1633):onCreateView 这是为什么呢,因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建;这样造成当旋转的时候,本身存在的Fragment会重新启动,然后当执行Activity的onCreate时,又会再次实例化一个新的Fragment,这就是出现的原因。 那么如何解决呢: 其实通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建: 默认的savedInstanceState会存储一些数据,包括Fragment的实例:通过打印可以看出: [java] view plain copy 07-2008:23:12.952:E/FragmentOne(1782):Bundle[{android:fragments=android.app.FragmentManagerState@40d0b7b8,android:viewHierarchyState=Bundle[{android:focusedViewId=2131230721,android:views=android.util.SparseArray@40d0af68}]}] 所以,我们简单改一下代码,只有在savedInstanceState==null时,才进行创建Fragment实例: [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.Window; publicclassMainActivityextendsActivity { privatestaticfinalStringTAG="FragmentOne"; privateFragmentOnemFOne; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); Log.e(TAG,savedInstanceState+""); if(savedInstanceState==null) { mFOne=newFragmentOne(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.add(R.id.id_content,mFOne,"ONE"); tx.commit(); } } } 现在无论进行多次旋转都只会有一个Fragment实例在Activity中。 现在还存在一个问题,就是重新绘制时,Fragment发生重建,原本的数据如何保持? 其实和Activity类似,Fragment也有onSaveInstanceState的方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。 由于篇幅原因,就不贴测试代码了。 5、Fragmeny与ActionBar和MenuItem集成 Fragment可以添加自己的MenuItem到Activity的ActionBar或者可选菜单中。 a、在Fragment的onCreate中调用 setHasOptionsMenu(true); b、然后在Fragment子类中实现onCreateOptionsMenu c、如果希望在Fragment中处理MenuItem的点击,也可以实现onOptionsItemSelected;当然了Activity也可以直接处理该MenuItem的点击事件。 代码: Fragment [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Fragment; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.view.Menu; importandroid.view.MenuInflater; importandroid.view.MenuItem; importandroid.view.View; importandroid.view.ViewGroup; importandroid.widget.Toast; publicclassFragmentOneextendsFragment { @Override publicvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer, BundlesavedInstanceState) { Viewview=inflater.inflate(R.layout.fragment_one,container,false); returnview; } @Override publicvoidonCreateOptionsMenu(Menumenu,MenuInflaterinflater) { inflater.inflate(R.menu.fragment_menu,menu); } @Override publicbooleanonOptionsItemSelected(MenuItemitem) { switch(item.getItemId()) { caseR.id.id_menu_fra_test: Toast.makeText(getActivity(),"FragmentMenuItem1",Toast.LENGTH_SHORT).show(); break; } returntrue; } } Activity [java] view plain copy packagecom.zhy.zhy_fragments; importandroid.app.Activity; importandroid.app.FragmentManager; importandroid.app.FragmentTransaction; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.Menu; importandroid.view.MenuItem; importandroid.view.Window; importandroid.widget.Toast; publicclassMainActivityextendsActivity { privatestaticfinalStringTAG="FragmentOne"; privateFragmentOnemFOne; @Override protectedvoidonCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); Log.e(TAG,savedInstanceState+""); if(savedInstanceState==null) { mFOne=newFragmentOne(); FragmentManagerfm=getFragmentManager(); FragmentTransactiontx=fm.beginTransaction(); tx.add(R.id.id_content,mFOne,"ONE"); tx.commit(); } } @Override publicbooleanonCreateOptionsMenu(Menumenu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main,menu); returntrue; } @Override publicbooleanonOptionsItemSelected(MenuItemitem) { switch(item.getItemId()) { caseR.id.action_settings: Toast.makeText(this,"setting",Toast.LENGTH_SHORT).show(); returntrue; default: //如果希望Fragment自己处理MenuItem点击事件,一定不要忘了调用super.xxx returnsuper.onOptionsItemSelected(item); } } } 效果图: 好了,可以很好的看到,Fragment可以添加MenuItem,也可以自己处理点击~~~ 6、没有布局的Fragment的作用 没有布局文件Fragment实际上是为了保存,当Activity重启时,保存大量数据准备的 请参考博客:Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案 7、使用Fragment创建对话框 这是Google推荐的方式,我也单独写过博客介绍,请参考:Android 官方推荐 : DialogFragment 创建对话框 好了,终于把Fragment相关的联系到一起了,上述基本包含了Fragment所有的用法~~~相信大家如果能够看完,一定有不少的收获~~~ 本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/4936646.html,如需转载请自行联系原作者

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

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等操作系统。

WebStorm

WebStorm

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

用户登录
用户注册