如何修改SeekBar的样式

自定义SeekBar样式
谷歌是怎么定义的?
<SeekBar
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
style=”@android:style/Widget.SeekBar”/>

谷歌定义的SeekBar的样式全在@android:style/Widget.SeekBar中,通过样式可以观察发现谷歌是如何定义SeekBar的。可以通过修改样式,覆盖原来样式,从而达到我们需要的效果。

<style name=”Widget.SeekBar”>
<item name=”indeterminateOnly”>false</item>
<!–progressDrawable定义了背景图,进度图,和缓冲图–>
<item name=”progressDrawable”>@drawable/progress_horizontal</item>
<item name=”indeterminateDrawable”>@drawable/progress_horizontal</item>
<item name=”minHeight”>20dip</item>
<item name=”maxHeight”>20dip</item>
<!–thumb定义了滑块的图片–>
<item name=”thumb”>@drawable/seek_thumb</item>
<item name=”thumbOffset”>8dip</item>
<item name=”focusable”>true</item>
<item name=”mirrorForRtl”>true</item>
</style>

如果想改变SeekBar的背景图,进度图,和缓存图等,我们可以自定义progressDrawable,例如
progress_horizotal.xml

<layer-list xmlns:android=”http://schemas.android.com/apk/res/android”>
<!–这是SeekBar的背景图–>
<item android:id=”@android:id/background” android:drawable=”@drawable/seekbar_background”>
</item>
<!– 这是缓存进度图–>
<item android:id=”@android:id/secondaryProgress”>
<clip>
<shape>
<corners android:radius=”5dip” />
<gradient
android:startColor=”#80ffd300″
android:centerColor=”#80ffb600″
android:centerY=”0.75″
android:endColor=”#a0ffcb00″
android:angle=”270″
/>
</shape>
</clip>
</item>
<!–这是SeekBar的进度图–>
<item android:id=”@android:id/progress”>
<clip>
<shape>
<corners android:radius=”5dip” />
<solid android:color=”@color/blue”/>
</shape>
</clip>
</item>

</layer-list>

然后在定义SeekBar的时候可以这样使用:

<SeekBar
style=”@android:style/Widget.SeekBar”
<!–覆盖原来进度的样式–> android:progressDrawable=”@drawable/progress_horizotal”
<!–覆盖滑块的样式–>
android:thumb=”@drawable/seekbar_thumb”
android:minHeight=”3dp”
android:maxHeight=”3dp”
android:max=”100″
android:progress=”50″
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_weight=”1″
android:layout_marginLeft=”4dp”
android:layout_marginRight=”4dp”/>
————————————————

Android SeekBar控件详解

SeekBar控件使用##
使用 SeekBar 实现图片的虚幻

注解:图片虚幻度*大值为255,为实体*小值为0,虚幻值越小,图片越虚幻。

使用 SeekBar 实现图片的虚幻有两种方式:
*种是 : implements SeekBar.OnSeekBarChangeListener 然后重写方法

第二种是:private SeekBar.OnSeekBarChangeListener sbl = new SeekBar.OnSeekBarChangeListener(){}; 创建一个方法 用来监听SeekBar控件

原图为:

%title插图%num

效果图为:

%title插图%num

xml 布局 seekbar.xml:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical” android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<TextView
android:id=”@+id/tv_homeWork_text”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”图片虚幻度为:255″
/>
<ImageView
android:id=”@+id/iv_homeWork_image”
android:layout_width=”100dp”
android:layout_height=”100dp”
android:src=”@drawable/s2″
/>
<SeekBar
android:id=”@+id/sb_homeWork_lucency”
android:layout_width=”250dp”
android:layout_height=”wrap_content”
/>

</LinearLayout>

*种方法

package androidstudio.androidsix;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;

import java.io.File;

/**
* Created by Administrator on 2017/6/6.
*/

public class SeekBarActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {

private ImageView imageView;
private SeekBar sb;
private int currentAlpha=255;
private TextView tv;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.seekbar);
//获取图片
imageView = (ImageView) findViewById(R.id.iv_homeWork_image);
//获取文本框
tv = (TextView) findViewById(R.id.tv_homeWork_text);
//获取SeekBar
sb = (SeekBar) findViewById(R.id.sb_homeWork_lucency);
sb.setMax(255);
sb.setProgress(10);
//seekBar设置滑动事件
sb.setOnSeekBarChangeListener(this);

/**
* 当进度条发生变化时调用该方法
* @param seekBar
* @param progress
* @param fromUser
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//设置文本框的值
tv.setText(“图片虚幻度为:”+progress);
//滑动滑动条时图片虚幻度跟着变幻
imageView.setImageAlpha(progress);
}

/**
* 开始滑动时调用该方法
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}

/**
* 结束滑动时调用该方法
* @param seekBar
*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}

}

第二种方法
SeekBarActivity.java类

package androidstudio.androidsix;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;

import java.io.File;

/**
* Created by Administrator on 2017/6/6.
*/

public class SeekBarActivity extends AppCompatActivity {

private ImageView imageView;
private SeekBar sb;
private int currentAlpha=255;
private TextView tv;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.seekBar);
//获取图片
imageView = (ImageView) findViewById(R.id.iv_homeWork_image);
//获取文本框
tv = (TextView) findViewById(R.id.tv_homeWork_text);
//获取SeekBar
sb = (SeekBar) findViewById(R.id.sb_homeWork_lucency);
sb.setMax(255);
sb.setProgress(10);
//seekBar设置滑动事件
sb.setOnSeekBarChangeListener(sbl);

private SeekBar.OnSeekBarChangeListener sbl = new SeekBar.OnSeekBarChangeListener() {
/**
* 当进度条发生变化时调用该方法
* @param seekBar
* @param progress
* @param fromUser
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//设置文本框的值
tv.setText(“图片虚幻度为:”+progress);
//滑动滑动条时图片虚幻度跟着变幻
imageView.setImageAlpha(progress);
}

/**
* 开始滑动时调用该方法
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}

/**
* 结束滑动时调用该方法
* @param seekBar
*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}
};

}

 

 

为什么要使用linux

1.省心
如果你经常重装系统(对于那些经常捣鼓系统或是有软件洁癖的人),或是偶尔需要来那么一两次。那么你肯定就能体会到在windows下配置环境的麻烦事儿。

以Java为例:
在windows下:
首先我们得先弄一个JDK安装包,确保没有选错,等待下载完成,然后准确地从文件中找到他。
然后开始安装,配置路径(如果你有文件整理的习惯)和环境变量,等待安装完成。
*后因为环境变量的原因我们得注销一下验证结果。
至此Over,但如果其中出了什么问题,恐怕我们还得再倒腾一会儿。
在linux下:
首先连上网络
其次在命令行下输入sudo apt-get update && sudo apt-get install openjdk-7-jdk,键入口令,开始安装。
等待进度条,然后Over。
重复几次之后,我们就会发现使用apt-get进行安装真是屡试不爽。除此之外还有很多东西都可以用它来完成:g++,scrapy,GIMP,jdk,ruby等等。敲两行命令就搞定。而在windows下这都需要一个又一个的软件来补充。

2.便利
提到便利,主要针对的是刚刚装完系统时的情况。此时linux(笔者使用的是Ubuntu)下很多东西都会便利许多。

如果你使用C语言,python进行开发,那么在不依靠IDE的情况下那么就已经可以开工了,因为这些都是系统自带的。
如果你需要处理办公文件,那么系统自带的Libre Office已经基本可以满足需求。
浏览器也无需担心,FireFox还是很不错的。
*实在的一点是,系统是免费的。
这些便利的地方也往往是windows下普遍的短处。

首先开发环境需要自行搭建,系统是不带任何工具的,这都得自己动手,丰衣足食。
使用Office需要单独安装,这需要一定的时间。
IE应该也不是大家普遍常用的吧,装一个浏览器,这又是一个活儿。
*关键的一点,上面的许多工具都不是免费的午餐。除了在工具上我们可以使用替代的产品外,windows这个系统本身,我们是跑不掉的。激活就是一个麻烦事儿。花钱还是花时间破解,二选其一吧。
3.安全
安全问题*早也是在windows上流行起来的,windows在系统方面做得很完整,不过在安全问题上确实不怎么把关。windows平台下一个软件的执行,可以直接更改注册表,启动项,文件关联,文件系统,开关机等等。而windows平台本身却并不为此提供多少安全方面的保障(这与windows平台的主要适用人群有关)。所以,这部分缺陷不得不留给各大厂商的杀毒软件来弥补。于是各种杀毒软件大行其道。这对系统本身来说就像是一种讽刺。

Linux下相对来说要安全很多,首先因为他是开源的,里里外外一清二楚,众目睽睽之下,群众的眼睛是雪亮的。大的漏洞往往少很多。
其二,干什么事儿都得验证一下。软件安装卸载,系统更改,文件权限设置,开关机等等,都得先报上口令,看看是不是本人操作。这些东西看起来挺麻烦,不过也确实为系统提供了很好的保障。至少针对与linux的杀毒工具还是比较少的。

缺陷
事物往往都有正有反,linux也未能例外。

首先图形界面并非尽善尽美,界面很炫酷(Ubuntu Unity),bug也不少。笔者使用时就时常报错。
没有配置同步的功能。如果你之前改过系统,那么重装之后可能还得接着该。或者是自己手动导入备份。这一点就没有微软账户那么方便了。
软件不支持。因为没有钱赚,所以很多windows下的软件很多都没有linux版本,或者有也很难用(没有维护)。不过大都与开发无关,影响不大。

学嵌入式为什么要学Linux?

首先,ARM只是一个32位处理器,可以像51单片机一样用汇编直接操作它的寄存器、RAM、ROM等内部资源,当然也可以用C语言!那么如果只是把ARM当做单片机来用,那就有点大材小用了,那也就没必要学什么linux了。

事实上,ARM是通常都是用来做比较复杂的系统的,而且一般都是多任务系统的,当任务比较多,程序比较大的时候,只用汇编和C可怕难以胜任,所以我们应该给ARM配一个操作系统,用来管理这些任务,那么对于ARM,我们可以作使用哪些操作系统呢?Windows XP行吗?当然不行,太大了,不适合做嵌入式,微软倒是推出了专门针对嵌入式系统开发的WinCE,可是这可不是免费的,而且不提供源码的,给你就像一个黑夹子,对于一般的嵌入式学习,就不太合适了?那么什么样的操作系统才能用于做嵌入式呢?嵌入式操作系统通常有Vxworks,WinCE,uC/OS,Linux/ucLinux等。而uC/OS、Linux因为其免费,而且开源,深受学习者和小公司的欢迎。

uC/OS操作系统内核很小,特别适合初学,而且它是占先式内核,是一个实时的操作系统,特别适合实时性要求较高的场合,其内核很小当然其功能当然也就没有linux强大,而且uC/OS的图形用户界面(GUI)还是刚有雏形,没有linux的GUI好,而它的GUI是收费的。这样的话只是linux是*好了,但是linux本身不是实时的操作系统,不适合实时性高的场合,于是ucLinux产生了,ucLinux是一个实时操作系统(RTOS),因此学习ARM+ucLinux的人较多。

所以,如果只搞uc/OS+ARM,那就可以不学linux了,如果想学习ARM+ucLinux,要在ARM上移植linux,那么移植后,这个装有linux的ARM处理器,当然只能运行linux环境下编译出来的应用程序(这里还有一个交叉编译的概念需要正确理解),所以理所当然要学linux。通常小红帽Red Hat Linux 使用比较广泛,那么不能在Windows下开发ARM+ucLinux吗?其实倒是可以在Windows使用虚拟机软件VMware装一个linux。

 

嵌入式 Linux *主要的工作是让目标板能启动 Linux 系统,启动 Linux 系统的核心工作就是启动 Linux 内核,启动 Linux 内核的核心工作就是让这个 Linux 内核能支持你这个设备,Linux 内核能支持你这个设备的前提就是 Linux 内核经过你的改进后确实能启动,你改进内核的前提就是你会 Linux 内核。 你要是学嵌入式 WinCE ,你同样要学 WinCE ,和学 Linux 内核的主要学习内容一样。当然针对性不同。

基于ARM+Linux系统的智能家居系统

该项目为基于ARM的智能家居系统。系统采用Tiny4412开发板作为中控端,节点使用了两个stm32开发板,通信采用zigbee模块,包含了安防系统,照明系统,环境系统等,并且拥有良好的人机交互界面。

功能简介

1.安防系统

1)智能门禁功能。实现刷卡和密码锁两种开门方式,实现可以查看访客记录,密码连续错误触发报警等功能。同时管理员模式可以增加删除用户和修改密码,门卡挂失功能等。

2)火灾检测功能。采用气体传感器和火焰传感器实现火灾和煤气泄漏检测,当火灾发生时可自动向用户报警。
3)自动报警功能。户外模式下若检测到家中有人,节点会查询门禁系统是否被正常打开,若没有则向用户报警。
4)监控功能。基于Linux下的视频监控系统,摄像头可进行监控,用户在接到节点报警时可以选择打开摄像头,还可截图并查看。
2.照明系统
1)使用热释电模块和GY-30模块实现当有人经过并且光线很暗时自动打开灯,一段时间后自动熄灭。
2)利用pwm波和Zigbee实现可自己从中控端调节节点每个灯的亮度。
3)利用GY-30模块和pwm波实现根据光照强度的变化自动调节灯光亮度。
3.环境系统
1)使用DHT11和GY-30模块分别采集室内温湿度和光照强度,并在中控端要求查看时发送,用户可根据需要调节; 2)使用电机模拟窗帘开闭,可自己调节电机转的方向和圈数。
3)使用气体传感器和火焰传感器实现火灾和煤气泄漏实时检测报告。
4)使用热释电模块检测家中是否有人。

移动端原生app、混合app、webapp 区别

原生app:
安卓操作系统:java语言是安卓系统开发原生语言,原生app

IOS操作系统:object-c 语言是原生app

特点:比较快捷的使用设备端提供的接口,处理速度上有优势。

混合app:
控件:UI webview 可以假装网页,加载网址,直接有原生打包生成app

遇到设备端的拍照、音频处理、视频录制等等操作,需要使用原生方式调取,HTML5只是提供了展示形式,采用的操作javascript;

中间件:phonegap appcan 生成app应用。

特点:展示方面开发效率比原生开发效率更高,人员成本,时间成本上;

WEB APP
还是基于网站本身,利用HTML5技术实现移动端浏览器浏览体现更好展示和应用

比如:微信中的公众号,创建桌面快捷方式,直接进入公众号。

百度提供轻应用,直达号等。

Web与App的五种关系及演变

Web与App的五种关系及演变

本文会阐述Web与App的五种关系类型,以及从流量分发视角和技术演进视角看待关系的演变。

五种关系类型

对于移动端应用来说,Web与App的关系可以分为五种基本类型。移动互联网发展的整体趋势决定了主流的关系类型。每个具体的产品也会随着发展阶段的不同而采用不同的形态,或者各种形态混合共存。

Web or App – 独立应用

Web与App各自独立完成服务。这种模式下Web与App实现的业务可以是差异化的,但各自保持独立且完整,相互之间的交互基本仅限于流量引导。

App从应用商店获取流量,占总流量的主要部分。Web应用独立运行在浏览器中,从搜索引擎获取长尾流量。Web应用可以灵活处理用户的随机需求,在服务过程中找机会将用户引导至App。

Web in App – 壳型混合应用

App作为Web应用的壳资源。App提供的容器能让Web能够突破浏览器的限制实现某些功能。更重要的是有了App的壳就能够利用App的应用商店作为分发渠道。早期的Facebook App就是典型的壳应用。

Web on App – 平台型混合应用

App作为平台承载各种Web应用。平台型App会提供容器让其它合作方以相对独立的Web应用的形式接入平台。平台上的Web应用可以获得部分扩展功能和用户信息。微信公众号和支付宝服务窗就是这种模式的代表。

Web and App – 综合型混合应用

Web与App高度混合,共同实现某项业务。这种模式下原生部分与Web部分会有大量的交互。为了达到用户体验的一致性,App会加强容器的能力,让Web部分能够调用更多的Native资源,以弥补Web部分在体验上的不足。在这类应用中,用户通常难以区分哪些部分是Web,哪些部分是原生。美团、携程等综合型服务商会大量采用这种模式。

Web of App – 融合型应用

使用Web技术来做App。不用Webview来做混合嵌入,而是直接借用部分Web技术来实现原生的功能。这种模式是既要Native的体验,又要Web动态更新与代码复用的好处。微信小程序就是这种模式的代表。

流量分发视角

移动时代到来之前,互联网产品主要以PC端的Web应用为主,入口主要是搜索引擎。早期用户逐渐向移动设备上迁移时,依然保持着使用浏览器和搜索引擎的习惯,这个时候有独立的移动端Web应用来获取流量并完成服务就很重要。

随着App的崛起,流量的来源从搜索引擎变成了应用市场,这个时候主战场就由浏览器转向了App,混合应用应运而生。

一部分App获得市场*对优势地位之后,就会形成头部效应,做流量的二次分发,这类App中接入的应用也就是平台型混合应用。

头部效应与二次分发:

头部App发展到后期,就会有资本挑战已经成熟的Webview嵌入模式,引导合作方做技术融合型应用。合作方会付出更多的开发成本来换取体验上的提升和更多的流量支持。这种做法能够提高技术壁垒,形成差异化与体验优势来巩固头部App自身的地位。

在移动互联网时代,App是主要的流量载体,但从内容生产和消费层面上并没有统一标准,自然就造成了开发成本的大幅上升。

为了降低成本,App从PC时代将Web的标准和内容迁移过来,混合应用应运而生。

混合应用发展到后期,由于头部App厂商的技术和流量优势的持续积累,同时原有的Web标准的演进速度又不及预期,导致头部App可以有实力去掉Web容器这一中间层,直接推行新的内容标准,这些内容标准大量借鉴并且继承了原有的Web标准,同时又更贴近了App的使用场景。

腾讯的微信小程序和Facebook的React Native虽然商业目的上各有不同,但从技术上来讲都是去掉了Web容器这一层,将Web标准延伸到Native领域。

总结

本文阐述了Web与App的五种基本关系类型以及他们各自的产生背景和特点。

从商业角度来看,互联网产品获取流量就像生物需要获取食物一样重要,所以产品形态一定会向流量入口所需要的方向倾斜。

从技术角度来看,Web标准是移动互联网从PC时代继承的技术遗产,未来会有各种基于传统Web标准的新标准产生,另一方面Web标准自身会持续演进以适应移动互联网的快速发展。

这也正说明了一个问题,互联网发展离不开前端开发,而前端开发在app的开发中占据了一定行主导的位置,斑码教育正是看到了这一发展趋势,联合行业大牛研发出一整套全栈前端的实用性课程,专注于现在行业方向,提供从就业角度出发的技术服务,保障学员毕业薪资,,持续关注我进行了解前端知识。

利用Docker构建开发环境

*近接触PAAS相关的知识,在研发过程中开始使用Docker搭建了自己完整的开发环境,感觉生活在PAAS时代的程序员真是幸福,本文会简要介绍下Docker是什么,如何利用Docker来搭建自己的开发环境(本文主要是面向Mac OS X),以及期间所遇到的一些坑和解决方案。(本文会要求你对PAAS、LXC、CGroup、AUFS有一定的了解基础,请自行Google )

大背景–虚拟化技术历史

计算机虚拟化技术由来已久,从硬件仿真到全虚拟化,再到准虚拟化和操作系统虚拟化,各种技术粉墨登场,种类繁多,说实在的有点眼花缭乱和复杂;但用户的核心诉求一直是比较简单的,降低信息技术(IT)的运营成本,提高资源利用率,提高安全性和可靠性等等;虽说用户的核心诉求比较简单,但每个时代的需求场景却是不同的。在大型机时代,虚拟化技术被用来支持多个用户能够同时使用大型机,在x86架构时代,随着企业服务的大规模部署,虚拟化技术主要是用来提高企业资源的利用率,而现如今,随着云计算时代的到来,人们对应用的安全性、隔离性越来越高,对于部署的标准化以及虚拟机的性能要求越来越高。现如今,一种叫Linux容器的虚拟化技术逐渐得到广泛的应用,它的优点有许多,本文不一一赘述,有太多的文章可以参考。

什么是Docker?

docker的英文本意是码头工人,也就是搬运工,这种搬运工搬运的是集装箱(Container),集装箱里面装的可不是商品货物,而是任意类型的App,Docker把App(叫Payload)装在Container内,通过Linux Container技术的包装将App变成一种标准化的、可移植的、自管理的组件,这种组件可以在你的latop上开发、调试、运行,*终非常方便和一致地运行在production环境下。

Docker的核心底层技术是LXC(Linux Container),Docker在其上面加了薄薄的一层,添加了许多有用的功能。这篇stackoverflow上的问题和答案很好地诠释了Docker和LXC的区别,能够让你更好的了解什么是Docker, 简单翻译下就是以下几点:

  • Docker提供了一种可移植的配置标准化机制,允许你一致性地在不同的机器上运行同一个Container;而LXC本身可能因为不同机器的不同配置而无法方便地移植运行;
  • Docker以App为中心,为应用的部署做了很多优化,而LXC的帮助脚本主要是聚焦于如何机器启动地更快和耗更少的内存;
  • Docker为App提供了一种自动化构建机制(Dockerfile),包括打包,基础设施依赖管理和安装等等;
  • Docker提供了一种类似git的Container版本化的机制,允许你对你创建过的容器进行版本管理,依靠这种机制,你还可以下载别人创建的Container,甚至像git那样进行合并;
  • Docker Container是可重用的,依赖于版本化机制,你很容易重用别人的Container(叫Image),作为基础版本进行扩展;
  • Docker Container是可共享的,有点类似github一样,Docker有自己的INDEX,你可以创建自己的Docker用户并上传和下载Docker Image;
  • Docker提供了很多的工具链,形成了一个生态系统;这些工具的目标是自动化、个性化和集成化,包括对PAAS平台的支持等;

那么Docker有什么用呢?对于运维来说,Docker提供了一种可移植的标准化部署过程,使得规模化、自动化、异构化的部署成为可能甚至是轻松简单的事情;而对于开发者来说,Docker提供了一种开发环境的管理方法,包括映像、构建、共享等功能,而后者是本文的主题。

Docker的安装和构成

Docker官方本身提供了非常具体的安装教程,这里不说具体的安装过程,请参考Docker安装(Mac系统),重要的是描述下原理和安装完成后的结构,好对Docker更好的了解。 由于LXC本身不支持Mac内核,因此需要跑一个VirtualBox虚拟机(TinyCoreLinux)来安装,幸好Docker社区提供了一个非常方便的工具boot2docker(其实就是一个VBoxManage的包装shell脚本),用于安装Mac下的整个Docker环境。具体的结构如下:

docker-install

如图所示,安装完成后,具体情况如下:

  • 在Mac的home目录~/.boot2docker下创建了虚拟机所需要的文件,其中boot2docker.iso是虚拟机映像,这是一个由CD-ROM引导的TinyCoreLinux系统;而boot2docker-vm.vmdk文件则是你的虚拟机磁盘,你所有的持久化数据都存放在这里,包括docker创建的lxc容器等文件。
  • 在Mac下,docker被分为客户端docker-client和服务端docker-daemon两部分,如果是在linux(比如ubuntu),实际上则是同一个可执行文件同时充当客户端和服务端。docker-daemon可以监听unix scoket,也可以在tcp socket(默认端口为4234),docker-client会通过一个叫DOCKER_HOST的环境变量读取服务地址和端口,因此你应该在你的bash_profile文件里面添加这么一行:

docker-daemon跑在虚拟机上,这个程序实际上就是接收docker-client发送过来的消息命令,创建、启动和销毁lxc容器,以及docker本身的版本管理、映像存储等等 运行你的*个docker容器 安装完成后,就差不多可以开始创建和运行docker容器了,在这之前,你首先得下载一个Image,什么是Image?我们先来了解docker的2个基础概念:Image和Container。

Container和Image 在Docker的世界里,Image是指一个只读的层(Layer),这里的层是AUFS里的概念,*直观的方式就是看一下docker官方给出的图:

docker-filesystems-multilayer

Docker使用了一种叫AUFS的文件系统,这种文件系统可以让你一层一层地叠加修改你的文件,*底下的文件系统是只读的,如果需要修改文件,AUFS会增加一个可写的层(Layer),这样有很多好处,例如不同的Container可以共享底层的只读文件系统(同一个Kernel),使得你可以跑N多个Container而不至于你的硬盘被挤爆了!这个只读的层就是Image!而如你所看到的,一个可写的层就是Container。

那Image和Container的区别是什么?很简单,他们的区别仅仅是一个是只读的层,一个是可写的层,你可以使用docker commit 命令,将你的Container变成一个Image,也就是提交你所运行的Container的修改内容,变成一个新的只读的Image,这非常类似于git commit命令,感觉真棒!

实际上这就是Docker对Container映像的版本管理基石,AUFS文件系统实在是太美妙了,更多细节可以参考DotCloud的这篇文章

运行和退出

在了解了Image和Container的概念后,我们可以开始下载一个Image,Docker的好处就是提供了一个类似github的Image仓库管理,你可以非常方便pull别人的Image下来运行,例如,我们可以下载一个ubuntu Image:

这里的13.10是一个Tag,类似于git的tag,这里的tag可以为你制定一个ubuntu的版本。下载完成后,执行docker images命令可以列出你已经下载或者自己构建的image:(请允许我使用可爱的马赛克 🙂 )

QQ20140322-1

你可以看到ubuntu:13.10的大小为178MB,以及它的IMAGE ID。 现在我们开始运行一个Container,命令很简单,例如我们想运行一个执行Shell终端的Container:

QQ20140322-2

如你看到的,你已经进入到一个Shell里面,可以执行你想执行的任何命令,就和在ubuntu里面一样,进去后默认是在根目录/下,可以看到经典的unix/linux目录结构,以及你所运行的bash版本等信息。你可以给你的Container定一个名字,通过–name选项,例如这里命名了shell,日后你就可以直接用这个名字引用Contanier。

退出一个Container也很简单,你直接exit就好了。 其他更多的命令这里不做赘述,因为官方的文档已经非常全面,这里只是给一个直观的初步印象。下面进入主题。

利用Docker搭建开发环境

我们先看看程序员在搭建开发环境时遇到的一些问题:

  • 软件安装麻烦,比如很多公司都使用redhat,一般开发人员又不给root,安装一个nginx或者是mysql都得自己下载编译安装 权限问题,没有root,一些软件无法运行,例如dnsmasq;
  • 没有root,无法修改hosts,无法netstat -nptl,无法tcpdump,无法iptable
  • 隔离性差,例如不同的开发人员如果在同一台主机环境下共享开发,虽然是用户隔离,但端口如果不规范可能会冲突;同一个Mysql如果权限管理不好很有可能误删别人的数据
  • 可移植性差,例如和生产环境不一致,开发人员之间也无法共享;更严重的情况是当有新人入职时,通常需要又折腾一遍开发环境,无法快速搭建

这些问题可以通过在本地搭建虚拟机来解决,但虚拟机是一个很笨重的解决方案,Docker是一个非常轻量级的方案,而且还拥有虚拟机没有的一些功能,例如标准化Image,Image共享等,更重要的是,利用Docker,你可以运行非常多的容器,在你的Mac下搭建一个分布式的开发环境根本不是什么大的问题,而且对内存、磁盘和cpu的消耗相比传统的虚拟机要低许多,这些都要归功于AUFS和LXC这两大神奇的技术。

构建基础Image

想要搭建一个节省磁盘空间和扩展性良好的开发环境,*重要的*步就是构建一个基础性的Image,比如你的主要开发语言是Ruby,那么你肯定需要一个已经安装好以下工具的基础Image:

  • ruby
  • bundler
  • gem

然后在此基础上,你可以扩展这个基础的Image(下面叫base)为不同的开发环境,例如rails,或者是nats。当然,你的这个base也可以从别人的Image扩展而来,还记得我们刚刚pull下来的ubuntu:13.10这个Image吗?你可以从这个Image扩展开始构建你的base,如何做呢?Docker提供了一种标准化的DSL方式,你只需要编写一个Dockerfile,运行docker build指令,就可以构建你自己的Image,这有点像Makefile和make命令一样,只是大家要构建的内容和构建语言不同。

Dockerfile的语法请参考Dockerfile Reference,这里给出上面提到的Ruby开发的base Dockerfile示例:

这里只用到了很简单的2个指令:FROM和RUN,FROM指定了我们要扩展的Image,RUN指定我们要运行的命令,这里是安装ruby,gem、bundler等软件。写好Dockerfile后,运行以下指令就可以创建你的base image了:

-t 选项是你要构建的base image的tag,就好比ubuntu:13.10一样 –rm 选项是告诉Docker在构建完成后删除临时的Container,Dockerfile的每一行指令都会创建一个临时的Container,一般你是不需要这些临时生成的Container的 如你所想,我们可以像运行ubuntu:13.10那样运行我们的base了:

这里我们使用dev:base这个Image运行了一个irb解释器(Ruby的交互式解释器)。 在构建完base之后,你可以依样画葫芦构建你的rails环境,很简单,只需要FROM dev:base,然后RUN安装你的rails组件就可以了,不再赘述。*终你可能构建的开发环境是这样的:

docker-dev

如上图所示,base和service都是从ubutnu:13.10继承而来,他们作为不同的基础开发环境,base是ruby开发环境(也许命名为dev:ruby更为合适?),而service是一些基础数据服务,例如mysql,memcache,我建议将这些第三方组件集中在一个Container中,因为他们的环境不经常修改,可以作为一种底层服务Container运行,除非你需要构建分布式的服务,例如memcache集群,那可以继续拆分。

指定Image入口

当你构建完你的base Image和其他应用的Image之后,你就可以启动这些Image了,还记得前面我们给出的运行命令吗?

这里我们运行了一个bash,这样你就可以在shell里面执行你所想要执行的任何命令了,但是我们有时候并不想每次都启动一个shell,接着再在shell里面启动我们的程序,比如一个mysql,而是想一启动一个容器,mysql服务就自动运行了,这很简单,Dockerfile提供了CMD和ENTRYPOINT这2个指令,允许你指定一个Image启动时的默认命令。CMD和ENTRYPOINT的区别是CMD的参数可以由docker run指令指定的参数覆盖,而ENTRYPOINT则不可以。例如我们想运行一个memcached服务,可以这么写Dockerfile:

或者可以这么写:

注意不要把memcached启动为后台进程,即加上-d选项,否则docker启动的container会马上stop掉,这点我也觉得比较意外。 接着我们build这个Image:

这样,当你build完你的Image后,你可以直接将该Image运行为一个容器,它会自动启动mysql服务:

注意使用-d (detach) 选项,这样这个container就会作为后台进程运行了,接着你可以使用docker ps命令查看是否有在运行。

磁盘映射

大部分时候你会需要把你host主机(宿主)上的目录映射到Container里面,这样你就非常方便地在host主机上编辑代码,然后直接就可以在Container里面运行它们,而不用手动copy到Container里面再重启Container。按理将host的目录映射到guest(指Container)上应该是一件很容易的事情,就好像VMWare那样,但可惜的是,由于Mac上的Docker多了一层虚拟机,因此多了一层周折,你必须先VM上的目录通过sshfs mount到host(指Mac)上,然后再将你的目录或文件copy到这个mount的目录,再将VM上的这个目录映射到Container里,听起来比较拗口,画个图会清晰很多。

docker-disk-map

如上图所示,VM里面的/mnt/sda1/dev/目录(你需要自己创建)通过sshfs命令mount到了host主机(Mac)的~/workspace/dev/目录 ,而VM里的/mnt/sda1/dev/目录又被映射到了Container的/src/目录下,这样你就可以在Container里面的/src/目录下访问你的host文件了。具体如何做呢?首先你需要安装sshfs命令,然后将VM的password写到一个文件中,例如~/.boot2docker/b2d-passwd,在用sshfs命令mount起VM的/mnt/sda1/dev目录:

接着你在run一个Container的时候需要通过-v选项来将/mnt/sda1/dev/映射到/src目录:

这样你就可以在你的Container的/src目录下看到你host里的文件了。 磁盘映射还有2个地方需要注意:

  • 你的文件实际上是存储在VM里面的,也就是说你需要将你的目录或者文件copy到VM里面,你sshfs之后,就是copy到~/workspace/dev目录下
  • 千万不要sshfs mount非/mnt/sda1下的目录,因为VM里面跑的是TinyCoreLinux,这个OS的rootfs是临时性的(放在内存的,实际上就是boot2docker.iso文件里面的一个rootfs),因此其根目录/下的东西(包括/home)根本不会持久化,只有/mnt/sda1这个目录下的才能持久化。如果你放在/home目录下,只要VM一重启,就会丢失的,/mnt/sda1则不会,实际上就是那个~/.boot2docker-vm.vmdk文件挂载到了/mnt/sda1目录下

端口映射

和磁盘映射一样,你有时候会需要将Container的端口映射到host主机上,同样蛋疼的是,由于多了一层VM,端口映射也显得比较麻烦。首先你需要设置VirtualBox的端口映射,然后再将Container的端口映射到你的VM里面:

docker-port-map

具体是这么做的,通过2条命令:

也就是说在docker run的时候通过-p选项指定要映射的端口到VM,而boot2docker ssh命令则是将VM的8000端口映射到了host(Mac)的8000端口,这样你就可以通过Mac的localhost:8000访问Container的8000端口了。 其实,有另一种解决方案就是你不用映射到host(Mac),而是直接登录到VM里面进行访问就好了,boot2docker ssh就可以登录到VM,这样就类似于你的host是ubuntu,但这种解决方案的问题是这个ubuntu太弱了(TinyCoreLinux),如果你在这个ubuntu里面开发代码,或者是运行浏览器,是非常蛋疼的事情,关键还是这个ubuntu是每次重启都会复原的!所以我建议还是做多一层映射好了。 *后,实际上在VM里面,你是可以直接访问所有的Container的端口的,因为VM到Container的网络都是桥接的。

其他的一些坑

在使用的过程中,还遇到一些不少的坑:

  1. /etc/hosts文件无法修改,这样你就不能自己做域名解析
  2. VM的系统时间是UTC +0000的,而且貌似无法修改
  3. Container的IP无法指定为静态IP,因此每次重启Container时,IP可能会变化

第1个问题的解决方案是通过安装dnsmasq软件来做域名解析:

第2个问题的解决方案就稍微麻烦些,起码我没有找到更好的解决方案,我是将boot2docker.iso文件重新制作一次来解决这个问题的:

第三个问题暂时无法解决(可能需要编辑底层的LXC配置文件)。

docker的限制以及后续的一些想法

docker其实还是有一些限制的:

  • 要求你的环境是Linux的,而且内核必须很新(>= 2.6.27 (29)),这其实是LXC本身的限制,和docker无关
  • docker的Container目前host是不能修改的,当然有解决方案(dnsmasq)
  • docker的Container也暂时无法指定静态IP

用docker作为开发环境甚至是生产环境其实还有很多地方值得尝试:

  • 在团队内部构建本地的仓库,标准化所有的开发环境,使得团队的新人可以快速上手
  • 在生产环境部署docker,这其实是PAAS的虚拟化和自动化的一种方式,利用LXC和Docker能够更便捷地实施PAAS
  • 尝试用docker做分布式集群模拟和测试,成本会更加低廉,更加容器维

iPhoneX适配方案

*对长度单位
英寸 厘米 毫米 磅 pc
inch cm mm pt pica
相对长度单位
是网页设计中使用*多的长度单位,包括px、em、rem等

屏幕尺寸

%title插图%num
指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米

iPhone 4/4S iPhone 5/5C/5S/SE iPhone 6/6S iPhone 6S Plus iPhone 7 iPhone 7 Plus iPhone 8 iPhone 8 Plus iPhone X
3.5英寸 4英寸 4.7英寸 5.5英寸 4.7英寸 5.5英寸 4.7英寸 5.5英寸 5.8英寸
屏幕分辨率
指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素来表示一个手机的分辨率,如1960*1080(这里的1像素指的是物理设备的1个像素点)

机型 分辨率 机型 分辨率 机型 分辨率
iPhone 4/4S 960*640 iPhone 6S Plus 1920*1080 iPhone 8 Plus 1920*1080
iPhone 5/5S 1136*640 iPhone 7 1334*750 iPhone X 2436*1125
iPhone SE 1136*640 iPhone 7 Plus 1920*1080
iPhone 6/6S 1334*750 iPhone 8 1334*750
屏幕像素密度

%title插图%num
屏幕上每英寸可以显示的像素点的数量,单位是ppi,即pixels per inch的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小

%title插图%num

屏幕上勾股定理算出对角线的分辨率:√(1920²+1080²)≈2203px
对角线分辨率除以屏幕尺寸:2203/5≈440dpi
1920^2 + 1080^2 ≈ 2203^2 //3686400 + 1166400 = 4852800
2203 / 5 ≈ 440
1
2
PPI与DPI

%title插图%num
PPI(Pixel Per Inch by diagonal):表示沿着对角线,每英寸所拥有的像素(Pixel)数目
PPI数值越高,代表显示屏能够以越高的密度显示图像,即通常所说的分辨率越高、颗粒感越弱

ppi与dpi 描述
ppi pixels per inch,屏幕上每英寸可以显示的像素点的数量,即屏幕像素密度
dpi dots per inch,*初用于衡量打印物上每英寸的点数密度,就是打印机可以在一英寸内打多少个点。当dpi的概念用在计算机屏幕上时,就称之为ppi。ppi和dpi是同一个概念,Android比较喜欢使用dpi,IOS比较喜欢使用ppi
Viewport
移动端开发中,通常我们都会采用meta标签设置viewport

<meta name=”viewport” content=”width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no”>
1
viewport是什么?

%title插图%num
通俗来讲,移动端的viewport就是我们所能看到的手机端浏览器的可视窗口大小,但viewport又不仅仅局限于可视窗口的大小,一般情况下,它是比默认窗口大小要大的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动端正常显示为桌面浏览器而设计的网页,移动端的浏览器都会默认把自己的默认的viewport设为980px到1024px不等,但其后果就是会出现横向滚动条,因为移动端浏览器可视区域的大小是比默认的viewport宽度要小的

参数 描述
width 设置layout viewport的宽度,为一个正整数,或字符串”device-width”
initial-scale 设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale 允许用户的*小缩放值,为一个数字,可以带小数
maximum-scale 允许用户的*大缩放值,为一个数字,可以带小数
height 设置layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable 是否允许用户进行缩放,值为”no”或”yes”, no 代表不允许,yes代表允许
不同的设备对1px有不一样的定义
在css中我们一般使用px作为单位,在桌面浏览器中css的1个像素往往都是对应着电脑屏幕的1个物理像素,这可能会造成我们的一个错觉,那就是css中的像素就是设备的物理像素。但实际情况却并非如此,css中的像素只是一个抽象的单位,在不同的设备或不同的环境中,css中的1px所代表的设备物理像素是不同的。在为桌面浏览器设计的网页中,我们无需对这个津津计较,但在移动设备上,必须弄明白这点。在早先的移动设备中,屏幕像素密度都比较低,如iphone3,它的分辨率为320×480,在iphone3上,一个css像素确实是等于一个屏幕物理像素的。后来随着技术的发展,移动设备的屏幕像素密度越来越高,从iphone4开始,苹果公司便推出了所谓的Retina屏,分辨率提高了一倍,变成640×960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,像素却多了一倍,这时,一个css像素是等于两个物理像素的。

在移动端浏览器中以及某些桌面浏览器中,window对象有一个devicePixelRatio属性,它的官方的定义为:设备物理像素和设备独立像素的比例,也就是devicePixelRatio = 物理像素 / 独立像素。css中的px就可以看做是设备的独立像素,所以通过devicePixelRatio,我们可以知道该设备上一个css像素代表多少个物理像素。例如,在Retina屏的iphone上,devicePixelRatio的值为2,也就是说1个css像素相当于2个物理像素

其实就是移动端和PC端的px是不同的,移动端的屏幕可视区域(viewport)小但像素多,所以跟PC相比的每个独立像素点的物理像素是多的,也就是移动端物理像素更密集,所以更PC的独立像素有dp的倍数转换

在进行具体的分析之前,首先得知道下面这些关键性基本概念(术语)。

物理像素(physical pixel)
一个物理像素是显示器(手机屏幕)上*小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。

设备独立像素(density-independent pixel)
设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。

所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。

设备像素比(device pixel ratio )
设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:

设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向
1
还可以通过window.devicePixelRatio获取到当前设备的dpr

window.devicePixelRatio
1
机型 iPhone 3G/3GS iPhone 4/4S iPhone 5/5C/5S/SE iPhone 6/6S iPhone 6S Plus iPhone 7 iPhone 7 Plus iPhone 8 iPhone 8 Plus iPhone X
屏幕尺寸 3.5英寸 3.5英寸 4英寸 4.7英寸 5.5英寸 4.7英寸 5.5英寸 4.7英寸 5.5英寸 5.8英寸
独立像素(CSS像素) 480*320 480*320 568*320 667*375 736*414 667*375 736*414 667*375 736*414 812*375
物理像素(分辨率) 480*320 960*640 1136*640 1334*750 1920*1080(2208×1242) 1334*750 1920*1080 1334*750 1920*1080 2436*1125
ppi/dpi(像素密度) 163 326 326 326 401 326 401 326 401 458
dpr(倍图) 1 2 2 2 3(2.5) 3 3 3 3 3(2.9)
如果APP要同时兼容iPhone3GS~iPhone6+,则需要提供icon.png/icon@2x.png/icon@3x.png三种分辨率的图片

在Android中,规定以160dpi为基准,1dp=1px。如果密度是320dpi,则1dp=2px,以此类推

2K分辨率指的是屏幕分辨率达到了一种级别,指屏幕横向像素达到2000以上(iPhone X是2K屏)

iPhoneX的适配
background-color
如果网页设置了一个背景颜色,那么*简单解决方案是,在body节点设置background-color,使背景颜色填充整个屏幕,从而解决横屏显示左右白边的问题

viewport-fit
<!–默认值:可视窗口完全包含网页内容 相当于在安全区域展示–>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, viewport-fit=auto”>
<!–或–>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, viewport-fit=contain”>
<!–网页内容完全覆盖可视窗口–>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, viewport-fit=cover”>

 

viewport-fit 描述 示例 示例 示例
auto/contain 默认值,页面内容显示在safe area内 示例1
cover 页面内容充满屏幕 示例1 示例2 示例3
横屏列表侧刘海 横屏下列表环绕刘海 示例1
设置auto前

%title插图%num

设置cover后

%title插图%num
safe-area-inset-*
在设置viewport-fit=cover之后,Web中会新增四个常量

safe-area-inset-top
safe-area-inset-right
safe-area-inset-left
safe-area-inset-bottom

分别表示safe area和可视窗口viewport顶部,右边,左边,底部的间距,可以用于设置margin和padding或者*对定位时left和top

注意:在横屏和竖屏状态下,safe-area-inset-*的值不同

%title插图%num

为了解决应用viewport-fit=cover之后,有些显示内容被裁剪的问题,我们可以通过添加边距使得网页主要内容处于safe area中不被裁剪,解决方式如下

%title插图%num

padding: constant(safe-area-inset-top) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left);

示例,比如下面是顶部导航条的适配,能让左上右都能出现padding来让元素保留在安全区域以内

总结为,我们可以利用safe-area-inset-*做以下适配,详细请看DEMO

竖屏下,对底部做padding-bottom: constant(safe-area-inset-bottom);,其他设置是无意义的
横屏下,对底部设置左,下,右的safe-area-inset-*,对头部设置左和右的safe-area-inset-*,其他设置也是无意义的
<header><button>返回</button> 头部</header>
<style>
* {margin: 0;padding: 0;}
body {
width: 100%;height: 100%;
//设置背景颜色,也是一种适配方案
background-color: #A4F4B0;
}
header {
background-color: red;height: 50px;line-height: 50px;width: 100%;color: white;position: fixed;left: 0;right: 0;top: 0;bottom: 0px;
//cover下元素出现对应的padding来适配
padding-left: constant(safe-area-inset-left);
padding-right: constant(safe-area-inset-right);
//padding-bottom: constant(safe-area-inset-bottom);
padding-top: constant(safe-area-inset-top);
}

button {
display: inline-block;background-color: blue;color: white;border: none;height: 50px;width: 80px;
//字体记得必须设置,不然按钮会有像素的误差
font-size: 18px;
}
</style>

媒体查询
device-width
device-height
-webkit-device-pixel-ratio
注意-webkit-device-pixel-ratio必须加前缀,否则无效

/*iPhoneX的适配*/
@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
header {
background-color: black;
}
}
/*iPhone8P的适配*/
@media only screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) {
header {
background-color: deepskyblue;
}
}

参考文档
iPhone-X适配方案
移动端高清、多屏适配方案
手淘移动端适配的方案学习和相关思考
手淘适配方案lib-flexible
iPhone X适配参考文档
iPhone X的Web设计
剖析 iOS 11 网页适配问题
关于H5页面在iPhoneX适配

深入理解iPhone数据持久化(手把手教你iphone开发 – 基础篇)

在所有的移动开发平台数据持久化都是很重要的部分:在j2me中是rms或保存在应用程序的目录中,在symbian中可以保存在相应的磁盘目录中和数据库中。symbian中因为权限认证的原因,在3rd上大多数只能访问应用程序的private目录或其它系统共享目录。在iphone中,apple博采众长,提供了多种数据持久化的方法,下面笔者会逐个进行详细的讲解。

iphone提供的数据持久化的方法,从数据保存的方式上讲可以分为三大部分:属性列表、对象归档、嵌入式数据库(SQLite3)、其他方法。

一、属性列表NSUserDefaults

NSUserDefaults类的使用和NSKeyedArchiver有很多类似之处,但是查看NSUserDefaults的定义可以看出,NSUserDefaults直接继承自NSObject而NSKeyedArchiver 继承自NSCoder。这意味着NSKeyedArchiver实际上是个归档持久化的类,也就可以使用NSCoder类的[encodeObject: (id)objv forKey:(NSString *)key]方法来对数据进行持久化存储。

 

– (void)applicationDidFinishLaunching:(UIApplication *)application {
NSString *strOne = @”Persistent data1″;
NSString *strTwo = @”Persistent data 2″;

NSMutableArray *persistentArray = [[NSMutableArray alloc] init];
[persistentArray addObject:strOne];
[persistentArray addObject:strTwo];

//archive
NSUserDefaults *persistentDefaults = [NSUserDefaults standardUserDefaults];
[persistentDefaults setObject:persistentArray forKey:@”myDefault”];
NSString *descriptionDefault = [persistentDefaults description];
NSLog(@”NSUserDefaults description is :%@”,descriptionDefault);

//unarchive
NSArray *UnpersistentArray =

[persistentDefaults objectForKey:@”myDefault”];

NSString *UnstrOne = [UnpersistentArray objectAtIndex:0];
NSString *UnstrTwo = [UnpersistentArray objectAtIndex:1];

NSLog(@”UnstrOne = %@,UnstrTwo = %@”,UnstrOne,UnstrTwo);

// Override point for customization after application launch
[window makeKeyAndVisible];
}

二、对象归档NSKeyedArchiver和NSKeyedUnarchiver

iPhone和symbian 3rd一样,会为每一个应用程序生成一个私有目录,这个目录位于

/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications下,并随即生成一个数字字母串作为目录名,在每一次应用程序启动时,这个字母数字串都是不同于上一次的,上一次的应用程序目录信息被转换成名为.DS_Store隐藏文件,这个目录的文件结构如下图:

通常使用Documents目录进行数据持久化的保存,而这个Documents目录可以通过NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserdomainMask,YES)得到,代码如下:

– (void)applicationDidFinishLaunching:(UIApplication *)application {
NSString *strOne = @”Persistent data1″;
NSString *strTwo = @”Persistent data 2″;

NSArray *persistentArray = [NSArray arrayWithObjects:strOne,strTwo,nil];
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,    NSAllDomainsMask, YES);

int pathLen = [pathArray count];

NSLog(@”path number is :%d”,pathLen);

NSString *filePath;

for(int i = 0; i < pathLen; i++)
{
filePath = [pathArray objectAtIndex:i];
NSLog(@”%d path is :%@”,i,filePath);
}

NSString *myFilename = [filePath stringByAppendingPathComponent:@”myFile.rtf”];

NSLog(@”myfile’s path is :%@”,myFilename);

// no files generated in correspond directory now

[NSKeyedArchiver archiveRootObject:persistentArray toFile:myFilename];
// now the myFile.rtf is generated

// Override point for customization after application launch
[window makeKeyAndVisible];
}

 

NSSearchPathForDirectoriesInDomains()的第二个参数是个枚举值,在笔者的测试代码中,只有NSUserDomainMask和NSAllDomainsMask可以获取到目录数为1,其余的皆为0,打印出来的结果如下:

[Session started at 2009-11-10 21:30:08 +0800.]
2009-11-10 21:30:10.516 PersistentExample[763:207] path number is :1
2009-11-10 21:30:10.518 PersistentExample[763:207] 0 path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/C93DC783-F137-4660-AE5A-08C3E11C774B/Documents
2009-11-10 21:30:10.521 PersistentExample[763:207] myfile’s path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/C93DC783-F137-4660-AE5A-08C3E11C774B/Documents/myFile.rtf
Terminating in response to SpringBoard’s termination.

[Session started at 2009-11-10 21:32:27 +0800.]
2009-11-10 21:32:30.091 PersistentExample[803:207] path number is :1
2009-11-10 21:32:30.092 PersistentExample[803:207] 0 path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/763E6772-E754-452F-8532-80C2CE4466B5/Documents
2009-11-10 21:32:30.100 PersistentExample[803:207] myfile’s path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/763E6772-E754-452F-8532-80C2CE4466B5/Documents/myFile.rtf
Terminating in response to SpringBoard’s termination.

从打印的结果如下,每次应用程序启动时生成的数字字母串目录名字并不一样。在调用[NSKeyedArchiver archiveRootObject:persistentArray toFile:myFilename]方法前,文件myFile.rtf并每生成,只有在调用此方法后才产生相应的文件。

下面需要把数据从属性列表中读取出来,在上面的代码中,笔者使用NSArray保存数据。但在大多数应用程序中,数据的尺寸并不是固定的,这个时候就需要使用NSMutalbeArray动态的保存数据,代码优化如下:

–          (void)applicationDidFinishLaunching:(UIApplication *)application {
NSString *myFilename;
// archive
{
NSString *strOne = @”Persistent data1″;
NSString *strTwo = @”Persistent data 2″;

NSMutableArray *persistentArray = [[NSMutableArray alloc] init];
[persistentArray addObject:strOne];
[persistentArray addObject:strTwo];

NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,    NSAllDomainsMask, YES);

int pathLen = [pathArray count];
NSLog(@”path number is :%d”,pathLen);

NSString *filePath;

for(int i = 0; i < pathLen; i++)
{
filePath = [pathArray objectAtIndex:i];

NSLog(@”%d path is :%@”,i,filePath);
}

myFilename = [filePath stringByAppendingPathComponent:@”myFile.rtf”];

NSLog(@”myfile’s path is :%@”,myFilename);

[NSKeyedArchiver archiveRootObject:persistentArray toFile:myFilename];
}

// unarchive
{
NSArray *unarchiveArray = [NSKeyedUnarchiver unarchiveObjectWithFile:myFilename];
NSString *UnstrOne = [unarchiveArray objectAtIndex:0];
NSString *UnstrTwo = [unarchiveArray objectAtIndex:1];

NSLog(@”UnstrOne = %@,UnstrTwo = %@”,UnstrOne,UnstrTwo);
}

// Override point for customization after application launch
[window makeKeyAndVisible];
}

 

输出结果如下:

 

[Session started at 2009-11-10 22:41:57 +0800.]
2009-11-10 22:41:59.344 PersistentExample[1082:207] path number is :1
2009-11-10 22:41:59.346 PersistentExample[1082:207] 0 path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/055CD17C-864E-4A83-ABF0-5F01EE85BD5A/Documents
2009-11-10 22:41:59.355 PersistentExample[1082:207] myfile’s path is :/Users/sundfsun2009/Library/Application Support/iPhone Simulator/User/Applications/055CD17C-864E-4A83-ABF0-5F01EE85BD5A/Documents/myFile.rtf
2009-11-10 22:41:59.357 PersistentExample[1082:207] UnstrOne = Persistent data1,UnstrTwo = Persistent data 2
Terminating in response to SpringBoard’s termination.

从上面的图中可以看到,目录中还有个tmp目录,读者也可以把数据保存在tmp目录中,获取这个目录使用NSTemporaryDirectory()方法。

三、嵌入式数据库(SQLite3)

嵌入式数据库持久化数据就是把数据保存在iphone的嵌入式数据库系统SQLite3中,本质上来说,数据库持久化操作是基于文件持久化基础之上的。
要使用嵌入式数据库SQLite3,首先需要加载其动态库libsqlite3.dylib,这个文件位于/Xcode3.1.4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.1.sdk/usr/lib目录下。在Framework文件夹上右击,选择“Adding->Existing Files…”,定位到上述目录并加载到文件夹。

首先在头文件中做如下修改:

#import <UIKit/UIKit.h>

#include “sqlite3.h”
#define kFileName @”mydb.sql”

@interface PersistentExampleAppDelegate : NSObject <UIApplicationDelegate> {
sqlite3 *database;
UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

– (void)applicationDidFinishLaunching:(UIApplication *)application {

NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *paths = [[path objectAtIndex:0] stringByAppendingPathComponent:kFileName];

NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL findFile = [fileManager fileExistsAtPath:paths];

NSLog(@”Database file path = %@”,paths);

// 如果找到了数据库文件
if(findFile)
{
NSLog(@”Database file have already existed.”);

if(sqlite3_open([paths UTF8String], &database) != SQLITE_OK)//打开数据库失败
{
sqlite3_close(database);
NSAssert(0,@”Failed to open database”);
}
}else
{
NSLog(@”Database file does not exsit!”);
if(sqlite3_open([paths UTF8String], &database) != SQLITE_OK)//打开数据库失败
{
sqlite3_close(database);
NSAssert(0,@”Failed to open database”);
}
}

char *errorMsg;

 

//创建表
NSString *createSQL = @”create table if not exists fields (row integer primary key, field_data text);”;
if(sqlite3_exec(database, [createSQL UTF8String],NULL,NULL,&errorMsg)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert1(0,@”Error creating table: %s”,errorMsg);
}

NSString *strOne = @”Persistent data1″;
NSString *strTwo = @”Persistent data 2″;

NSMutableArray *persistentArray = [[NSMutableArray alloc] init];
[persistentArray addObject:strOne];
[persistentArray addObject:strTwo];

for (int i = 0; i < [persistentArray count]; i++) {
NSString *upDataSQL = [[NSString alloc] initWithFormat:@”insert or replace into

fields (row,field_data) values (%d,’%@’);”,i,[persistentArray objectAtIndex:i]];

char* errorMsg;
if(sqlite3_exec(database,[upDataSQL UTF8String],NULL,NULL,&errorMsg)

!= SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0,@”Failed to open database”);
}
}

//unarchive
NSString *query = @”select row, field_data from fields order by row”;//查找表中的数据
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil)

== SQLITE_OK)
{
while(sqlite3_step(statement) == SQLITE_ROW)
{
int row = sqlite3_column_int(statement, 0);
char *rowData = (char *)sqlite3_column_text(statement, 1);

NSString *fieldName = [[NSString alloc] initWithFormat:@”show%d”,row];
NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];

NSLog(@”fieldName is :%@,fieldValue is :%@”,fieldName,fieldValue);

[fieldName release];
[fieldValue release];
}
sqlite3_finalize(statement);
}

// Override point for customization after application launch
[window makeKeyAndVisible];
}

在上面的代码中,我们使用
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL findFile = [fileManager fileExistsAtPath:paths];
来判断数据库文件是否已经存在,其实在大多数情况下是没有必要的,sqlite3_open()方法会自动帮我们判断数据库文件是否存在,如果不存在则创建心的数据库文件。

四、其它方法

除了上面的三种方法来保存持久化数据以外,我们还可以用写文件到磁盘的方式来保存持久化数据。

–          (void)applicationDidFinishLaunching:(UIApplication *)application {

NSString *strOne = @”Persistent data1″;
NSString *strTwo = @”Persistent data 2″;

NSMutableArray *persistentArray = [[NSMutableArray alloc] init];
[persistentArray addObject:strOne];
[persistentArray addObject:strTwo];

NSArray *filePathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath =

–          [[filePathArray objectAtIndex:0] stringByAppendingPathComponent:@”mydatas.plist”];

[[NSArray arrayWithObjects:persistentArray,nil] writeToFile:filePath atomically:NO];

//load
NSMutableArray *saveDataArray = [[NSMutableArray alloc] init];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
saveDataArray = [NSMutableArray arrayWithContentsOfFile:filePath];
else
saveDataArray = [NSMutableArray arrayWithContentsOfFile:[[NSBundle

–                  mainBundle] pathForResource:@”Savedatas” ofType:@”plist”]];


NSArray *strArray = [saveDataArray objectAtIndex:0];

NSString *UnstrOne = [strArray objectAtIndex:0];
NSString *UnstrTwo = [strArray objectAtIndex:1];

// Override point for customization after application launch
[window makeKeyAndVisible];
}