如何对"读取文件"html点击按钮下载文件进行添加响应函数onread

他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)上一篇中Star类的强大之处大家都看到了,今天我们继续来通过这个项目展示一下面向对象的另一个强大之处——继承。
代码复用准备
一提到代码复用,我又要提之前总说的“高内聚,低耦合”了。这个原则要求我们尽量让每个函数只实现最小颗粒度的功能。
我们看看上一篇中的Star类,Move()函数貌似调用频率非常高。虽然代码量不多,但仔细想想,它可以分成三个功能:
擦除之前的星星
计算新位置
画出新星星
按照这个功能划分,我们将Star类的代码修改如下:
classStar{ public:
~Star(){} voidInit(); voidMove(); protected: voidDraw(); voidNewPos(); voidRemove(); doublem_x = 0; intm_y; doublem_ intm_
}; voidStar::Init()
{ if(m_x == 0)
m_x = rand() % SCREEN_WIDTH;
m_y = rand() % SCREEN_HEIGHT;
m_step = (rand() % 5000) / 1000.0+ 1;
m_color = (int)(m_step * 255/ 6.0+ 0.5); m_color = RGB(m_color, m_color, m_color);
} voidStar::Move()
} voidStar::Draw()
putpixel((int)m_x, m_y, m_color);
} voidStar::NewPos()
m_x += m_ if(m_x & SCREEN_WIDTH) this-&Init();
} voidStar::Remove()
putpixel((int)m_x, m_y, 0);
新加入了三个protected函数,Draw、Remove和NewPos分别负责将“自己”画在屏幕上、从屏幕上删除和计算出新位置。
这三个函数代码都不多,独立成一个函数是不是有些多余呢?新加函数后代码总行数变得更多了。这个问题大家先自己思考一下,我们马上就能看到它的好处了。
不一样的星星
假如你是一名程序员,完成上面的代码之后突然接到这样的需求变更:“现在的星星有些小,需要改大一点。”这时,你该怎么改呢?
代码修改有个原则,在越封闭的区域内修改代码越安全。如果在上一篇,我们需要在Move()函数中修改一些代码才能实现这个功能,而现在我们只要修改Draw()和Remove()两个函数就好了。修改如下:
void Star::Draw{
putpixelm_x, m_y, m_color);
void Star::Remove{
putpixelm_x, m_y, 0);
最重要的是,即使你修改时写错了代码,也不会影响Move()函数。这两个函数让画图和计算位置两部分代码彻底隔离开了。
如果你觉得这样很神奇,那么告诉你,这才刚刚是个开始。
星星的继承
接下来,需求又变了。需要在程序中加入另外一种矩形的星星。哪有什么矩形的星星呢?告诉你,程序员经常接到这种毫无道理的需求变更。还是想想如何实现吧。
首先,现有的代码都是有用的,要保留。同时,需要新加入矩形星星的类。是不是有人觉得是这样呢?
classRectStar{ public:
~Star(){} voidInit(); voidMove(); protected: voidDraw(); voidNewPos(); voidRemove(); doublem_x = 0; intm_y; doublem_ intm_
再写一个RectStar类肯定是没问题的,但我们发现,这个类中的大部分代码和Star类完全一样。在后面实现的时候,Init()和Move()两个函数也不用修改,这样完全相同的两份代码不仅浪费,而且造成后期维护负担。
正确的方法其实是这样的,让RectStar类从Star类中继承。代码如下:
classRectStar : publicStar
RectStar(){}
~RectStar(){} voidMove(){
} protected: voidDraw(); voidRemove();
}; voidRectStar::Draw()
setfillcolor(m_color);
fillrectangle(m_x, m_y, m_x + 3, m_y + 3);
} voidRectStar::Remove()
clearrectangle(m_x, m_y, m_x + 4, m_y + 3);
这样,新的矩形星星就完成了。
我们再把main函数做些修改如下:
{ ((unsigned)time(NULL)); (SCREEN_WIDTH, SCREEN_HEIGHT); [MAXSTAR]; [MAXSTAR]; (int i = 0; i & MAXSTAR; i++)
{ [i].Init(); [i].Init();
} (!kbhit())
{ (int i = 0; i & MAXSTAR; i++)
{ [i].Move(); [i].Move();
好了,现在我们的程序中就会多出一些奇怪的矩形星星。哎呀,矩形的星星真的好难看。
不过,用继承来实现这个功能真的很炫,不是吗?
混迹博客园良久,想想还是应该多抽空写几篇小博客来回报下这个平台。
Android是一个运行在移动终端上的操作系统,跟传统PC最大的不同所在就是移动终端的资源紧缺问题“比较”明显,当然对于一些屌丝机型,应该用“非常“来形容才靠谱。所以经常会出现在一些比较缺乏青春活力的老型机上,运行一些软件被异常终止的情况;然而作为互联网厂家来说,广大的屌丝机用户肯定是一大笔用户资源,这是能放弃的市场吗?!当然不行o(╯□╰)o,所以我们要尽可能得提高软件的效率来赢取客户的回眸一笑了,屌丝也是客户!
这篇博客主要介绍如何在UI设计上提高效率,减少资源的利用,毕竟在终端资源短缺的今天,效率始终为王。我们评判一个UI界面不是认为有多复杂才给力,或者说有多炫才靠谱,一个简约而又不平凡的高效UI界面才是一个灰常牛逼的界面设计。
在android应用中,采用硬编码方式编写界面并不是一个提倡的方法。当然硬编码编写的界面比基于XML文件的软编码界面高效灵活一些,但是非常不容易维护,错综复杂的代码更会让程序埋雷重重,说不定哪天就把应用炸的惨不忍睹。所以如果非常必要非常肯定要采用代码编写硬编码界面之外,其他情况还是采用易于维护的XML来编写比较好。
所以文中对于UI优化设计归结到底也就是对XML布局文件的优化设计。
在谷歌给我们的开发环境中,存在这么一个非常好用的工具——hierarchyviewer,估计很多人都没搭理过这个藏在偏僻角落的小工具吧;它能非常容易的帮我们分析UI界面的结构和构造效率,这个工具的位置就在sdk/tools/文件夹。
楼下上图:
大家好,我是图~
这是分析的是一个布局上只有一个TextView组件的XML界面,图告诉我们,构造这个界面总共用了四个组件,也就是需要绘制四次组件,自然每一次绘制组件都需要耗费资源。
下面步入狂拽酷炫吊炸天的主题部分。。。。
尽量用最少的步骤完成布局
我是社会好青年,我为国家省资源;当然作为组件来说也需要这个觉悟,每个组件的绘制都会多多少少耗费终端的资源。所以我们在这里可不能听老祖宗的话:韩信点兵多多益善了,精兵简政才是UI设计的唯一出路。不相信?行!下面就开始给个对比的例子。
假设项目需要搞这么一个按钮:
这不简单吗?几行代码不是分分钟的事情吗?
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
&lt;RelativeLayout
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:gravity=&quot;center&quot; &gt;
&lt;Button
android:id=&quot;@+id/button1&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:background=&quot;@drawable/btn_backgroup&quot;
&lt;ImageView
android:id=&quot;@+id/imageView1&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:layout_alignParentLeft=&quot;true&quot;
android:layout_centerVertical=&quot;true&quot;
android:src=&quot;@drawable/header_back&quot; /&gt;
&lt;/RelativeLayout&gt;
也别急着看代码,多累多伤眼睛呀,直接上个hierarchyviewer里面的图来瞧瞧呗
一个小小的按钮就用了3个组件来绘制,这就是3N的复杂度了呀,如果有5个这样的按钮就要15个组件,如果有10个按钮就要有30个,如果有N++个,哎呀妈的,不敢想象下去了。既然这样,我们是不是应该考虑一下优化优化,翻翻资料我们发现原来是可以不用这么多组件来实现的这个按钮的。
&lt;Button
android:id=&quot;@+id/button1&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:background=&quot;@drawable/btn_backgroup&quot;
android:drawableLeft=&quot;@drawable/header_back&quot;
android:gravity=&quot;center&quot;
android:padding=&quot;10dp&quot;
按照国际惯例,二楼上图
还是原来的按钮,还是原来的味道,复杂度从3N降低到N!!!你敢说这样的效率你不想去提升????
小结一个:在我们设计UI布局时,应该从使用尽量少的组件的前提下入手,由于系统组件的封装比较完善,把多个简单的组件交由一个复杂一点的组件来实现,是可以得到比较好的效率的。因为每个组件都得需要独自进行绘制过程,多个组件绘制浪费的资源不仅仅谋害了我们的应用,更深深打击了用不起高端机的屌丝用户的自尊心——”他妈的,这软件又不能用!“。
你不干活?把你辞了。
我们还记刚开始给的一个图吗?我们在布局中使用的到仅仅是一个TextView,而RelativeLayout貌似啥子活儿都没干的样子。。。。。。
我们从来都不提倡吃空饷不干活,软件界的潜规则也是这样的。出于构建和谐社会的正义感,我们当然不能坐视RelativeLayout这种站着茅坑不拉屎的流氓行为,所以我们就需要借助一个解决措施——&merge&标签,它能帮我们干掉一些不需要的根节点。为了拥有更好的即视感,所以我用了一个更为复杂点的布局(其实一点都不复杂)、、
主布局XML文件:
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
&lt;FrameLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&
android:id=&quot;@+id/layout1&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;match_parent&quot;
&lt;ImageView android:id=&quot;@+id/image1&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;wrap_content&quot;
android:src=&quot;@drawable/bg&quot;
&lt;com.net168.text.MyLayout
android:id=&quot;@+id/layout2&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;match_parent&quot;
&lt;/com.net168.text.MyLayout&gt;
&lt;/FrameLayout&gt;
组合控件布局XML文件:
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:orientation=&quot;horizontal&quot;
&lt;Button android:id=&quot;@+id/button2&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:text=&quot;button2&quot;
&lt;TextView android:id=&quot;@+id/text1&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:text=&quot;text1&quot;
android:textColor=&quot;#ff0000&
&lt;/LinearLayout&gt;
这个界面很丑的,不忍直视:
丑归丑,我们还是需要继续用神器hierarchyviewer看看这个XML生成的界面结构图来探索一下丑女内心丰富多彩的世界~~~~~~~
我靠。。。。三个组件的布局竟然用了六层嵌套布局,瞬间有了一种大花姑娘嫁给老光棍的一种深深的浪费感。我们开始看图说话,第一层和第二层的组件是系统都会自动生成的,这个是板上钉钉没法商量的事情,除非你去底层跟他们好好谈谈。但是~但是这个第三层的FrameLayout和第五层的LinearLayout完完全全是在自我秀存在感而已,所以我们要狠下心做掉他们,怎么来呢?用&merge&标签。
由于&merge&标签只能作为根元素,所以我们可以将这两个根元素都稍加修改,如下:
主布局XML文件:
&lt;merge xmlns:android=&quot;http://schemas.android.com/apk/res/android&
android:id=&quot;@+id/layout1&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;match_parent&quot;
&lt;ImageView android:id=&quot;@+id/image1&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;wrap_content&quot;
android:src=&quot;@drawable/bg&quot;
&lt;com.net168.text.MyLayout
android:id=&quot;@+id/layout2&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;match_parent&quot;
&lt;/com.net168.text.MyLayout&gt;
&lt;/merge&gt;
组合控件布局XML文件:
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
<div class="crayon-num" data-line="crayon-5aaafc
<div class="crayon-num crayon-striped-num" data-line="crayon-5aaafc
&lt;merge xmlns:android=&quot;http://schemas.android.com/apk/res/android&
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
&lt;Button android:id=&quot;@+id/button2&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:text=&quot;button2&quot;
&lt;TextView android:id=&quot;@+id/text1&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:text=&quot;text1&quot;
android:textColor=&quot;#ff0000&
&lt;/merge&gt;
PS:注意需要在组合控件的类中加上一句setOrientation(LinearLayout.HORIZONTAL)来保证自组件的水平排列。
继续用神器看看结构:
呼呼呼~~是不是从六层降低到了四层结构,好一股小清新的感觉呀,我都感觉飘飘然了,自然效率的提升是毋容置疑滴。。。。。
小结一个:&merge&标签能百分百代替&FrameLayout&这个布局组件,对于不复杂的其他布局组件如线性布局等组合组件中,可以在继承子类中对其属性进行设置后也可以使用&merge&标签,&merge&标签不占资源,自然在生成界面时也不会生成对应的组件。另外需要注意一点是&merge&只能作为根元素,对于需要用inflate生成布局文件时,必须指定一个ViewGroup作为其父元素,并且要设置inflate的attachToRoot参数为true。(参照inflate(int, ViewGroup, boolean))。
Java四种引用级别由高到低依次为:强引用、软引用、弱引用和虚引用。
1、强引用:只要引用存在,垃圾回收器永远不会回收
Object obj = new Object();//可直接通过obj取得对应的对象 如obj.equels(new Object());而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。
2、软引用:非必须引用,内存溢出之前进行回收
可以通过以下代码实现
Object obj = new Object();
SoftReference&Object& sf = new SoftReference&Object&(obj);
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
3、弱引用:第二次垃圾回收时回收,可以通过如下代码实现
Object obj = new Object();
WeakReference&Object& wf = new WeakReference&Object&(obj);
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。
4、虚引用:垃圾回收时回收,无法通过引用取到对象值
可以通过如下代码实现
Object obj = new Object();
PhantomReference&Object& pf = new PhantomReference&Object&(obj);
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
使用软引用构建敏感数据的缓存
1 为什么需要使用软引用
首先,我们看一个雇员信息查询系统的实例。我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。作为一个用户,我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退”按钮)。这时我们通常会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另一种是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。我们知道,访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。
2 如果使用软引用
SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
看下面代码:
MyObject aRef = new
MyObject();
SoftReference aSoftRef=new SoftReference(aRef);
此时,对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,所以这个MyObject对象是强可及对象。
随即,我们可以结束aReference对这个MyObject实例的强引用:
此后,这个MyObject对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们可以通过:
MyObject anotherRef=(MyObject)aSoftRef.get();
重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。
3 使用ReferenceQueue清除失去了软引用对象的SoftReference
作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:
ReferenceQueue queue = new
ReferenceQueue();
SoftReference
SoftReference(aMyObject, queue);
那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。常用的方式为:
SoftReference ref =
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
非原创,来源:
Java设计模式——装饰者模式
我们来看一个图片
图片上面很清楚的显示了我们装饰者模式中的一些结构,那么我就来给大家分析一下:
1、Component是抽象构建,什么意思呢,它是一个借口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。
但是我们要注意
在装饰模式中,必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件。
2、ConcreteComponent 具体构件也就是说ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是它。
3、Decorator装饰角色 一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的属性里必然有一个private变量指向Component抽象构件。
4、具体装饰角色 什么意思呢,就是我们可以在这些类中具体的写入方法。ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰成其他东西。
定义差不多就讲到这里了,那么接下来我们就开始讲例子
我们考试的时候我一般都是靠40多名的,而全班也只有40多名,那我们考试考完后,还要让家长签字。这可愁坏我了,不过还好机智的我想出了个办法,就是先说我们的最高成绩,也没比我高多少,然后在老爸看完成绩单后,告诉他我在全班排第38名,这个也是实情,为啥呢?有将近十个同学退学了!这个情况我是不会说的。不知道是不是当时第一次发成绩单时学校没有考虑清楚,没有写上总共有多少同学,排第几名,反正是被我钻了个空子。
我们来看看这个图
图上面写了一个成绩单的抽象类,我们可以按照前面一张图的分析来看,
1、首先我们定义了一个抽象类SchoolReport.class
public abstract class SchoolReprt {
//成绩单主要展示的就是你的成绩情况
public abstract void report();
//成绩单要家长签字,这是最要命的
public abstract void sign(String name);
2、然后我们定义了一个四年级的成绩单FouthGradeSchoolReport 集成SchoolReport类,这个类是具体的实现类
public class FouthGradeSchoolReport extends SchoolReprt {
public void report() {
// TODO Auto-generated method stub
System.out.println("尊敬的XXX家长:");
System.out.println("........");
System.out.println("语文: 62
自然:63");
System.out.println("。。。。。。。。");
System.out.println("家长签字:");
//家长签名
public void sign(String name) {
// TODO Auto-generated method stub
System.out.println("家长签名:" +name );
3、我们可以写Decorator类来继承SchoolReport 类,这些类就是用来修饰SchoolReport类的方法
public abstract class Decorator extends SchoolReprt {
//首先我要知道是哪个成绩单
private SchoolR
//构造函数,传递成绩单过来
public Decorator(SchoolReprt sr){
//成绩单还是要被看到的
public void report() {
// TODO Auto-generated method stub
this.sr.report();
//看完还是要签名的
public void sign(String name) {
// TODO Auto-generated method stub
this.sr.sign(name);
4、我们用多个类来修饰具体的角色
public class HighSCoreDecorator extends Decorator{
//构造函数
public HighSCoreDecorator(SchoolReprt sr) {
super(sr);
// TODO Auto-generated constructor stub
//我要汇报最高成绩
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
//我要在老爸看成绩单前告诉他最高成绩,否则等他一看,就抡起扫帚揍我,我哪里还有机会说啊
public void report() {
// TODO Auto-generated method stub
this.reportHighScore();
super.report();
public class SortDecorator extends Decorator{
//构造函数
public SortDecorator(SchoolReprt sr) {
super(sr);
// TODO Auto-generated constructor stub
//告诉老爸学校的排名情况
private void reportSort(){
System.out.println("我的排名是38名");
//老爸看完成绩单后再告诉他,加强作用
public void report() {
// TODO Auto-generated method stub
super.report();
this.reportSort();
最后,我们开始实现我们的Father ,也就是主方法
public class Father {
public static void main(String[] args) {
//把成绩单拿过来
//原装的成绩单
sr = new FouthGradeSchoolReport();
//加了最高分说明的成绩单
sr = new HighSCoreDecorator(sr);
//又加了成绩排名的说明
sr = new SortDecorator(sr);
//看成绩单
sr.report();
//然后老爸一看,很开心,就签名了
sr.sign("你爹");
运行结果就是:
这次考试语文最高是75,数学是78,自然是80
尊敬的XXX家长:
。。。。。。。。
家长签字:
我的排名是38名
家长签名:你爹
那我们来分析一下,这个装饰类的用法
装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知
道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具
体的构件。
装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返
回的对象还是Component,实现的还是is-a的关系。
装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。
对于装饰模式记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想看,就像剥洋葱一样,你剥到了最后才发现是最里层的装饰出现了问题,想象一下工作量吧,因此,尽量减少装饰类的数量,以便降低系统的复杂度。
使用的场景
需要扩展一个类的功能,或给一个类增加附加功能。
需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
作者: 慕侠1074980 链接:https://www.imooc.com/article/23976来源:慕课网本文原创发布于慕课网 ,转载请注明出处,谢谢合作
C/C++混合代码实例
__cplusplus
__cplusplus
注意 :__cplusplus是c++编译器内置的宏,此处代码的意思为,即是在c++中调用c的代码。(因为c++中存在改名的问题,所以需要包含extern “C”)
通过几个例子说明C++指针参数传递内存的问题
void GetMemory(char* p, int num)
p = (char*)malloc( sizeof(char) * num );
int main(void)
char* str = NULL;
GetMemory(str, 30);
std::cout && str && std:: // str 仍为NULL
分析: 如果函数参数是一个指针,不要指望用该指针去申请动态内存。例子一中,main函数调用 GetMemoty(str,30) , 并没有使str获得期望的内存,str依旧为NULL。毛病出在GetMemory函数中, 编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使_p = p。如果函数内的程序修改了_p的内容,就导致参数p的内容作了相应的修改。这就是指针可以用作输出参数的原因。在例一中,_p申请了新的内存,只是把_p所指内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄漏一块内存,因为没有free释放内存。如果非要用指针参数去申请内存,那么应该改用“指向指针的指针”,具体见例子二。
void GetMemory_2(char** p, int num)
*p = (char*)malloc( sizeof(char) * num );
int main(void)
char* str = NULL;
GetMemory(&str, 30);
strcpy(str, &#8220;hello world&#8221;);
std::cout && str && std::
free(str);
分析: GetMemory_2 函数参数 *p的临时副本是 _*p, 在函数中,申请了一块新的内存,相当于改变了指针_*p的内容,从而指针*p的内容也改变了,所以内存申请成功了。
C++实践:正则表达式解析声卡参数
在中的正则表达式使用的https://www.txt2re.com/直接生成的,而在C++中txt2re并没有使用C++11中自带的正则库,而是使用的别的库,这么一来,如果想使用C++11中自带的正则库就用不了txt2re了。凭着感觉硬吞下来了。C++11来解析声卡参数。[txt2re还是很好用的][1]。
//============================================================================
// Name : CppPro.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
* 在构造函数中
class Card {
int getId() const {
class Device {
string trim(string& str)
size_t first = str.find_first_not_of(&#8216; &#8216;);
if (first == string::npos)
return &#8220;&#8221;;
size_t last = str.find_last_not_of(&#8216; &#8216;);
return str.substr(first, (last-first+1));
* 启动一个Loop接收客户端命令
int main() {
// 存放string结果的容器
std::string txt = &#8221; 0 [PCH ]: HDA-Intel &#8211; HDA Intel PCH&#8221;;
std::string re1 = &#8220;( )&#8221;; // Any Single Character 1
std::string re2 = &#8220;(\\d+)&#8221;; // Integer Number 1
std::string re3 = &#8220;( )&#8221;; // Any Single Character 2
std::string re4 = &#8220;(\\[.*?\\])&#8221;; // Square Braces 1
std::string re5 = &#8220;(:)&#8221;; // Any Single Character 3
std::string re6 = &#8220;( )&#8221;; // Any Single Character 4
if (regex_search(txt, sm, regex(re1 + re2 + re3 + re4 + re5 + re6))) {
id = sm[2];
name = sm[4];
name = name.substr(1, name.length() &#8211; 2);
name = trim(name);
cout&& &#8220;name: &#8221; && name && &#8221; id: &#8221; && id &&
运行结果:
name: PCH id: 0
数组指针(也称行指针)
定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
//将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
//该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。
定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]&#8230;p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i&3;i++)
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。
这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
优先级:()&[]&*
=========================================================================
一、指针数组和数组指针的内存布局
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
int *p1[10];
int (*p2)[10];
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:
二、int (*)[10] p2&#8212;&#8211;也许应该这么定义数组指针
这里有个有意思的话题值得探讨一下:平时我们定义指针不都是在数据类型后面加上指针变量名么?这个指针p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义p2:
int (*)[10] p2;
int (*)[10]是指针类型,p2 是指针变量。这样看起来的确不错,不过就是样子有些别扭。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量p2 前移了而已。你私下完全可以这么理解这点。虽然编译器不这么想。^_^
三、再论a 和&a 之间的区别
既然这样,那问题就来了。前面我们讲过a 和&a 之间的区别,现在再来看看下面的代码:
int main()
char a[5]={&#8216;A&#8217;,&#8217;B&#8217;,&#8217;C&#8217;,&#8217;D&#8217;};
char (*p3)[5] = &a;
char (*p4)[5] =
上面对p3 和p4 的使用,哪个正确呢?p3+1 的值会是什么?p4+1 的值又会是什么?毫无疑问,p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:
warning C4047: &#8216;initializing&#8217; : &#8216;char (*)[5]&#8217; differs in levels of indirection from &#8216;char *&#8217;。
还好,这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过我仍然警告你别这么用。
既然现在清楚了p3 和p4 都是指向整个数组的,那p3+1 和p4+1 的值就很好理解了。
但是如果修改一下代码,把数组大小改小点,会有什么问题?p3+1 和p4+1 的值又是多少呢?
int main()
char a[5]={&#8216;A&#8217;,&#8217;B&#8217;,&#8217;C&#8217;,&#8217;D&#8217;};
char (*p3)[3] = &a;
char (*p4)[3] =
甚至还可以把代码再修改,把数组大小改大点:
int main()
char a[5]={&#8216;A&#8217;,&#8217;B&#8217;,&#8217;C&#8217;,&#8217;D&#8217;};
char (*p3)[10] = &a;
char (*p4)[10] =
这个时候又会有什么样的问题?p3+1 和p4+1 的值又是多少?
上述几个问题,希望读者能仔细考虑考虑,并且上机测试看看结果。
(1).char (*p2)[5]=a;必须使用强制转换,如:char (*p2)[5]=(char (*)[5])a;
(2).把数组大小改变,都会编译不通过,提示:
error C2440: &#8216;initializing&#8217; : cannot convert from &#8216;char (*)[5]&#8217; to &#8216;char (*)[3]&#8217;
error C2440: &#8216;initializing&#8217; : cannot convert from &#8216;char (*)[5]&#8217; to &#8216;char (*)[10]&#8217;
(3).把以上程序测试代码如下:
int main()
char a[5]={&#8216;a&#8217;,&#8217;b&#8217;,&#8217;c&#8217;,&#8217;d&#8217;};
char (*p1)[5]= &a;
char (*p2)[5]=(char (*)[5])a;
printf(&#8220;a=%d\n&#8221;,a);
printf(&#8220;a=%c\n&#8221;,a[0]);
printf(&#8220;p1=%c\n&#8221;,**p1);
printf(&#8220;p2=%c\n&#8221;,**p2);
printf(&#=%c\n&#8221;,**(p1+1));
printf(&#=%c\n&#8221;,**(p2+1));
Press any key to continue
根据指针类型及所指对象,表示指针大小,每次加1,表示增加指针类型大小的字节.&#8212;-后面还会有解释说明.
四、地址的强制转换
先看下面这个例子:
struct Test
char cha[2];
short sBa[4];
假设p 的值为0x100000。如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?
我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?
还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0x1 的值为:0x100014。
(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。
(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。
上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);//指向a数组后面的内存单元,&a+1表示向后移16个存储单元
int *ptr2=(int *)((int)a+1);//表示a的存储单元的地址增加一个字节
printf(&#8220;%x,%x&#8221;,ptr1[-1],*ptr2);//ptr1[-1]其实指向的是a数组的最后一个单元,*ptr1则表示a数组的地址后移一个字节之后的4个连续存储单元所存储的值
这是我讲课时一个学生问我的题,他在网上看到的,据说难倒了n 个人。我看题之后告诉他,这些人肯定不懂汇编,一个懂汇编的人,这种题实在是小case。下面就来分析分析这个问题:
根据上面的讲解,&a+1 与a+1 的区别已经清楚。
ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。
其内存布局如下图:
好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。
int checkSystem()
union check
return (c.ch ==1);//如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。
Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient。
尽管Google在大部分安卓版本中推荐使用HttpURLConnection,但是这个类相比HttpClient实在是太难用,太弱爆了。
OkHttp是一个相对成熟的解决方案,据说Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp实现了。所以我们更有理由相信OkHttp的强大。
OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果你用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。
注:在国内使用OkHttp会因为这个问题导致部分酷派手机用户无法联网,所以对于大众app来说,需要等待这个bug修复后再使用。或者尝试使用OkHttp的老版本。
截止到目前,OkHttp一直没有修复,并把修复计划延迟到了OkHttp2.3中。不是所有设备都能重现,仅少量设备会出现这个问题。(如果问题这么明显,OkHttp早就修复了)
OkHttp支持Android 2.3及其以上版本。
对于Java, JDK1.7以上。
官方介绍页面有链接位置。这里把下载链接也写在下面。
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
throw new IOException(&#8220;Unexpected code &#8220; + response);
Request是OkHttp中访问的请求,Builder是辅助类。Response即OkHttp中的响应。
Response类:
public boolean isSuccessful()
Returns true if the code is in [200..300),
which means the request was successfully received, understood, and accepted.
response.body()返回ResponseBody类
可以方便的获取string
public final String string() throws IOException
Returns the response as a string decoded with the charset of the Content&#8211;Type header. If that header is either absent or lacks a charset,
this will attempt to decode the response body as UTF&#8211;8.Throws:
IOException
当然也能获取到流的形式:
public final InputStream byteStream()
POST提交Json数据
public static final MediaType JSON = MediaType.parse(&#8220;application/ charset=utf-8&#8221;);
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.post(body)
Response response = client.newCall(request).execute();
f (response.isSuccessful()) {
return response.body().string();
throw new IOException(&#8220;Unexpected code &#8220; + response);
使用Request的post方法来提交请求体RequestBody
POST提交键值对
很多时候我们会需要通过POST方式把键值对数据传送到服务器。 OkHttp提供了很方便的方式来做这件事情。
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody formBody = new FormEncodingBuilder()
.add(&#8220;platform&#8221;, &#8220;android&#8221;)
.add(&#8220;name&#8221;, &#8220;bug&#8221;)
.add(&#8220;subject&#8221;, &#8220;XXXXXXXXXXXXXXX&#8221;)
Request request = new Request.Builder()
.post(body)
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
throw new IOException(&#8220;Unexpected code &#8220; + response);
通过上面的例子我们可以发现,OkHttp在很多时候使用都是很方便的,而且很多代码也有重复,因此特地整理了下面的工具类。
OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。 如果有需要,可以使用clone方法,再进行自定义。这点在后面的高级教程里会提到。
enqueue为OkHttp提供的异步方法,入门教程中并没有提到,后面的高级教程里会有解释。
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import cn.wiz.sdk.constant.WizConstant;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
public class OkHttpUtil {
private static final OkHttpClient mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
* 该不会开启异步线程。
* @param request
* @throws IOException
public static Response execute(Request request) throws IOException{
return mOkHttpClient.newCall(request).execute();
* 开启异步线程访问网络
* @param request
* @param responseCallback
public static void enqueue(Request request, Callback responseCallback){
mOkHttpClient.newCall(request).enqueue(responseCallback);
* 开启异步线程访问网络, 且不在意返回结果(实现空callback)
* @param request
public static void enqueue(Request request){
mOkHttpClient.newCall(request).enqueue(new Callback() {
public void onResponse(Response arg0) throws IOException {
public void onFailure(Request arg0, IOException arg1) {
public static String getStringFromServer(String url) throws IOException{
Request request = new Request.Builder().url(url).build();
Response response = execute(request);
if (response.isSuccessful()) {
String responseUrl = response.body().string();
return responseUrl;
throw new IOException(&#8220;Unexpected code &#8220; + response);
private static final String CHARSET_NAME = &#8220;UTF-8&#8221;;
* 这里使用了HttpClinet的API。只是为了方便
* @param params
public static String formatParams(List&BasicNameValuePair& params){
return URLEncodedUtils.format(params, CHARSET_NAME);
* 为HttpGet 的 url 方便的添加多个name value 参数。
* @param url
* @param params
public static String attachHttpGetParams(String url, List&BasicNameValuePair& params){
return url + &#8220;?&#8221; + formatParams(params);
* 为HttpGet 的 url 方便的添加1个name value 参数。
* @param url
* @param name
* @param value
public static String attachHttpGetParam(String url, String name, String value){
return url + &#8220;?&#8221; + name + &#8220;=&#8221; + value;
高级属性其实用的不多,这里主要是对OkHttp github官方教程进行了翻译。
下载一个文件,打印他的响应头,以string形式打印响应体。
响应体的 string() 方法对于小文档来说十分方便、高效。但是如果响应体太大(超过1MB),应避免适应 string()方法 ,因为他会将把整个文档加载到内存中。
对于超过1MB的响应body,应使用流的方式来处理body。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://publicobject.com/helloworld.txt&#8221;)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
Headers responseHeaders = response.headers();
for (int i = 0; i & responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + &#8220;: &#8220; + responseHeaders.value(i));
System.out.println(response.body().string());
在一个工作线程中下载文件,当响应可读时回调Callback接口。读取响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://publicobject.com/helloworld.txt&#8221;)
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Request request, Throwable throwable) {
throwable.printStackTrace();
@Override public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
Headers responseHeaders = response.headers();
for (int i = 0; i & responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + &#8220;: &#8220; + responseHeaders.value(i));
System.out.println(response.body().string());
提取响应头
典型的HTTP头 像是一个 Map&String, String& :每个字段都有一个或没有值。但是一些头允许多个值,像Guava的。例如:HTTP响应里面提供的Vary响应头,就是多值的。OkHttp的api试图让这些情况都适用。
当写请求头的时候,使用header(name, value)可以设置唯一的name、value。如果已经有值,旧的将被移除,然后添加新的。使用addHeader(name, value)可以添加多值(添加,不移除已有的)。
当读取响应头时,使用header(name)返回最后出现的name、value。通常情况这也是唯一的name、value。如果没有值,那么header(name)将返回null。如果想读取字段对应的所有值,使用headers(name)会返回一个list。
为了获取所有的Header,Headers类支持按index访问。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;https://api.github.com/repos/square/okhttp/issues&#8221;)
.header(&#8220;User-Agent&#8221;, &#8220;OkHttp Headers.java&#8221;)
.addHeader(&#8220;Accept&#8221;, &#8220;application/ q=0.5&#8221;)
.addHeader(&#8220;Accept&#8221;, &#8220;application/vnd.github.v3+json&#8221;)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(&#8220;Server: &#8220; + response.header(&#8220;Server&#8221;));
System.out.println(&#8220;Date: &#8220; + response.header(&#8220;Date&#8221;));
System.out.println(&#8220;Vary: &#8220; + response.headers(&#8220;Vary&#8221;));
Post方式提交String
使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse(&#8220;text/x- charset=utf-8&#8221;);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = &#8220;&#8221;
+ &#8220;Releases\n&#8221;
+ &#8220;&#8212;&#8212;&#8211;\n&#8221;
+ &#8220;\n&#8221;
+ &#8221; * _1.0_ May 6, 2013\n&#8221;
+ &#8221; * _1.1_ June 15, 2013\n&#8221;
+ &#8221; * _1.2_ August 11, 2013\n&#8221;;
Request request = new Request.Builder()
.url(&#8220;https://api.github.com/markdown/raw&#8221;)
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
Post方式提交流
以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse(&#8220;text/x- charset=utf-8&#8221;);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8(&#8220;Numbers\n&#8221;);
sink.writeUtf8(&#8220;&#8212;&#8212;-\n&#8221;);
for (int i = 2; i &= 997; i++) {
sink.writeUtf8(String.format(&#8221; * %s = %s\n&#8221;, i, factor(i)));
private String factor(int n) {
for (int i = 2; i & n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + &#8221; × &#8220; + i;
return Integer.toString(n);
Request request = new Request.Builder()
.url(&#8220;https://api.github.com/markdown/raw&#8221;)
.post(requestBody)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
Post方式提交文件
以文件作为请求体是十分简单的。
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse(&#8220;text/x- charset=utf-8&#8221;);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
File file = new File(&#8220;README.md&#8221;);
Request request = new Request.Builder()
.url(&#8220;https://api.github.com/markdown/raw&#8221;)
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
Post方式提交表单
使用FormEncodingBuilder来构建和HTML&form&标签相同效果的请求体。键值对将使用一种HTML兼容形式的URL编码来进行编码。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormEncodingBuilder()
.add(&#8220;search&#8221;, &#8220;Jurassic Park&#8221;)
Request request = new Request.Builder()
.url(&#8220;https://en.wikipedia.org/w/index.php&#8221;)
.post(formBody)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
Post方式提交分块请求
MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。
private static final String IMGUR_CLIENT_ID = &#8220;&#8230;&#8221;;
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse(&#8220;image/png&#8221;);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
Headers.of(&#8220;Content-Disposition&#8221;, &#8220;form- name=\&#8221;title\&#8221;&#8221;),
RequestBody.create(null, &#8220;Square Logo&#8221;))
Headers.of(&#8220;Content-Disposition&#8221;, &#8220;form- name=\&#8221;image\&#8221;&#8221;),
RequestBody.create(MEDIA_TYPE_PNG, new File(&#8220;website/static/logo-square.png&#8221;)))
Request request = new Request.Builder()
.header(&#8220;Authorization&#8221;, &#8220;Client-ID &#8220; + IMGUR_CLIENT_ID)
.url(&#8220;https://api.imgur.com/3/image&#8221;)
.post(requestBody)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
使用Gson来解析JSON响应
Gson是一个在JSON和Java对象之间转换非常方便的api。这里我们用Gson来解析Github API的JSON响应。
注意:ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体。默认是UTF-8。
private final OkHttpClient client = new OkHttpClient();
private final Gson gson = new Gson();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;https://api.github.com/gists/c2a7c1be&#8221;)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
for (Map.Entry&String, GistFile& entry : gist.files.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue().content);
static class Gist {
Map&String, GistFile& files;
static class GistFile {
String content;
为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。
一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。
响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。
private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);
client = new OkHttpClient();
client.setCache(cache);
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://publicobject.com/helloworld.txt&#8221;)
Response response1 = client.newCall(request).execute();
if (!response1.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response1);
String response1Body = response1.body().string();
System.out.println(&#8220;Response 1 response:
&#8220; + response1);
System.out.println(&#8220;Response 1 cache response:
&#8220; + response1.cacheResponse());
System.out.println(&#8220;Response 1 network response:
&#8220; + response1.networkResponse());
Response response2 = client.newCall(request).execute();
if (!response2.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response2);
String response2Body = response2.body().string();
System.out.println(&#8220;Response 2 response:
&#8220; + response2);
System.out.println(&#8220;Response 2 cache response:
&#8220; + response2.cacheResponse());
System.out.println(&#8220;Response 2 network response:
&#8220; + response2.networkResponse());
System.out.println(&#8220;Response 2 equals Response 1? &#8220; + response1Body.equals(response2Body));
在这一节还提到了下面一句:
There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.
我不是很懂cache,平时用到的也不多,所以把Google在Android Developers一段相关的解析放到这里吧。
Force a Network Response
In some situations, such as after a user clicks a &#8216;refresh&#8217; button, it may be necessary to skip the cache, and fetch data directly from the server. To force a full refresh, add the no-cache directive:
connection.addRequestProperty(&#8220;Cache-Control&#8221;, &#8220;no-cache&#8221;);
If it is only necessary to force a cached response to be validated by the server, use the more efficient max-age=0 instead:
connection.addRequestProperty(&#8220;Cache-Control&#8221;, &#8220;max-age=0&#8221;);
Force a Cache Response
Sometimes you&#8217;ll want to show resources if they are available immediately, but not otherwise. This can be used so your application can show something while waiting for the latest data to be downloaded. To restrict a request to locally-cached resources, add the only-if-cached directive:
connection.addRequestProperty(&#8220;Cache-Control&#8221;, &#8220;only-if-cached&#8221;);
InputStream cached = connection.getInputStream();
// the resource was cached! show it
catch (FileNotFoundException e) {
// the resource was not cached
This technique works even better in situations where a stale response is better than no response. To permit stale cached responses, use the max-stale directive with the maximum staleness in seconds:
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks staleconnection.addRequestProperty(&#8220;Cache-Control&#8221;, &#8220;max-stale=&#8221; + maxStale);
以上信息来自:
取消一个Call
使用Call.cancel()可以立即停止掉一个正在执行的call。如果一个线程正在写请求或者读响应,将会引发IOException。当call没有必要的时候,使用这个api可以节约网络资源。例如当用户离开一个应用时。不管同步还是异步的call都可以取消。
你可以通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://httpbin.org/delay/2&#8221;) // This URL is served with a 2 second delay.
final long startNanos = System.nanoTime();
final Call call = client.newCall(request);
// Schedule a job to cancel the call in 1 second.
executor.schedule(new Runnable() {
@Override public void run() {
System.out.printf(&#8220;%.2f Canceling call.%n&#8221;, (System.nanoTime() &#8211; startNanos) / 1e9f);
call.cancel();
System.out.printf(&#8220;%.2f Canceled call.%n&#8221;, (System.nanoTime() &#8211; startNanos) / 1e9f);
}, 1, TimeUnit.SECONDS);
System.out.printf(&#8220;%.2f Executing call.%n&#8221;, (System.nanoTime() &#8211; startNanos) / 1e9f);
Response response = call.execute();
System.out.printf(&#8220;%.2f Call was expected to fail, but completed: %s%n&#8221;,
(System.nanoTime() &#8211; startNanos) / 1e9f, response);
} catch (IOException e) {
System.out.printf(&#8220;%.2f Call failed as expected: %s%n&#8221;,
(System.nanoTime() &#8211; startNanos) / 1e9f, e);
没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。
private final OkHttpClient client;
public ConfigureTimeouts() throws Exception {
client = new OkHttpClient();
client.setConnectTimeout(10, TimeUnit.SECONDS);
client.setWriteTimeout(10, TimeUnit.SECONDS);
client.setReadTimeout(30, TimeUnit.SECONDS);
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://httpbin.org/delay/2&#8221;) // This URL is served with a 2 second delay.
Response response = client.newCall(request).execute();
System.out.println(&#8220;Response completed: &#8220; + response);
每个call的配置
使用OkHttpClient,所有的HTTP Client配置包括代理设置、超时设置、缓存设置。当你需要为单个call改变配置的时候,clone 一个 OkHttpClient。这个api将会返回一个浅拷贝(shallow copy),你可以用来单独自定义。下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url(&#8220;http://httpbin.org/delay/1&#8221;) // This URL is served with a 1 second delay.
Response response = client.clone() // Clone to make a customized OkHttp for this request.
.setReadTimeout(500, TimeUnit.MILLISECONDS)
.newCall(request)
.execute();
System.out.println(&#8220;Response 1 succeeded: &#8220; + response);
} catch (IOException e) {
System.out.println(&#8220;Response 1 failed: &#8220; + e);
Response response = client.clone() // Clone to make a customized OkHttp for this request.
.setReadTimeout(3000, TimeUnit.MILLISECONDS)
.newCall(request)
.execute();
System.out.println(&#8220;Response 2 succeeded: &#8220; + response);
} catch (IOException e) {
System.out.println(&#8220;Response 2 failed: &#8220; + e);
这部分和HTTP AUTH有关。
相关资料:
OkHttp会自动重试未验证的请求。当响应是401 Not Authorized时,Authenticator会被要求提供证书。Authenticator的实现中需要建立一个新的包含证书的请求。如果没有证书可用,返回null来跳过尝试。
public List&Challenge& challenges()
Returns the authorization challenges appropriate for this response&#8216;s code.
If the response code is 401 unauthorized,
this returns the &#8220;WWW-Authenticate&#8221; challenges.
If the response code is 407 proxy unauthorized, this returns the &#8220;Proxy-Authenticate&#8221; challenges.
Otherwise this returns an empty list of challenges.
当需要实现一个Basic challenge, 使用Credentials.basic(username, password)来编码请求头。
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
client.setAuthenticator(new Authenticator() {
@Override public Request authenticate(Proxy proxy, Response response) {
System.out.println(&#8220;Authenticating for response: &#8220; + response);
System.out.println(&#8220;Challenges: &#8220; + response.challenges());
String credential = Credentials.basic(&#8220;jesse&#8221;, &#8220;password1&#8221;);
return response.request().newBuilder()
.header(&#8220;Authorization&#8221;, credential)
@Override public Request authenticateProxy(Proxy proxy, Response response) {
return null; // Null indicates no attempt to authenticate.
Request request = new Request.Builder()
.url(&#8220;http://publicobject.com/secrets/hellosecret.txt&#8221;)
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException(&#8220;Unexpected code &#8220; + response);
System.out.println(response.body().string());
同时整合了
这篇文章以及其中的评论。
关于如何在 Android 手机上窃取微信聊天记录。
这几天事情比较多,可还是想尽快写下这篇文章。
本以为微信的聊天记录以我本人现存能力获取不到,但经过一番尝试,还是成功了。前提:手机需要已经 root。
在我的直觉里,微信的聊天记录一定会是加密的,而且是用了现代密码学中的加密算法,只要官方保存好密钥那么我等平民不可能获取得到。而接下来,我想说的是我等平民如何能够获取到微信的所有聊天记录。
一般来说,Android 应用程序的数据库文件会保存在 /data/data/packagename/database 文件夹下,而微信稍稍有些不同,但也不难发现其数据库文件保存位置,位于:/data/data/com.tencent.mm/MicroMsg 路径下,注意这里有两个像是乱码的很长名字的文件夹,这两个文件夹正是用户的个人信息保存的位置(我这里有两个,估计是之前谁用我的手机登录过微信。。。),再看这个文件夹里面有一个很显眼的数据库文件 EnMicroMsg.db,凭猜测也可以感觉到前缀 En 应该是 Encrypt 的缩写,果然,用 SQLite Professional 打开提示该数据库文件被加密,具体如下几幅图
说实话,给 SQLite 加密,我还是第一次遇到。于是放狗搜,发现有一个软件叫做 sqlcipher.exe,在有密钥的情况下可以打开被加密的 db 文件(这个软件国内很难找,我放在了我的七牛云存储上,)。那么问题来了,密钥是什么?
不懂微信为何要这样做,密钥是当前手机的 IMEI + 微信UIN 的 MD5值(32位小写)前7位。(注:此处信息不是自己发现的,是无意中在网上搜到的)分别说如何获得如上内容:
获取 IMEI 码 :
手机拨号输入 *#06# 显示出来的即是所需 IMEI
获取微信 UIN 码 :
相信大家都用过微信的网页版,我们就通过它来获取。首先登录微信版网页,再新建一个标签页 chrome://net-internals/#events,这个是 chrome 内置的抓包工具,所有进出的数据包都能看到,所以类似于下面这样
之后在网页聊天界面随意给某个联系人发送一条消息,回到抓包标签页,按 Ctrl + F 全局搜索 uin ,这个时候就会看到下图中的 uin 码,我这里是 9 位
到此为止,我们获取了设备的 IMEI(如12345) 和 微信账号的 UIN(如67890) 码,下面来计算 32 位 MD5 消息摘要值,使用
在线加密的网站就可以,则要加密的字符串为 IMEI+UIN,即 ,加密后就会得到四种 MD5 值,我们需要的是32位长度小写的,如图
取该 MD5 值前 7 位,即为密钥。接下来,打开 sqlcipher.exe ,File-&Open Database, 选择 EnMicroMsg.db 文件,打开后就可以看到让我们输入密钥,输入刚才的7位密钥,点击OK,如图
如果你成功看到了下图的界面,则说明99%已经成功了。
那如何能看到真正的聊天记录内容呢,点击 Browse Data,再筛选 message ,看吧,赤裸裸的聊天记录赫然摆在眼前。。。
最后,想跟各位说的是,手机千万不要交给程序员。。。

我要回帖

更多关于 点击按钮下载文件 的文章

 

随机推荐