在 Android 开发中,我们经常需要对对象进行序列化与反序列化操作,最常见的就是通过 Intent 传输数据时,Intent 只能传输基本数据类型、String 类型和可序列化与反序列化的对象类型,要想通过 Intent 传递对象类型,我们需要让该对象类型支持序列化和反序列化。
我们知道,Android 给我们提供了两种方式来完成序列化与反序列化过程:一种是 Serializable 方式,另一种是 Parcelable 方式;本篇文章将详细讲述使用 Serializable 方式实现序列化你所需要知道的一切。
你可能会疑问,使用 Serializable 实现序列化,不是只要让类实现 Serializable 接口就可以了吗,有什么好讲的?那你就 too naive 了少年!除了基础的直接实现 Serializable 接口之外,我们使用 Serializable 方式实现序列化的过程还有很多需要注意的细节,例如 serialVersionUID 是干什么的呢?如果我们想自定义实现序列化与反序列化过程该怎么办呢?本文将会详细介绍这些知识。
本文讲述的知识点如下:
想要序列化和反序列化一个对象,首先要让对象支持序列化与反序列化,使用 Serializable 方式实现序列化相当简单,只需要让类实现 Serializable 接口就可以:
1 | public class UserSerial implements Serializable { |
现在 UserSerial 类就支持序列化和反序列化了,那么我们应该怎么将 UserSerial 类的某个对象序列化到文件,然后再将其读取出来呢?当然是使用 ObjectOutputStream
和 ObjectInputStream
啦~
完整的序列化流程如下:
1 | public void serial(UserSerial user) { |
完整的反序列化流程如下,这里为了防止反序列化异常返回 null,默认我们返回了一个新构造的空 UserSerial 对象:
1 | public UserSerial deserial() { |
可能有人要吐槽我上面说的都是废话了,上面这些东西,稍微了解点 Java 的都知道啊~没事,且往下看,我们来说一些可能大家使用过程中没注意到的细节。
我们在使用 Serializable 方式实现序列化时,除了实现 Serializable 接口之外,一般还需要声明一个 serialVersionUID 静态字段,当然我们也可以选择不声明这个字段,那么我们在使用过程中,要不要指定这个字段呢?如果指定了,这个字段的值又是干什么用的呢?
其实 serialVersionUID 这个字段,是序列化和反序列化过程中,用来校验类是否发生了变动的依据,序列化的时候系统会把当前类的 serialVersionUID 字段写入序列化的文件中,当反序列化的时候,系统会去检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化时类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化时的类相比发生了某些变换,比如增删了某些成员变量等,这个时候是无法正常的反序列化的,并且会报 InvalidClassException
。
一般来讲,我们都应该手动指定 serialVersionUID 的值,可以随意指定一个数字,或者根据编辑器提示自动根据当前类的结构生成 hash 值,这样如果我们不手动修改,序列化和反序列化过程中 serialVersionUID 字段的值一直都会是一致的,可以最大限度的保证反序列化过程的成功,就算类结构发生了变动,我们也可以保证那些没有发生变动的成员变量被成功的反序列化。如果我们不手动指定 serialVersionUID 字段的值,那么如果反序列化时相比序列化时的类结构发生了变动,比如增删了成员变量等,那么系统就会重新计算当前类的 hash 值,并将其赋值给 serialVersionUID,导致序列化时的类和当前类的 serialVersionUID 值不一致,导致反序列化失败,从而 crash。
最后要说明的是,如果类结构发生了毁灭性的变化,如类名发生了改变,这个时候就算 serialVersionUID 值一致,也是不能被正常反序列化的,这一点后面还会提到。
现在我们知道如果一个类,实现了 Serializable 接口,那么在需要的时候,自动完成该类的序列化,与反序列化过程;那么如果我们想自定义序列化过程与反序列化过程该怎么办呢?例如我在版本 1 的时候,将 User 对象的 name 序列化了,但是反序列化的时候,我想把这个这个字段的值赋值给 nameNew,该怎么做到呢?
第一部分我们我们提到,使用 Serializable 方式实现序列化是,对象的序列化和反序列化过程是通过 ObjectOutputStream.writeObject
和 ObjectInputStream.readObject
实现的,这里以 ObjectOutputStream.writeObject
为例分析,追踪 writeObject
方法代码,发现其内部调用了 writeObject0 方法:
1 | public final void writeObject(Object obj) throws IOException { |
进入 writeObject0
方法:
1 | private void writeObject0(Object obj, boolean unshared) throws IOException { |
因为我们的对象是 Serializable,所以最终会走到 writeOrdinaryObject
方法:
1 | private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared) throws IOException { |
我们看到 writeOrdinaryObject
方法内部做了判断,看当前类是实现的 Externalizable
接口还是 Serializable
接口,因为我们是实现的 Serializable
接口,所以最终走到了 writeSerialData
方法:
1 | private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { |
最后我们发现,是通过反射调用的实现 Serializable 接口的类的 writeObject
方法,而参数类型是 ObjectOutputStream
类型。
而如果 hasWriteObjectMethod()
为 false,就使用 defaultWriteFields
完成序列化,也就是系统默认的序列化方法,想了解系统默认序列化方法的可以点进源码自己查看。
通过同样的步骤,我们可以发现 ObjectInputStream.readObject
方法内部,也判断了类是否有 readObject
方法,有就使用类自己的,没有就使用默认的。
所以如果我们在使用 Serializable 实现序列化与反序列化是,想实现自定义的序列化和反序列化过程,只需要给当前类添加 writeObject
和 readObject
方法即可,参考代码如下:
1 | /** |
认真的同学可能还会有一个疑问,那就是为啥你这自己写的 readObject
方法没有返回值呢?这个方法不应该返回一个当前类对象吗,像 ObjectInputStream.readObject
方法一样?这个问题的答案也可以通过查看源码得到解答,其实反序列化过程可以分为两步,一步是通过反射创建类对象的过程,一个是给创建的对象内的变量赋值的过程,而我们重写的方法只是完成了给当前创建的对象复制的过程,是赋给自身变量的,所以没有返回值,对象创建的过程在 ObjectInputStream.readObject
方法内,可通过源码验证,这也印证了我们上面所说,如果类名发生了变化,那么反序列化是不可能成功的,因为找不到类了。
经过以上分析,我们可以得出一下结论:
writeObject(ObjectOutputStream out)
和 readObject(ObjectInputStream in)
方法的方式自定义序列化和反序列化过程Externalizable
来打破这个限制,内部原理差不多ActivityThread#performLaunchActivity - oncreate、onstart、onrestoreinstancestate、onpostCreate(Called when activity start-up is complete (after {@link #onStart} and {@link #onRestoreInstanceState} have been called). Applications will generally not implement this method; it is intended for system classes to do final initialization after application code has run.)
Activity 启动过程中最重要的几个类:Instrumentation、ActivityThread、Activity
]]>最近看到阳春面的博客,感觉非常漂亮,正好最近也想自己搭个博客,记录一下自己的学习经历和生活感悟,给自己留下点回忆,同时整理一下自己的思路,于是就想仿照着弄一个类似的静态博客网站,正好最近有时间,而且内心搭博客的念头越来越汹涌,所以,说干就干!
拉到阳春面博客的最下方,发现他的静态博客是Hexo驱动,使用的NexT.Mist主题,而且我非常喜欢这个主题,所以我完全就是冲着阳春面的博客效果去的,所以这片文章主要介绍:
1. 基于Hexo和github pages搭建静态博客
2. Next主题配置及优化
最终目标是实现阳春面博客那样的效果!另外,因为我用的电脑是Mac Pro,所以这篇文章所说的方法都是在mac下才有用的,windows下基本步骤类似,可能只是依赖工具安装方法不同。最后,我还没有申请和绑定自己的域名,所以域名注册和绑定都没有介绍,我认为,在自己真正开始经营博客之前,没有必要花钱注册域名,所以这部分工作留在博客小有名气,有一定流量之后再做!
好,下面正式开始搭建博客!首先要想使用Hexo和github pages搭建博客,需要以下环境:
这些东西都不熟悉没关系,下面都会详细介绍。
因为我们的博客是基于github pages的,也就是博客内容都托管在github pages,所以需要有一个github账号,并且创建一个公开库(repo),用来存放你的博客。Github账号大家应该都有了,俗话说,没有Github 账号的程序员,不是好段子手,身为程序员,不加入这个全球最大的技术(搞基)社区怎么说的过去呢。如果还没有github账号,去这里申请一个就好了,申请步骤很简单,不再详述,记住选免费服务就可以。在github上创建public库是免费的,也就是传说中的创建开源库。创建private库是要付费的,我们使用github主要就是为了拥抱开源,如果没有特殊需求,创建public库就可以,我们一会儿要创建的博客仓库就是public库。
申请完账号,登陆之后,就可以创建repo了,点击New repository
,会跳转到这个界面:
需要注意的地方,我都在图上做了标注,这些标注里最需要注意的就是新创建的库的名字,必须是username.github.io
,等你创建库的时候,把username
换成你的用户名就可以,例如我的用户名是madongqiang2201,那库名就是madongqiang2201.github.io
。信息填写完毕,点击create repository
就可以把库创建出来。
要想进一步深入了解github,可以阅读这些资料:
Homebrew并不是必须的,你也可以通过其他途径安装git和node.js,但是,个人认为Homebrew相当nice,而且在mac下管理安装包特别方便,所以在这里强行安利一波,Homebrew需要你的mac安装了Xcode,很多其他mac应用也需要,所以建议先安装一下,appstore里就有。装完Xcode之后,剩下的步骤就特别简单了,打开mac terminal终端,输入以下命令
1 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
就可以自动完成该工具的安装,如果这条命令失效了,可能是Homebrew更新了安装方式,可以去这里查看最新安装命令以及Homebrew的简要介绍。Homebrew安装完成之后,安装git和node.js简直简单的不要不要的。
Homebrew一些常用命令可以阅读以下资料,深度应用请自行检索:
Git是一个强大的版本管理工具,Github的版本管理就是基于Git的,现在svn基本过时了,使用Git进行版本管理才是王道。安装完Homebrew之后,安装Git几乎零成本,还是在terminal终端,输入以下命令:
1 | brew install git |
然后等一段时间就ok了,Homebrew会自动去帮你完成下载安装。
Git使用教程,可以参考以下资料:
Hexo是基于node.js的,所以要让Hexo运行,node.js环境是必不可少的。使用Homebrew安装node.js也特别简单,在terminal终端输入如下命令:
1 | brew install node |
然后等着Homebrew帮你完成下载安装就可以了,舒爽到爆炸!安装完node.js就可以使用常见的npm命令了。
这一步可以忽略,配置SSH key与否,并不影响博客的搭建和使用,只是配置了之后,更新博客方便一点,不用每次都输用户名和密码。
打卡终端,输入如下命令:
1 | cd .ssh |
检查终端输出的文件列表中是否已经存在id_rsa.pub 或 id_dsa.pub 文件,如果文件已经存在,那么你可以跳过步骤2,直接进入步骤3.
在终端输入以下命令:
1 | $ ssh-keygen -t rsa -C "your_email@example.com" |
回车,接着会提示你,让你输入文件名,直接回车会创建使用默认文件名的文件(推荐使用默认文件名);然后会提示你输入两次密码(输入密码之后没有反馈,显示还是空白,但是你确实已经输入了),当然密码也可以不输,直接回车,如果这里没有输入密码,以后提交博客更新的时候就不需要输入密码了。
经过第二步,如果你没有指定文件名(也就是使用的默认文件名),那么你的.ssh文件夹下,应该有一个id_rsa.pub文件了,打开该文件,复制里面的文本。然后登陆github,点击右上角头像右边的三角图标,点击Settings,然后在左边菜单栏点击SSH and GPG keys,点击new ssh key,title 随便填一个,在key 栏填入你复制的内容,点击add ssh key,就可以添加一个ssh key了,如下图:
在终端输入以下命令:
1 | ssh -T git@github.com |
如果你创建的key没有设密码的话,直接一顿回车,到最后提示你1
Hi yourusername! You've successfully authenticated, but GitHub does not provide shell access.
说明你的ssh key添加成功了。如果过程中提示你perimission deny相关错误,就在命令前加上sudo
然后执行命令的时候输入你的appleid密码应该就可以了。sudo
用来说明用管理员权限运行。
以上简略介绍了怎么配置SHH key,如果想知道命令里面参数的含义,或者配置过程不顺利的话,可以看这里的详细教程。
经过以上步骤的铺垫,终于到了Hexo安装了,前面我们安装了node.js,装完node之后,我们就可以使用npm命令了,而Hexo安装就是使用npm,在终端输入以下命令:
1 | npm install -g hexo |
然后等待一会儿,hexo会自动完成下载安装。Hexo安装完成之后,在你喜欢的位置随意创建一个文件夹,这个文件夹以后就是你存放本地博客的地方了,通过cd filepath
(filepath替换成你创建的文件夹目录)命令,进入到你创建的文件夹目录,然后执行以下命令:
1 | hexo init |
这样Hexo会在该文件夹创建本地博客所需的一切资源。这样本地博客就搭建好了,输入以下命令:
1 | hexo g // 全拼是:hexo generate,可以简写成 hexo g |
这样就开启了一个本地博客服务器,打开浏览器,在地址栏输入localhost:4000
,就可以查看本地博客了,hexo默认生成了一片hello world博客。注意,以上hexo开头的命令,执行目录必须是你创建的博客文件夹目录。
现在你已经可以在本机查看你的博客了,但是要想让别人通过网络可以查看你的博客,还需要一步,那就是将你的博客发布到github仓库。在terminal终端,将当前目录切换到你的本地博客目录,执行以下命令:
1 | npm install hexo-deployer-git --save |
安装完成之后,打开本地博客目录的_config.yml
文件,编辑其中的deploy节点:
1 | deploy: |
将上面yourusername
替换成你的github用户名即可,你也可以去你开始的时候创建的名为yourusername.github.io
的仓库去直接复制完整的地址,如下图:
当前复制出来的值,就是通过SSH方式clone的地址,配置完成后,以后提交博客更新不用输用户名和密码(如果你ssh key没有设置密码的话);点击上图右上角Use HTTPS
,复制出来的clone地址也可以配置到repo,但是这样,提交更新的时候,就需要输入用户名和密码了。
保存配置之后,在本地博客目录执行以下命令:
1 | hexo clean // clean本地项目,防止缓存 |
然后,在浏览器地址栏输入yourusername.github.io
就可以访问你的博客了,别人也可以通过这个地址访问你的博客。
如果想了解Hexo常用命令可以点击这里,Hexo常用命令没几个,常用的有创建新博客、clean、生成静态文件、发布等,上述官方文档有详细介绍,这里记录几个我经常用到的:
1 | hexo init //在指定目录执行该命令,会将当前目录初始化为hexo站点,生成hexo站点所需的一切文件 |
NexT主题是一套简约的主题,设置完成之后,就像我的博客现在的样子,NexT主题的详细配置请看这里,这个是NexT主题作者维护的配置文档,作者是国人,所以文档是中文的,而且写的非常详细,对照文档,所有功能都能轻松实现。等你配置完成后,你的博客将拥有评论系统,访问次数统计,站内搜索,代码高亮,百度统计,社交分享(分享到微博,微信,qq等)等等强大的功能。官方文档很详细,我就不再赘述了。
1.如何删除一篇博文
当然,我们辛辛苦苦写了博文,一般是不会删除的,最多修改一下,但是我们搭建的过程中或者刚搭建好个人博客站点,可能一激动就发了好多测试博文,如果想删除这类文章,在Finder中,找到本地博客所在目录,找到/source/_posts
文件夹,里面放了所有我们写的博客,想删除哪篇,直接在这里删除,然后再重新发布到github,这篇博文就不见了
2.fork me on github
如果你访问我的个人博客,你会发现右上角有一个倾斜的fork me on github
图标,想要集成这个图标,只需要去这里挑选你喜欢的样式,把样式代码复制过来,然后打开你本地博客目录下的themes/next/layout/layout.swig
文件,然后把你复制过来的样式代码粘贴到如下位置:
1 | <body itemscope itemtype="http://schema.org/WebPage" |
其中哪个超链接就是复制的样式代码,粘贴位置在body内header
标签之上。
3.给博文添加tag和分类
使用如下命令:
1 | hexo new "blog title" |
创建的新博文文件,打开之后顶部会有一段自动生成的文本,在其中加入tag和category标签即可指定tag和分类。
1 | --- |
4.手动实现某条博文置顶
Next主题没有博文置顶相关设置(或许这个功能Hexo应该提供,但我分不清),但是我发现发布的博文是根据发布日期倒序排序的,即:越早发布的,排的越靠后!而发布日期,我们可以通过博文的头部date
字段指定:
1 | --- |
所以我们可以给想要置顶的博文,指定一个将来的日期,这样就可以让这篇博文一直排在别的博文前面,达到手工置顶的目的!但是要注意:对于已经发布的博文,发布日期和文件名称(不是title字段的值,而是md文件名)是该文章访问url的组成部分,也就意味着,已经发布的文章如果改了发布日期,针对这篇文章的链接就都不能用了,而且浏览统计等信息都会受到影响,所以手工置顶应该慎重使用!
Hexo博客至此搭建完毕,并且应用了简约美丽的NexT.Mist主题,但是仍有一些不足之处,例如没有通过正常渠道博文置顶功能;该主题的主页过于简单,没有边栏,导致主页展示的有效信息减少,不利于访客浏览,也不利于推广自己的其它渠道,这可能就是简约付出的代价,主题作者以后可能也会改进吧!如果不喜欢这个主题,可以参看一下开源实验室或者stormzhang的个人博客主题,我感觉这两个都不错。但是最重要的还是博客要有内容,没有内容,再好的个人站点也没有意义!所以样式先这样吧,这段时间先写技术文章,如果以后自己开始认真经营博客了,再绑定域名和优化样式。最后,如果大家有什么问题,欢迎留言讨论!