• 关于

    相变化内存如何看配置

    的搜索结果

问题

【阿里云产品评测】个人WP站的云体验

1 ECS 首先来说说我自己,一直对计算机颇有兴趣,高中的时候就开始研究了些PHP的东西, 05年上大二的时候,注册了自己的域名www.cnsjw.cn  ,并开通了自己...
cnsjw 2019-12-01 20:54:27 22207 浏览量 回答数 25

问题

性能测试:软件测试的重中之重

       性能测试在软件的质量保证中起着重要的作用,它包括的测试内容丰富多样。中国软件评测中心将性能测试概括为三个方面:应用在客户端性能的测试、应用在网络上性能的测试和应用在服务器端性能的测试。通常情况下&#...
云效平台 2019-12-01 21:45:09 5839 浏览量 回答数 1

回答

浅谈Flutter框架原理及其生态圈 Flutter的锋芒 跨平台高性能的渲染引擎逐渐成为移动端、大前端领域的一个热点,作为其中的明星框架Flutter,经过近几年来的迅速发展,由极大的可能成为下一代跨端终端解决方案。自从2017 年 5 月,谷歌公司发布的了 Alpha 版本的 Flutter; 2018 年底 Flutter Live 发布的 1.0 版本;2019年7月发布1.5版本,截止今日(2020年2月)已经发布了v1.14.6 Beta版本。 在Flutter诞生之前,已经有许多跨平台UI框架的方案如Cordova、ReactNative、weex、uni-app、Hippy等,常见的需要处理兼容的终端平台也包括android、ios、web、Iot等,但是在大前端的浪潮下,对于企业和开发者来说开发效率和使用体验都十分重要,传统的做法莫过于分不同的团队开发不同的终端项目,如果还要继续向其他平台,拓展的话,我们需要付出的成本和时间将成倍增长。正因为如此,在这样的背景下,Flutter等跨端框架的兴起,从本质上讲,帮助开发者增加业务代码的复用率,减少因为要适配多个平台带来的工作量,从而降低开发成本、提高开发效率。 纵观已有的跨端方案,可以分为三类:Web 容器、泛 Web 容器、自绘引擎框架。 基于web容器即基于浏览器的跨平台也做得越来越好,自然管线也越来越短,与native的一些技术手段来实现性能上的相互补充。比如Egret、Cocos、Laya这些游戏引擎,它们在跨平台方面的做法多以Typescript编写,在iOS和安卓平台的各种浏览器中轻松的运行HTML5游戏,并在不同平台浏览器里提供近乎一致的用户体验,比如Egret还会提供高效的 JS-C Binding 编译机制,以满足游戏编译为原生格式的需求,不过大多数HTML游戏引擎也属于web容器这个范畴内。web容器框架也有一个明显的致命(在对体验&性能有较高要求的情况下)的缺点,那就是WebView的渲染效率和JavaScript执行性能太差。再加上Android各个系统版本和设备厂商的定制,很难保证所在所有设备上都能提供一致的体验。 泛 Web 容器框架比如ReactNative和Weex,即上层通过面向前端友好的UI,下层通过native的渲染形式,虽然同样使用类HTML+JS的UI构建逻辑,但是最终会生成对应的自定义原生控件,以充分利用原生控件相对于WebView的较高的绘制效率,同时H5与native相互补充来达到更好的用户体验,这也是一种很好的解决方案。缺陷也很明显,随着系统版本变化和API的变化,开发者可能也需要处理不同平台的差异,甚至有些特性只能在部分平台上实现,这样框架的跨平台特性就会大打折扣。 自绘引擎框架这里专指Flutter框架,从底层就承担跨端的任务和渲染方式,从目前来看,从技术的实现和方案的成熟度、产品的性能方面比较,Flutter有很大可能成为下一代主流跨平台框架。 Flutter与其他跨端框架的不同点之一就是自带渲染引擎,Flutter渲染引擎依靠跨平台的Skia图形库来实现,Skia引擎会将使用Dart语言构建的抽象的视图结构数据加工成GPU数据,交由 OpenGL 最终提供给 GPU 渲染,至此完成渲染闭环,因此可以在最大程度上保证一款应用在不同平台、不同设备上的体验一致性。 而开发语言选用的是同时支持 JIT和 AOT的 Dart语言,Dart本身提供了三种运行方式,应对web环境,用Dart2js编译成JavaScript代码,运行在常规浏览器中;使用DartVM直接在命令行中运行Dart代码;AOT方式编译成机器码,例如Flutter App框架。而且Dart 避免了抢占式调度和共享内存,可以在没有锁的情况下进行对象分配和垃圾回收,在性能方面表现相当不错,不仅保证了开发效率,代码性能和用户体验也更卓越。因此,Flutter在各类跨平台移动开发方案中脱颖而出。同时在去年2019的Google IO大会上,备受关注的Fuchsia系统虽然并没有发布,但是宣布了 Flutter除了支持开发 Android 和 iOS 程序之外,现在还支持开发Web程序了,在 I/O 大会上,谷歌发布了 Web 版 Flutter 的首个技术预览版,宣布 Flutter 将为包括 Google Home Hub 在内的 Google Smart Display 平台提供技术支持,并迈出利用 Chrome 操作系统支持桌面级应用的第一步。 很多JS开发者会思考Google Flutter团队至于为啥选择Dart而不是JS,其实Google 公司给出的原因很简单也很直接:Dart 语言开发组就在隔壁,对于 Flutter 需要的一些语言新特性,能够快速在语法层面落地实现;而如果选择了 JavaScript,就必须经过各种委员会(TC39等)和浏览器提供商漫长的决议。 Flutter绘制原理 在计算机系统中,图像的显示需要 CPU、GPU 和显示器一起配合完成:CPU 负责图像数据计算,GPU 负责图像数据渲染,而显示器则负责最终图像显示。 CPU 把计算好的、需要显示的内容交给 GPU,由 GPU 完成渲染后放入帧缓冲区,随后视频控制器根据垂直同步信号(VSync)以每秒 60 次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。 操作系统在呈现图像时遵循了这种机制,而 Flutter 作为跨平台开发框架也采用了这种底层方案。下面有一张更为详尽的示意图来解释 Flutter 的绘制原理。可以看到,Flutter 关注如何尽可能快地在两个硬件时钟的 VSync 信号之间计算并合成视图数据,然后通过 Skia 交给 GPU 渲染:UI 线程使用 Dart 来构建视图结构数据,这些数据会在 GPU 线程进行图层合成,随后交给 Skia 引擎加工成 GPU 数据,而这些数据会通过 OpenGL 最终提供给 GPU 渲染。 Skia原理 Skia 是一款用由C++ 开发的2D 图像绘制引擎。在2005 年被 Google 公司收购后被广泛应用在 Android和其他等核心产品上,Skia 目前是Android 官方的图像渲染引擎,因此 Flutter Android SDK 无需内嵌 Skia 引擎就可以获得天然的 Skia 支持;而对于 iOS 平台来说,由于 Skia 是跨平台的,因此它作为 Flutter iOS 渲染引擎被嵌入到 Flutter 的 iOS SDK 中,替代了 iOS 闭源的 Core Graphics/Core Animation/Core Text,这也正是 Flutter iOS SDK 打包的 App 包体积比 Android 要大一些的原因。 底层渲染能力统一了,上层开发接口和功能体验也就随即统一了,开发者再也不用操心平台相关的渲染特性了。也就是说,Skia 保证了同一套代码调用在 Android 和 iOS 平台上的渲染效果是完全一致的。 Flutter架构 Framework底层是Flutter引擎,引擎主要负责图形绘制(Skia)、文字排版(libtxt)和提供Dart运行时,引擎全部使用C++实现,Framework层使我们可以用Dart语言调用引擎的强大能力。Flutter 架构采用分层设计,从下到上分为三层,依次为:Embedder、Engine、Framework。 Embedder 是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配。从这里我们可以看到,Flutter 平台相关特性并不多,这就使得从框架层面保持跨端一致性的成本相对较低。 Engine 层主要包含 Skia、Dart 和 Text,实现了 Flutter 的渲染引擎、文字排版、事件处理和 Dart 运行时等功能。Skia 和 Text 为上层接口提供了调用底层渲染和排版的能力,Dart 则为 Flutter 提供了运行时调用 Dart 和渲染引擎的能力。而 Engine 层的作用,则是将它们组合起来,从它们生成的数据中实现视图渲染。 Framework 层则是一个用 Dart 实现的 UI SDK,包含了动画、图形绘制和手势识别等功能。为了在绘制控件等固定样式的图形时提供更直观、更方便的接口,Flutter 还基于这些基础能力,根据 Material 和 Cupertino 两种视觉设计风格封装了一套 UI 组件库,开发者可以直接使用这些组件库。 Flutter运行流程 页面中的各界面元素(Widget)以树的形式组织,即控件树。Flutter 通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。在Flutter界面渲染过程分为三个阶段:布局、绘制、合成,布局和绘制在Flutter框架中完成,合成则交由引擎负责。 Flutter 采用深度优先机制遍历渲染对象树,决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中,渲染对象树中的每个渲染对象都会接收父对象的布局约束参数,决定自己的大小,然后父对象按照控件逻辑决定各个子对象的位置,最终完成布局过程。这里只需要注意一点,无论布局还是绘制,都是父子间的遍历关系:父Widget的布局需要依赖子Widget的布局结果;而绘制则反过来(子Widget需要盖在父Widget上),布局是后续遍历,绘制是前序遍历,他们都是深度优先遍历。 Flutter生命周期 可以看到,Flutter中State 的生命周期可以分为 3 个阶段:创建(插入视图树)、更新(在视图树中存在)、销毁(从视图树中移除)。接下来,我们一起看看每一个阶段的具体流程。 第一步创建 State 初始化时会依次执行 :构造方法 -> initState -> didChangeDependencies -> build,随后完成页面渲染。构造方法是 State 生命周期的起点,Flutter 会通过调用StatefulWidget.createState() 来创建一个 State。我们可以通过构造方法,来接收父 Widget 传递的初始化 UI 配置数据。这些配置数据,决定了 Widget 最初的呈现效果。 initState,会在 State 对象被插入视图树的时候调用。这个函数在 State 的生命周期中只会被调用一次,所以我们可以在这里做一些初始化工作,比如为状态变量设定默认值。 didChangeDependencies 则用来专门处理 State 对象依赖关系变化,会在 initState() 调用结束后,被 Flutter 调用。 build,作用是构建视图。经过以上步骤,Framework 认为 State 已经准备好了,于是调用 build。我们需要在这个函数中,根据父 Widget 传递过来的初始化配置数据,以及 State 的当前状态,创建一个 Widget 然后返回。 第二步更新 Widget 的状态更新,主要由个方法触发:setState、didchangeDependencies、didUpdateWidget。 setState:我们最熟悉的方法之一。当状态数据发生变化时,我们总是通过调用这个方法告诉 Flutter:“我这儿的数据变啦,请使用更新后的数据重建 UI!” didChangeDependencies:State 对象的依赖关系发生变化后,Flutter 会回调这个方法,随后触发组件构建。哪些情况下 State 对象的依赖关系会发生变化呢?典型的场景是,系统语言 Locale 或应用主题改变时,系统会通知 State 执行 didChangeDependencies 回调方法。 didUpdateWidget:当 Widget 的配置发生变化时,比如,父 Widget 触发重建(即父 Widget 的状态发生变化时),热重载时,系统会调用这个函数。一旦这三个方法被调用,Flutter 随后就会销毁老 Widget,并调用 build 方法重建 Widget。 第三步销毁 比如组件被移除,或是页面销毁的时候,系统会调用 deactivate 和 dispose 这两个方法,来移除或销毁组件。 Flutter生态圈及其常用框架 一项技术一个框架是否流行,最直观的体现就是它的生态圈是否活跃,下面列举了一些Flutter开发中常用的库工具。 参考文献 1、[Flutter原理与实践](https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html) 少杰 2、[Flutter框架技术概览](https://flutter.dev/docs/resources/technical-overview) 3、[Flutter中文官网](https://pub.dartlang.org/flutter/) 4、[Flutter插件仓库](https://pub.dev/flutter/packages)
罗思雨 2020-02-27 11:47:50 0 浏览量 回答数 0

阿里云爆款特惠专场,精选爆款产品低至0.95折!

爆款ECS云服务器8.1元/月起,云数据库低至1.5折,限时抢购!

回答

首先“缓存”Cache这个东西是干什么的,我们应该先有些基本的了解。要是不太明白的可以看看网上的解释:http://baike.baidu.com/view/907.htm 简单讲,阿里云OCS提供的功能就是提供对热点数据的高速访问。在使用OCS之前(或者在使用任何一种缓存服务之前),我们都应该明白关于缓存的这么几点: 缓存里的数据不是持久化保存的,也就是说它像是电脑里的内存,而不像硬盘;我们不能指望OCS里的数据一直保存不丢失。如果你真的需要存储持久化的数据,也许你应该出门左转找阿里云OSS(开发存储服务); 缓存里存的应该是“热点”数据。遵循常常出现的“20-80法则”,通常程序应用中都有一定比例的数据常常被请求访问,这就是所谓的热点数据,OCS正是为这种数据设计存在的。假定我们的程序中有100个数据,每次访问这些数据的概率完全是均匀分布的1/100,那么使用缓存的效果就不会太好,因为这其中不存在热点数据。 数据逐出。我们可以决定哪些数据是热点数据被放到缓存当中,但是如果我们的缓存容量不够大,这些热点数据中某些最近较少被用到的数据还是会被“挤出去”,这种行为叫做数据逐出。如果想减少出现这种情况,我们可以购买更高容量的OCS。 -------------------------         在开始使用之前,关于阿里云OCS,我们还需要知道以下这些事: 阿里云OCS仅支持阿里云内网访问,不支持公网访问。也就是说,我们用办公室或者家里的电脑(都属于公网)是无法连上阿里云OCS的。为什么会这样呢?因为缓存服务的根本目标是要提供低延迟的高速访问,而从公网电脑来连接OCS服务器的场景下,公网的网络环境是不可控的,可能出现延迟很高甚至断连接的情况,这使得缓存服务无法保证“高速、低延迟”的基本特性,所以阿里云OCS是不支持公网直接访问的。如果觉得高延迟的情况对于我们的应用也能接受,那么我们应该去选择阿里云其他的产品(比如OSS开放存储服务),而不应该选择OCS缓存服务。 阿里云OCS需要与ECS(阿里云服务器)配合使用,而且只能与本地区节点的ECS连通。这一点与上一条相关。OCS只能从阿里云内网访问,也就是说我们只能从阿里云ECS上才能访问并使用OCS服务。所以我们在官网购买OCS的时候,会看到提示信息说需要至少有一台ECS才能买OCS。另外,阿里云ECS是分地区节点的,比如北京、杭州、青岛等,我们在购买OCS缓存的时候也要选相应的地区节点。北京的ECS只能访问北京的OCS,而不能访问杭州或青岛的OCS。 阿里云OCS是按购买量收费的,而不是按使用量收费。这点需要提醒新同学们注意,在我们购买了OCS缓存之后,计费就已经开始了,即使我们还没有真正使用缓存。也就是说,我们买了1G的OCS缓存后,即使目前使用量为0,系统也会按照1G的标准来计费。所以我们在购买OCS的时候,要选取适合我们业务数据需要的缓存档位。当然了,阿里云OCS也提供在线升降缓存容量的功能。也就是说,如果我们在使用了一段时间之后,发现购买的OCS缓存不够用了(或者缓存使用量太低),我们可以在线的对已有的OCS实例进行升档(或者降档),而OCS缓存服务不会被中断。 阿里云OCS对于存贮的对象大小是有限制的。缓存通常对其内部存储的数据尺寸是有限制的,阿里云OCS也一样。目前OCS支持存储的数据对象的上限是1,000,000Byte。如果要存的值超过这个限制,我们应该考虑把数据压缩,或从逻辑上分成不同键存储的几个值。 ------------------------- 现在我们开始在阿里云官网上购买OCS实例  http://buy.aliyun.com/ocs  首先我们需要已经有了一台阿里云ECS,否则我们无法在这个页面成功购买OCS。购买的第一步,我们先要确定选择买哪个地区的OCS;这个很重要,如上面所说,如果我们的ECS是属于北京,而我们在这里购买了杭州的OCS,那么这两者是无法配合协同工作的。所以,在购买OCS的时候一定要选择应用服务器ECS所在地区的OCS。下一步是要选择OCS缓存容量。我们要购买多大的缓存,这个取决于我们对自身业务应用中热点数据总量大小的判断。如果一时难以准确判断数据量,也不用担心:我们可以先买一个大致容量的OCS(比如1GB),随后在使用过程中,通过OCS控制台提供的监控功能,我们可以了解到目前OCS缓存的使用量等数据,然后可以自主的调整所需的缓存量,购买更大的缓存(比如升到5GB)或者减少已购的缓存量(比如降到512MB),阿里云会根据我们选择的新配置来调整对应的收费。此外在选择缓存容量的时候,要知道不同容量的缓存档位对应着不同的性能配额,具体来说包括两个指标:吞吐量带宽与每秒请求处理数(QPS)。比如以现在的配额标准,1GB的OCS缓存对应5MB/sec的吞吐量带宽和3000次/sec的请求处理峰值。当我们使用OCS的时候,如果数据量传输的带宽超过了5MB/s, 或者每秒的请求数超过了3000次,都会触发性能配额控制机制,导致某些请求无法返回正常结果。在确定了地区和缓存容量之后,我们就可以直接下单购买OCS了。 ------------------------- 在成功购买OCS之后,我们的联系邮箱和手机都会收到OCS创建成功的通知,里面会包括OCS的实例ID和初始密码(关于密码的用处后面会讲到)。我们现在登录OCS控制台, http://ocs.console.aliyun.com/ 就可以看到已经购买到的OCS实例列表。在列表页面上对应OCS实例的后面点击“管理”,就可以进入该OCS实例的详情页,看到更多的详细信息。 ------------------------- 我们现在已经有了一个OCS缓存实例,现在是时候试玩OCS了。要使用OCS就要写一点程序代码,不过不用担心,我们在这里采用“Happy-Path”的方法,从最简单的操作开始,让新上手的菜鸟们能马上就有一个能调用OCS缓存服务的程序。OCS提供缓存服务,它并不要求我们的程序是哪种语言来写的。我们这里先以Java程序为例,写一个最简单的“Hello World”。(其他编程语言的例子,我们随后附上。)第一步,登录你的阿里云ECS服务器,在上面安装Java JDK和你常用的IDE(比如Eclipse)。一定要记得我们之前说过的,只有在阿里云内网的ECS服务器上,才能访问我们的OCS实例。所以,用家里或是公司的电脑执行下面的代码示例是看不到结果的。 Java JDK和Eclipse都很容易从网上找到下载,比如 http://download.eclipse.org/ 或者 http://www.onlinedown.net/soft/32289.htm 第二步,在把Java开发环境准备好了之后,下载第一个代码示例(Sample-Code-1第三步,在Eclipse里面打开刚下载的OcsSample1.java,我们要根据自己的OCS实例信息修改几个地方。        我们每个人买到的OCS实例的ID都是不重复的,其对应的阿里云内网地址也是独一无二的,这些信息都在OCS控制台上显示出来。我们在同自己的OCS实例建立连接的时候,需要根据这些信息修改OcsSample1.java中的对应地方。         public static void main(String[] args) {                                        final String host = "b2fd2f89f49f11e3.m.cnqdalicm9pub001.ocs.aliyuncs.com"; //控制台上的“内网地址”                   final String port ="11211";       //默认端口 11211,不用改                   final String username = "b2fd2f89f49f11e3"; //控制台上的“访问账号”                   final String password = "my_password"; //邮件或短信中提供的“密码”                   …… …… ……       信息修改完毕,我们可以运行自己的程序了。运行main函数,我们会在Eclipse下面的console窗口看到下面这样的结果(请忽略可能出现的红色INFO调试信息): OCS Sample CodeSet操作完成!Get操作: Open Cache Service,  from www.Aliyun.com     OK,搞定!我们已经成功的连接上了阿里云的OCS并且调用缓存服务成功,就这么简单。-------------------------我们已经成功运行了第一个调用阿里云OCS缓存服务的Sample程序OcsSample1.java,现在我们看看这个程序里都做了什么。                                  …… …… ……                            System.out.println("OCS Sample Code");                                                        //向OCS中存一个key为"ocs"的数据,便于后面验证读取数据,                             //这个数据对应的value是字符串 Open Cache Service,  from www.Aliyun.com                            OperationFuture future = cache.set("ocs", 1000," Open Cache Service,  from www.Aliyun.com");                            //向OCS中存若干个数据,随后可以在OCS控制台监控上看到统计信息                            for(int i=0;i<100;i++){                                String key="key-"+i;                                String value="value-"+i;                                 //执行set操作,向缓存中存数据                                cache.set(key, 1000, value);                            }                             System.out.println("Set操作完成!");                             future.get();  //  确保之前(cache.set())操作已经结束                         //执行get操作,从缓存中读数据,读取key为"ocs"的数据                            System.out.println("Get操作:"+cache.get("ocs"));                            …… …… …… 从这些代码中可以看出: 1. 我们在建立与OCS缓存服务器的连接后,先是向缓存中存(set)了一个“key-value”(键值对)形式的数据,这个数据的key是字符串“ocs”,其对应的value也是字符串;2. 接着我们继续向缓存中存(set)了100个其他简单的“key-value”数据。3. 最后我们进行功能验证。根据之前给定的key,从缓存中获取(get)其对应的value:也就是输入字符串“ocs”,缓存给我们返回value对应的字符串。 以上的步骤中,1与3是相对应的,我们只有先向缓存中set了某个数据,后面才能从缓存中get到这个数据。步骤2中程序向缓存set了100个数据,是为了从另一个方面进行验证。我们回到阿里云OCS控制台,打开“实例详情”页,在“实例监控”的部分点击刷新,会看到其中一些监控项的值已经发生了变化(注:监控信息的刷新可能存在数秒的延迟), 其中的“Key的个数”已经变成了101,也就是说我们程序已经成功地向OCS缓存中存放了101个数据。-------------------------在写下一篇技术贴之前,列一些OCS用户在入门时问到的问题,方便其他刚认识OCS的同学:Question:买了1G的OCS,那就相当于这个1G是专门缓存用的,与ECS服务器的内存没关系是吧~Answer:是的,OCS的缓存容量与您ECS的内存容量是没关系的。Question:OCS 外网测试,怎么连接?有没有外网连接地址哦?Answer:OCS是不能从外网访问的。参照上面的文章。Question:我之前那个OCS可以正常使用,但现在换了一个OCS就不行了,怎么回事?Answer:经核实您的主机是属于杭州节点的,而现在这个OCS是青岛节点的,不同地域之间的产品内网不互通。Question:在设置一个value时,如果指定过期时间为0,会永久保留吗?Answer:指定过期时间为0,OCS就认为此数据不根据过期时间发生淘汰;但是,此数据仍有可能基于LRU被其他数据淘汰,或者由内存清理造成丢失 ,因此不能认为这个value会永久保留。 Question:对OCS的访问是否需要负载均衡? Answer:不需要。对访问请求的负载均衡都是在OCS服务器端来进行的,用户直接使用缓存服务即可,不用考虑负载均衡的事情。 Question:OCS是否会主动关闭闲置的连接? 如果会,请问连接闲置多久会被关闭?Answer:OCS不会主动关闭闲置的用户连接。但是用户的环境如果使用了SLB,则需要参考SLB连接关闭时间。Question:如何设置数据在OCS缓存中的过期时间 ?Answer:关于设置缓存数据的过期时间,可以参考Memcached官方说明: https://code.google.com/p/memcached/wiki/NewCommands An expiration time, in seconds. Can be up to 30 days. After 30 days, is treated as a unix timestamp of an exact date. 翻译过来就是:0~2592000表示从当前时刻算起的时间长度(以秒计算,最长2592000即30天);大于2592000表示UNIX时间戳。 此值设置为0表明此数据不会主动过期。------------------------- 回 12楼(村里一把手) 的帖子 谢谢,要让大家用得好才算数。 -------------------------缓存与数据库相结合使用,是常见的一种应用搭配场景。现在我们再看一个例子,是用OCS搭配MySQL数据库使用。Java示例代码在此(这个示例代码中,大部分与前几个例子类似。因为要与数据库结合,所以程序需要依赖一个JDBC的jar包才能运行。支持MySQL的JDBC jar包在此(在程序中添加MySQL数据库的连接信息:     …… …… ……            // JDBC driver name and database URL    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";    static final String DB_URL = "jdbc:mysql://xxxxxxx.mysql.rds.aliyuncs.com/testdb"; //MySQL数据库URL        //  Database用户名及密码    static final String DB_USER = "xxxxxx";    static final String DB_PASS = "xxxxxx";            我们设想这样一个场景:我们需要从数据库的tableone表中查找区域不属于北京的记录总数,用SQL表示就是:SELECT count(*)  FROM testdb.tableone where region != 'beijing'假定这个表中的数据如下,则这条SQL查询返回的结果就是7:如果这个查询被调用到的频率很高,多个用户反复不断的在数据库中查这个数据,我们就可以把这个查询结果放到OCS缓存中去。看下面的代码片段,我们用for循环模拟用户连续20次在数据库中查询上述SQL语句:              for (int i = 1; i <= 20; i++) {                String sql = "SELECT count(*)  FROM testdb.tableone where region != 'beijing'";                String key ="non-beijing"; //给SQL语句自定义一个key                //在OCS缓存里按key查找               String value =  (String) cache.get(key);                                if (value == null) {                    // 在OCS缓存里没有命中                    // step 1:从My SQL数据库中查询                    //Load MySQL Driver                      Class.forName(JDBC_DRIVER);                     con = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);                    ps = con.prepareStatement(sql);                    ResultSet result = ps.executeQuery(sql);                    result.next();                                        value=result.getString(1);                    System.out.println("从MySQL中查询数据.  Key= "+key+" Value="+value);                                       // step 2: 把数据库返回的数据作为value存放到OCS缓存中去                    cache.set(key, EXPIRE_TIME, value);                                    } else {                    // 在OCS缓存里命中                    System.out.println("从OCS中读取数据.     Key= "+key+" Value="+value);                }                            }// end of for在这段代码中我们可以看到,我们给这条SQL语句标记了一个key,当有用户要执行这条SQL的时候,我们首先按照key在OCS缓存中查找:如果没有对应的缓存数据,则连接MySQL数据库执行SQL查询,把结果返回给用户,并把这个查询结果存到OCS缓存中去;如果OCS中已经有了对应的缓存数据,则直接把缓存数据返回给用户。运行结果如下: 从MySQL中查询数据.  Key= non-beijing, Value=7从OCS中读取数据.     Key= non-beijing, Value=7从OCS中读取数据.     Key= non-beijing, Value=7从OCS中读取数据.     Key= non-beijing, Value=7…… …… 从结果可以看出,程序第1次是从MySQL数据库当中查询数据,后面的19次都是从OCS缓存中获取key对应的value直接返回。也就是说,OCS降低了程序去连接MySQL数据库执行SQL查询的次数,减轻了对数据库的负载压力。用户对热点数据访问的频率越高,OCS的这种优势就越明显。
唐翰 2019-12-01 23:41:23 0 浏览量 回答数 0

回答

Android平台进行数据存储的五大方式,分别如下: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解这五种方式的特点 第一种: 使用SharedPreferences存储数据 适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等 核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name>/shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下: Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。 Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。 Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写 Editor有如下主要重要方法: SharedPreferences.Editor clear():清空SharedPreferences里所有数据 SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据 SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项 boolean commit(): 当Editor编辑完成后,使用该方法提交修改 实际案例:运行界面如下 这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下: 、class ViewOcl implements View.OnClickListener{ @Override public void onClick(View v) { switch(v.getId()){ case R.id.btnSet: //步骤1:获取输入值 String code = txtCode.getText().toString().trim(); //步骤2-1:创建一个SharedPreferences.Editor接口对象,lock表示要写入的XML文件名,MODE_WORLD_WRITEABLE写操作 SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit(); //步骤2-2:将获取过来的值放入文件 editor.putString("code", code); //步骤3:提交 editor.commit(); Toast.makeText(getApplicationContext(), "口令设置成功", Toast.LENGTH_LONG).show(); break; case R.id.btnGet: //步骤1:创建一个SharedPreferences接口对象 SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE); //步骤2:获取文件中的值 String value = read.getString("code", ""); Toast.makeText(getApplicationContext(), "口令为:"+value, Toast.LENGTH_LONG).show(); break; } } } 、读写其他应用的SharedPreferences: 步骤如下 1、在创建SharedPreferences时,指定MODE_WORLD_READABLE模式,表明该SharedPreferences数据可以被其他程序读取 2、创建其他应用程序对应的Context: Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);这里的com.tony.app就是其他程序的包名 3、使用其他程序的Context获取对应的SharedPreferences SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE); 4、如果是写入数据,使用Editor接口即可,所有其他操作均和前面一致。 SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。 第二种: 文件存储数据 核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选: MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用Context.MODE_APPEND MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 MODE_WORLD_READABLE:表示当前文件可以被其他应用读取; MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。 除此之外,Context还提供了如下几个重要的方法: getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录 File getFilesDir():获取该应用程序的数据文件夹得绝对路径 String[] fileList():返回该应用数据文件夹的全部文件 public String read() { try { FileInputStream inStream = this.openFileInput("message.txt"); byte[] buffer = new byte[1024]; int hasRead = 0; StringBuilder sb = new StringBuilder(); while ((hasRead = inStream.read(buffer)) != -1) { sb.append(new String(buffer, 0, hasRead)); } inStream.close(); return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } public void write(String msg){ // 步骤1:获取输入值 if(msg == null) return; try { // 步骤2:创建一个FileOutputStream对象,MODE_APPEND追加模式 FileOutputStream fos = openFileOutput("message.txt", MODE_APPEND); // 步骤3:将获取过来的值放入文件 fos.write(msg.getBytes()); // 步骤4:关闭数据流 fos.close(); } catch (Exception e) { e.printStackTrace(); } } openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data//files目录,如: /data/data/cn.tony.app/files/message.txt, 下面讲解某些特殊文件读写需要注意的地方: 读写sdcard上的文件 其中读写步骤按如下进行: 1、调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 2、调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录 3、使用IO流操作SD卡上的文件 注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡 必须在AndroidManifest.xml上配置读写SD卡的权限 // 文件写操作函数 private void write(String content) { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } PrintStream out = null; // 打印流对象用于输出 try { out = new PrintStream(new FileOutputStream(file, true)); // 追加文件 out.println(content); } catch (Exception e) { e.printStackTrace(); } finally { if (out != null) { out.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } } // 文件读操作函数 private String read() { if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 如果sdcard存在 File file = new File(Environment.getExternalStorageDirectory() .toString() + File.separator + DIR + File.separator + FILENAME); // 定义File类对象 if (!file.getParentFile().exists()) { // 父文件夹不存在 file.getParentFile().mkdirs(); // 创建文件夹 } Scanner scan = null; // 扫描输入 StringBuilder sb = new StringBuilder(); try { scan = new Scanner(new FileInputStream(file)); // 实例化Scanner while (scan.hasNext()) { // 循环读取 sb.append(scan.next() + "\n"); // 设置文本 } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } finally { if (scan != null) { scan.close(); // 关闭打印流 } } } else { // SDCard不存在,使用Toast提示用户 Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show(); } return null; } 复制代码 第三种:SQLite存储数据 SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧 SQLiteDatabase类为我们提供了很多种方法,上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用 1 db.executeSQL(String sql); 2 db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集 除了统一的形式之外,他们还有各自的操作方法: 1 db.insert(String table, String nullColumnHack, ContentValues values); 2 db.update(String table, Contentvalues values, String whereClause, String whereArgs); 3 db.delete(String table, String whereClause, String whereArgs);以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age > ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样 下面给出demo 数据的添加 1.使用insert方法 复制代码1 ContentValues cv = new ContentValues();//实例化一个ContentValues用来装载待插入的数据2 cv.put("title","you are beautiful");//添加title3 cv.put("weather","sun"); //添加weather4 cv.put("context","xxxx"); //添加context5 String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")6 .format(new Date());7 cv.put("publish ",publish); //添加publish8 db.insert("diary",null,cv);//执行插入操作复制代码2.使用execSQL方式来实现 String sql = "insert into user(username,password) values ('Jack Johnson','iLovePopMuisc');//插入操作的SQL语句db.execSQL(sql);//执行SQL语句数据的删除 同样有2种方式可以实现 String whereClause = "username=?";//删除的条件String[] whereArgs = {"Jack Johnson"};//删除的条件参数db.delete("user",whereClause,whereArgs);//执行删除使用execSQL方式的实现 String sql = "delete from user where username='Jack Johnson'";//删除操作的SQL语句db.execSQL(sql);//执行删除操作数据修改 同上,仍是2种方式 ContentValues cv = new ContentValues();//实例化ContentValuescv.put("password","iHatePopMusic");//添加要更改的字段及内容String whereClause = "username=?";//修改条件String[] whereArgs = {"Jack Johnson"};//修改条件的参数db.update("user",cv,whereClause,whereArgs);//执行修改使用execSQL方式的实现 String sql = "update user set password = 'iHatePopMusic' where username='Jack Johnson'";//修改的SQL语句db.execSQL(sql);//执行修改数据查询 下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式: 1 db.rawQuery(String sql, String[] selectionArgs); 2 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy); 3 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 4 db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集; 各参数说明: table:表名称colums:表示要查询的列所有名称集selection:表示WHERE之后的条件语句,可以使用占位符selectionArgs:条件语句的参数数组groupBy:指定分组的列名having:指定分组条件,配合groupBy使用orderBy:y指定排序的列名limit:指定分页参数distinct:指定“true”或“false”表示要不要过滤重复值Cursor:返回值,相当于结果集ResultSet最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。下面是Cursor对象的常用方法: 复制代码 1 c.move(int offset); //以当前位置为参考,移动到指定行 2 c.moveToFirst(); //移动到第一行 3 c.moveToLast(); //移动到最后一行 4 c.moveToPosition(int position); //移动到指定行 5 c.moveToPrevious(); //移动到前一行 6 c.moveToNext(); //移动到下一行 7 c.isFirst(); //是否指向第一条 8 c.isLast(); //是否指向最后一条 9 c.isBeforeFirst(); //是否指向第一条之前 10 c.isAfterLast(); //是否指向最后一条之后 11 c.isNull(int columnIndex); //指定列是否为空(列基数为0) 12 c.isClosed(); //游标是否已关闭 13 c.getCount(); //总数据项数 14 c.getPosition(); //返回当前游标所指向的行数 15 c.getColumnIndex(String columnName);//返回某列名对应的列索引值 16 c.getString(int columnIndex); //返回当前行指定列的值 复制代码实现代码 复制代码String[] params = {12345,123456};Cursor cursor = db.query("user",columns,"ID=?",params,null,null,null);//查询并获得游标if(cursor.moveToFirst()){//判断游标是否为空 for(int i=0;i<cursor.getCount();i++){ cursor.move(i);//移动到指定记录 String username = cursor.getString(cursor.getColumnIndex("username"); String password = cursor.getString(cursor.getColumnIndex("password")); } }复制代码通过rawQuery实现的带参数查询 复制代码Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable");//Cursor c = db.rawQuery("s name, inventory FROM mytable where ID=?",new Stirng[]{"123456"}); result.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); int inventory=result.getInt(2); // do something useful with these result.moveToNext(); } result.close();复制代码 在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。 最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。 上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。 这里直接使用案例讲解:下面是案例demo的界面 SQLiteOpenHelper类介绍 SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。 方法名 方法描述SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version) 构造方法,其中 context 程序上下文环境 即:XXXActivity.this; name :数据库名字; factory:游标工厂,默认为null,即为使用默认工厂; version 数据库版本号 onCreate(SQLiteDatabase db) 创建数据库时调用onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) 版本更新时调用getReadableDatabase() 创建或打开一个只读数据库getWritableDatabase() 创建或打开一个读写数据库首先创建数据库类 复制代码 1 import android.content.Context; 2 import android.database.sqlite.SQLiteDatabase; 3 import android.database.sqlite.SQLiteDatabase.CursorFactory; 4 import android.database.sqlite.SQLiteOpenHelper; 5 6 public class SqliteDBHelper extends SQLiteOpenHelper { 7 8 // 步骤1:设置常数参量 9 private static final String DATABASE_NAME = "diary_db";10 private static final int VERSION = 1;11 private static final String TABLE_NAME = "diary";12 13 // 步骤2:重载构造方法14 public SqliteDBHelper(Context context) {15 super(context, DATABASE_NAME, null, VERSION);16 }17 18 /*19 * 参数介绍:context 程序上下文环境 即:XXXActivity.this 20 * name 数据库名字 21 * factory 接收数据,一般情况为null22 * version 数据库版本号23 */24 public SqliteDBHelper(Context context, String name, CursorFactory factory,25 int version) {26 super(context, name, factory, version);27 }28 //数据库第一次被创建时,onCreate()会被调用29 @Override30 public void onCreate(SQLiteDatabase db) {31 // 步骤3:数据库表的创建32 String strSQL = "create table "33 + TABLE_NAME34 + "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";35 //步骤4:使用参数db,创建对象36 db.execSQL(strSQL);37 }38 //数据库版本变化时,会调用onUpgrade()39 @Override40 public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {41 42 }43 }复制代码正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。 我们需要一个Dao,来封装我们所有的业务方法,代码如下: 复制代码 1 import android.content.Context; 2 import android.database.Cursor; 3 import android.database.sqlite.SQLiteDatabase; 4 5 import com.chinasoft.dbhelper.SqliteDBHelper; 6 7 public class DiaryDao { 8 9 private SqliteDBHelper sqliteDBHelper;10 private SQLiteDatabase db;11 12 // 重写构造方法13 public DiaryDao(Context context) {14 this.sqliteDBHelper = new SqliteDBHelper(context);15 db = sqliteDBHelper.getWritableDatabase();16 }17 18 // 读操作19 public String execQuery(final String strSQL) {20 try {21 System.out.println("strSQL>" + strSQL);22 // Cursor相当于JDBC中的ResultSet23 Cursor cursor = db.rawQuery(strSQL, null);24 // 始终让cursor指向数据库表的第1行记录25 cursor.moveToFirst();26 // 定义一个StringBuffer的对象,用于动态拼接字符串27 StringBuffer sb = new StringBuffer();28 // 循环游标,如果不是最后一项记录29 while (!cursor.isAfterLast()) {30 sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"31 + cursor.getString(2) + "/" + cursor.getString(3) + "/"32 + cursor.getString(4)+"#");33 //cursor游标移动34 cursor.moveToNext();35 }36 db.close();37 return sb.deleteCharAt(sb.length()-1).toString();38 } catch (RuntimeException e) {39 e.printStackTrace();40 return null;41 }42 43 }44 45 // 写操作46 public boolean execOther(final String strSQL) {47 db.beginTransaction(); //开始事务48 try {49 System.out.println("strSQL" + strSQL);50 db.execSQL(strSQL);51 db.setTransactionSuccessful(); //设置事务成功完成 52 db.close();53 return true;54 } catch (RuntimeException e) {55 e.printStackTrace();56 return false;57 }finally { 58 db.endTransaction(); //结束事务 59 } 60 61 }62 }复制代码我们在Dao构造方法中实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。 我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。 我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法: 复制代码 1 public synchronized SQLiteDatabase getReadableDatabase() { 2 if (mDatabase != null && mDatabase.isOpen()) { 3 // 如果发现mDatabase不为空并且已经打开则直接返回 4 return mDatabase; 5 } 6 7 if (mIsInitializing) { 8 // 如果正在初始化则抛出异常 9 throw new IllegalStateException("getReadableDatabase called recursively"); 10 } 11 12 // 开始实例化数据库mDatabase 13 14 try { 15 // 注意这里是调用了getWritableDatabase()方法 16 return getWritableDatabase(); 17 } catch (SQLiteException e) { 18 if (mName == null) 19 throw e; // Can't open a temp database read-only! 20 Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); 21 } 22 23 // 如果无法以可读写模式打开数据库 则以只读方式打开 24 25 SQLiteDatabase db = null; 26 try { 27 mIsInitializing = true; 28 String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径 29 // 以只读方式打开数据库 30 db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY); 31 if (db.getVersion() != mNewVersion) { 32 throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " 33 + mNewVersion + ": " + path); 34 } 35 36 onOpen(db); 37 Log.w(TAG, "Opened " + mName + " in read-only mode"); 38 mDatabase = db;// 为mDatabase指定新打开的数据库 39 return mDatabase;// 返回打开的数据库 40 } finally { 41 mIsInitializing = false; 42 if (db != null && db != mDatabase) 43 db.close(); 44 } 45 }复制代码在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了: 复制代码public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例 return mDatabase; } if (mIsInitializing) { throw new IllegalStateException("getWritableDatabase called recursively"); } // If we have a read-only database open, someone could be using it // (though they shouldn't), which would cause a lock to be held on // the file, and our attempts to open the database read-write would // fail waiting for the file lock. To prevent that, we acquire the // lock on the read-only database, which shuts out other users. boolean success = false; SQLiteDatabase db = null; // 如果mDatabase不为空则加锁 阻止其他的操作 if (mDatabase != null) mDatabase.lock(); try { mIsInitializing = true; if (mName == null) { db = SQLiteDatabase.create(null); } else { // 打开或创建数据库 db = mContext.openOrCreateDatabase(mName, 0, mFactory); } // 获取数据库版本(如果刚创建的数据库,版本为0) int version = db.getVersion(); // 比较版本(我们代码中的版本mNewVersion为1) if (version != mNewVersion) { db.beginTransaction();// 开始事务 try { if (version == 0) { // 执行我们的onCreate方法 onCreate(db); } else { // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法 onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion);// 设置最新版本 db.setTransactionSuccessful();// 设置事务成功 } finally { db.endTransaction();// 结束事务 } } onOpen(db); success = true; return db;// 返回可读写模式的数据库实例 } finally { mIsInitializing = false; if (success) { // 打开成功 if (mDatabase != null) { // 如果mDatabase有值则先关闭 try { mDatabase.close(); } catch (Exception e) { } mDatabase.unlock();// 解锁 } mDatabase = db;// 赋值给mDatabase } else { // 打开失败的情况:解锁、关闭 if (mDatabase != null) mDatabase.unlock(); if (db != null) db.close(); } } }复制代码大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。 看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了 最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码: 复制代码public class SQLiteActivity extends Activity { public DiaryDao diaryDao; //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory); //所以要确保context已初始化,我们可以把实例化Dao的步骤放在Activity的onCreate里 @Override protected void onCreate(Bundle savedInstanceState) { diaryDao = new DiaryDao(SQLiteActivity.this); initDatabase(); } class ViewOcl implements View.OnClickListener { @Override public void onClick(View v) { String strSQL; boolean flag; String message; switch (v.getId()) { case R.id.btnAdd: String title = txtTitle.getText().toString().trim(); String weather = txtWeather.getText().toString().trim();; String context = txtContext.getText().toString().trim();; String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()); // 动态组件SQL语句 strSQL = "insert into diary values(null,'" + title + "','" + weather + "','" + context + "','" + publish + "')"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"添加成功":"添加失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnDelete: strSQL = "delete from diary where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"删除成功":"删除失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; case R.id.btnQuery: strSQL = "select * from diary order by publish desc"; String data = diaryDao.execQuery(strSQL); Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show(); break; case R.id.btnUpdate: strSQL = "update diary set title = '测试标题1-1' where tid = 1"; flag = diaryDao.execOther(strSQL); //返回信息 message = flag?"更新成功":"更新失败"; Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); break; } } } private void initDatabase() { // 创建数据库对象 SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this); sqliteDBHelper.getWritableDatabase(); System.out.println("数据库创建成功"); } }复制代码 Android sqlite3数据库管理工具 Android SDK的tools目录下提供了一个sqlite3.exe工具,这是一个简单的sqlite数据库管理工具。开发者可以方便的使用其对sqlite数据库进行命令行的操作。 程序运行生成的.db文件一般位于"/data/data/项目名(包括所处包名)/databases/.db",因此要对数据库文件进行操作需要先找到数据库文件: 1、进入shell 命令 adb shell2、找到数据库文件 cd data/data ls --列出所有项目 cd project_name --进入所需项目名 cd databases ls --列出现寸的数据库文件 3、进入数据库 sqlite3 test_db --进入所需数据库 会出现类似如下字样: SQLite version 3.6.22Enter ".help" for instructionsEnter SQL statements terminated with a ";"sqlite>至此,可对数据库进行sql操作。 4、sqlite常用命令 .databases --产看当前数据库.tables --查看当前数据库中的表.help --sqlite3帮助.schema --各个表的生成语句 原文地址https://www.cnblogs.com/ITtangtang/p/3920916.html
auto_answer 2019-12-02 01:50:21 0 浏览量 回答数 0

云产品推荐

上海奇点人才服务相关的云产品 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务