Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

Android颜色集

$
0
0

对于在不居中引入项目,我们有时候要去color.xml中设置一些颜色,本篇整理一些常见颜色值。直接给一个Color文件,放在values文件下就行。这里面包含了不知道多少种颜色,请任意Get!

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <color name="white">#FFFFFF</color><!--白色 -->  
    <color name="ivory">#FFFFF0</color><!--象牙色 -->  
    <color name="lightyellow">#FFFFE0</color><!--亮黄色-->  
    <color name="yellow">#FFFF00</color><!--黄色 -->  
    <color name="snow">#FFFAFA</color><!--雪白色 -->  
    <color name="floralwhite">#FFFAF0</color><!--花白色 -->  
    <color name="lemonchiffon">#FFFACD</color><!--柠檬绸色 -->  
    <color name="cornsilk">#FFF8DC</color><!--米绸色 -->  
    <color name="seashell">#FFF5EE</color><!--海贝色 -->  
    <color name="lavenderblush">#FFF0F5</color><!--淡紫红 -->  
    <color name="papayawhip">#FFEFD5</color><!--番木色 -->  
    <color name="blanchedalmond">#FFEBCD</color><!--白杏色 -->  
    <color name="mistyrose">#FFE4E1</color><!--浅玫瑰色 -->  
    <color name="bisque">#FFE4C4</color><!--桔黄色 -->  
    <color name="moccasin">#FFE4B5</color><!--鹿皮色 -->  
    <color name="navajowhite">#FFDEAD</color><!--纳瓦白 -->  
    <color name="peachpuff">#FFDAB9</color><!--桃色 -->  
    <color name="gold">#FFD700</color><!--金色 -->  
    <color name="pink">#FFC0CB</color><!--粉红色 -->  
    <color name="lightpink">#FFB6C1</color><!--亮粉红色-->  
    <color name="orange">#FFA500</color><!--橙色 -->  
    <color name="lightsalmon">#FFA07A</color><!--亮肉色 -->  
    <color name="darkorange">#FF8C00</color><!--暗桔黄色 -->  
    <color name="coral">#FF7F50</color><!--珊瑚色 -->  
    <color name="hotpink">#FF69B4</color><!--热粉红色 -->  
    <color name="tomato">#FF6347</color><!--西红柿色 -->  
    <color name="orangered">#FF4500</color><!--红橙色 -->  
    <color name="deeppink">#FF1493</color><!--深粉红色 -->  
    <color name="fuchsia">#FF00FF</color><!--紫红色 -->  
    <color name="magenta">#FF00FF</color><!--红紫色 -->  
    <color name="red">#FF0000</color><!--红色 -->  
    <color name="oldlace">#FDF5E6</color><!--老花色 -->  
    <color name="lightgoldenrodyellow">#FAFAD2</color><!--亮金黄色 -->  
    <color name="linen">#FAF0E6</color><!--亚麻色 -->  
    <color name="antiquewhite">#FAEBD7</color><!--古董白 -->  
    <color name="salmon">#FA8072</color><!--鲜肉色 -->  
    <color name="ghostwhite">#F8F8FF</color><!--幽灵白 -->  
    <color name="mintcream">#F5FFFA</color><!--薄荷色 -->  
    <color name="whitesmoke">#F5F5F5</color><!--烟白色 -->  
    <color name="beige">#F5F5DC</color><!--米色 -->  
    <color name="wheat">#F5DEB3</color><!--浅黄色 -->  
    <color name="sandybrown">#F4A460</color><!--沙褐色-->  
    <color name="azure">#F0FFFF</color><!--天蓝色 -->  
    <color name="honeydew">#F0FFF0</color><!--蜜色 -->  
    <color name="aliceblue">#F0F8FF</color><!--艾利斯兰 -->  
    <color name="khaki">#F0E68C</color><!--黄褐色 -->  
    <color name="lightcoral">#F08080</color><!--亮珊瑚色 -->  
    <color name="palegoldenrod">#EEE8AA</color><!--苍麒麟色 -->  
    <color name="violet">#EE82EE</color><!--紫罗兰色 -->  
    <color name="darksalmon">#E9967A</color><!--暗肉色 -->  
    <color name="lavender">#E6E6FA</color><!--淡紫色 -->  
    <color name="lightcyan">#E0FFFF</color><!--亮青色 -->  
    <color name="burlywood">#DEB887</color><!--实木色 -->  
    <color name="plum">#DDA0DD</color><!--洋李色 -->  
    <color name="gainsboro">#DCDCDC</color><!--淡灰色 -->  
    <color name="crimson">#DC143C</color><!--暗深红色 -->  
    <color name="palevioletred">#DB7093</color><!--苍紫罗兰色 -->  
    <color name="goldenrod">#DAA520</color><!--金麒麟色 -->  
    <color name="orchid">#DA70D6</color><!--淡紫色 -->  
    <color name="thistle">#D8BFD8</color><!--蓟色 -->  
    <color name="lightgray">#D3D3D3</color><!--亮灰色 -->  
    <color name="lightgrey">#D3D3D3</color><!--亮灰色 -->  
    <color name="tan">#D2B48C</color><!--茶色 -->  
    <color name="chocolate">#D2691E</color><!--巧可力色 -->  
    <color name="peru">#CD853F</color><!--秘鲁色 -->  
    <color name="indianred">#CD5C5C</color><!--印第安红 -->  
    <color name="mediumvioletred">#C71585</color><!--中紫罗兰色 -->  
    <color name="silver">#C0C0C0</color><!--银色 -->  
    <color name="darkkhaki">#BDB76B</color><!--暗黄褐色 -->  
    <color name="rosybrown">#BC8F8F</color> <!--褐玫瑰红 -->  
    <color name="mediumorchid">#BA55D3</color><!--中粉紫色 -->  
    <color name="darkgoldenrod">#B8860B</color><!--暗金黄色 -->  
    <color name="firebrick">#B22222</color><!--火砖色 -->  
    <color name="powderblue">#B0E0E6</color><!--粉蓝色 -->  
    <color name="lightsteelblue">#B0C4DE</color><!--亮钢兰色 -->  
    <color name="paleturquoise">#AFEEEE</color><!--苍宝石绿 -->  
    <color name="greenyellow">#ADFF2F</color><!--黄绿色 -->  
    <color name="lightblue">#ADD8E6</color><!--亮蓝色 -->  
    <color name="darkgray">#A9A9A9</color><!--暗灰色 -->  
    <color name="darkgrey">#A9A9A9</color><!--暗灰色 -->  
    <color name="brown">#A52A2A</color><!--褐色 -->  
    <color name="sienna">#A0522D</color><!--赭色 -->  
    <color name="darkorchid">#9932CC</color><!--暗紫色-->  
    <color name="palegreen">#98FB98</color><!--苍绿色 -->  
    <color name="darkviolet">#9400D3</color><!--暗紫罗兰色 -->  
    <color name="mediumpurple">#9370DB</color><!--中紫色 -->  
    <color name="lightgreen">#90EE90</color><!--亮绿色 -->  
    <color name="darkseagreen">#8FBC8F</color><!--暗海兰色 -->  
    <color name="saddlebrown">#8B4513</color><!--重褐色 -->  
    <color name="darkmagenta">#8B008B</color><!--暗洋红 -->  
    <color name="darkred">#8B0000</color><!--暗红色 -->  
    <color name="blueviolet">#8A2BE2</color><!--紫罗兰蓝色 -->  
    <color name="lightskyblue">#87CEFA</color><!--亮天蓝色 -->  
    <color name="skyblue">#87CEEB</color><!--天蓝色 -->  
    <color name="gray">#808080</color><!--灰色 -->  
    <color name="grey">#808080</color><!--灰色 -->  
    <color name="olive">#808000</color><!--橄榄色 -->  
    <color name="purple">#800080</color><!--紫色 -->  
    <color name="maroon">#800000</color><!--粟色 -->  
    <color name="aquamarine">#7FFFD4</color><!--碧绿色-->  
    <color name="chartreuse">#7FFF00</color><!--黄绿色 -->  
    <color name="lawngreen">#7CFC00</color><!--草绿色 -->  
    <color name="mediumslateblue">#7B68EE</color><!--中暗蓝色 -->  
    <color name="lightslategray">#778899</color><!--亮蓝灰 -->  
    <color name="lightslategrey">#778899</color><!--亮蓝灰 -->  
    <color name="slategray">#708090</color><!--灰石色 -->  
    <color name="slategrey">#708090</color><!--灰石色 -->  
    <color name="olivedrab">#6B8E23</color><!--深绿褐色 -->  
    <color name="slateblue">#6A5ACD</color><!--石蓝色 -->  
    <color name="dimgray">#696969</color><!--暗灰色 -->  
    <color name="dimgrey">#696969</color><!--暗灰色 -->  
    <color name="mediumaquamarine">#66CDAA</color><!--中绿色 -->  
    <color name="cornflowerblue">#6495ED</color><!--菊兰色 -->  
    <color name="cadetblue">#5F9EA0</color><!--军兰色 -->  
    <color name="darkolivegreen">#556B2F</color><!--暗橄榄绿  -->  
    <color name="indigo">#4B0082</color><!--靛青色 -->  
    <color name="mediumturquoise">#48D1CC</color><!--中绿宝石 -->  
    <color name="darkslateblue">#483D8B</color><!--暗灰蓝色 -->  
    <color name="steelblue">#4682B4</color><!--钢兰色 -->  
    <color name="royalblue">#4169E1</color><!--皇家蓝 -->  
    <color name="turquoise">#40E0D0</color><!--青绿色 -->  
    <color name="mediumseagreen">#3CB371</color><!--中海蓝 -->  
    <color name="limegreen">#32CD32</color><!--橙绿色 -->  
    <color name="darkslategray">#2F4F4F</color><!--暗瓦灰色 -->  
    <color name="darkslategrey">#2F4F4F</color><!--暗瓦灰色 -->  
    <color name="seagreen">#2E8B57</color><!--海绿色 -->  
    <color name="forestgreen">#228B22</color><!--森林绿 -->  
    <color name="lightseagreen">#20B2AA</color><!--亮海蓝色 -->  
    <color name="dodgerblue">#1E90FF</color><!--闪兰色 -->  
    <color name="midnightblue">#191970</color><!--中灰兰色 -->  
    <color name="aqua">#00FFFF</color><!--浅绿色 -->  
    <color name="cyan">#00FFFF</color><!--青色 -->  
    <color name="springgreen">#00FF7F</color><!--春绿色-->  
    <color name="lime">#00FF00</color><!--酸橙色 -->  
    <color name="mediumspringgreen">#00FA9A</color><!--中春绿色 -->  
    <color name="darkturquoise">#00CED1</color><!--暗宝石绿 -->  
    <color name="deepskyblue">#00BFFF</color><!--深天蓝色 -->  
    <color name="darkcyan">#008B8B</color><!--暗青色 -->  
    <color name="teal">#008080</color><!--水鸭色 -->  
    <color name="green">#008000</color><!--绿色 -->  
    <color name="darkgreen">#006400</color><!--暗绿色 -->  
    <color name="blue">#0000FF</color><!--蓝色 -->  
    <color name="mediumblue">#0000CD</color><!--中兰色 -->  
    <color name="darkblue">#00008B</color><!--暗蓝色 -->  
    <color name="navy">#000080</color><!--海军色 -->  
    <color name="black">#000000</color><!--黑色 -->  
  
</resources>


作者:qq_32059827 发表于2016/10/23 22:46:31 原文链接
阅读:248 评论:2 查看评论

Android MediaPlayer的生命周期

$
0
0

转载本专栏文章,请注明出处尊重原创:博客地址http://blog.csdn.net/qq_32059827/article/details/52905490:小杨的博客

MediaPlayer的状态转换图也表征了它的生命周期,搞清楚这个图可以帮助我们在使用MediaPlayer时考虑情况更周全,写出的代码也更具健壮性。

接下来用几张图,来慢慢演变它的生命周期过程:

图一:初识几个API,了解播放暂停

图二:了解stoped状态与其它状态关系

图三:Preparing以及它的回调方法

图四:需要考虑的两个状态。Error和释放内存End

图五:官方文档版本以及详细解释:

解释:

这张状态转换图清晰的描述了MediaPlayer的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常

 

Idle 状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确),通过reset()方法进入idle状态的话会触发OnErrorListener.onError(),并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError(),也不会进入Error状态。

 

End 状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会在进入任何其他状态了。

 

Initialized 状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。

 

Prepared 状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。

 

Preparing 状态:这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。

 

Started 状态:显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。

 

Paused 状态:Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。

 

Stop 状态:Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。

 

PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。

 

Error状态:如果由于某种原因MediaPlayer出现了错误,会触发OnErrorListener.onError()事件,此时MediaPlayer即进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态。

 

参考文档:AndroidSDK1.5官方文档:android-sdk-windows-1.5_r3/docs/reference/android/media/MediaPlayer.html

相信通过上面介绍生命周期,相信可以更方便更简单的理解。
作者:qq_32059827 发表于2016/10/23 22:57:20 原文链接
阅读:249 评论:0 查看评论

工作第十四周:整理收藏夹、旧文章有感

$
0
0

这一周

作死选了本英文版 Effective Java,看得好痛苦 T.T.

新单词:
composed 组成
accomplished 典型的,熟练的
encapsulates 封装
transient 短暂的,瞬态的,Java 关键字之一
explicit 明确的
skeletal 骨骼的,骨瘦如柴的
overall efficiency 整体效率
prohibit 禁止
poll 调查,得到,投票
peek 偷看,窥视;眯着眼睛看
ineligible 不合格的,无入选资格的
versus VS

今天晚上整理了下自己写的、转载的文章,还有收藏的一些链接。

在整理过程中可以把我的文章分为 5 类:

  1. 鸡汤
  2. 时兴技术
  3. 零散知识点
  4. 报错解决办法
  5. 面试相关

鸡汤

鸡汤这种东西,少喝有益,多喝伤身。

之前有段时间找工作、学习疲惫,总爱看些鸡汤,然后转载过来,结果放在那再也没看。

这次整理过程,看到没什么用的,统统删掉。

不过还是有一些立意新鲜,打破我三观的:
比如这篇 年收入50万美元的软件工程师做的是什么类型的工作?,虽然标题看着有点反胃,图片也丢了,但是观点很正啊,摘几段重温一下:

被忽视的细节是,并不是所有的汗水能够创造同等的价值。

第1种类型的工人希望通过使用“技能”或完成“工作”来“获取报酬”。

第2种类型的工人愿意打破一些规则,成为弃儿,并经历一段不确定时间的饥饿阶段,以期为村庄创造一条源源不断的现金流。

可以说,这个村庄(在这个例子中指Google)中的所有先驱几乎都是第2种类型的人,他们能将自己的渴望维持多年,直到建立数十亿美元的现金流。这部分人创造了很大数量的限制股(RSUs),诸如:

1.从项目成立之初,负责并搭建起项目的主要核心价值。

2.创造新的价值,作为项目的一方面并被证明是有价值的。

3.以一家能创造价值的创业公司的身边被收购。

4.或者(可能性不大),拥有一条价值流的知识垄断。

第2种类型的人并不攀比薪水或就薪水进行谈判,因为他们并不是在贩卖服务给村庄(组织)。他们是在贩卖被忽视的财富。村庄基本上别无选择,只能补偿他,按照他所带来的财富估值。在他手中的财富可以进行交易,使双方受益。

问题并不在于是否会有谈判。而在于当谈判进行时,这一特殊村庄是否会坐在谈判桌的一边。并且当它对于村庄是不可获取的水资源时,在美元符号前的额外的零会被认为是一种无需置疑的必要。

再比如这篇 我为什么把think in java 读了10遍
:

自从干上这行以后,我可能就像中国大多数程序员一样,一个项目接着一个项目,什么不会就学什么,就这样干了5年,什么html、css、js、java、sql、服务器等等都做过了,终于感觉自己都会了,可是又很茫然,为什么呢?

因为在这行里,总有存在这样一些论调,“如果你30岁还做程序员,你就会失业”我不知道这句话是谁说的,但是的确给我的印象很深,那是我快30了,我可不想30岁以后没有饭吃,虽然我这么想但是也没有采取什么行动,直到有一天我不知为什么买了一本think
in java 4th,哦对了,好像是为了面试更高的职位,好像是吧,具体的我记不得,不过这不重要,重要的是我从第二章就有些看不懂了,从那时起,我才知道,妈的,我的基础太差,我有些恐惧了,不是恐惧我会失业,而是恐惧这个行业我干了5年,居然输给了一本书的前50页,丢人。

我就怀着这样的心情开始了读think in java,说实在的读第一遍很痛苦,第二遍同样很痛苦,因为很多名词、思想根本不理解,为了理解这些词和思想,迫使我一次又一次的读下去,就这样整整读了一年10遍,虽然有些地方我还是读不明白,但发现我的思想产生了巨大的变化,这个变化不是指java水平,确切的说应该是语言本身。

之后我又读了effective
java等书籍,从那以后在公司中几乎已经没有人是我的对手了,我分析问题的角度和方式与读书之前完全是两个级别,我在公司小有名气了,我有点沾沾自喜,天天盼望着给人讲讲我的思想,就在这样的状态下,我继续买书、读书,不久之后问题又来了,我发现并没有搞懂think in java,更准确的说我没有搞懂计算机,因为我基础太差了,什么数据结构、算法导论等等著作,我几乎是打开前10页就读不下去了,我突然间感到了,整个中国这个行业的浮躁和大学教育的失败,我又重新开始学习基础知识,目的不是为了更高的工资,而是更明白我所从事的事业。

虽然作者表达有点激动,但还是说的比较实际。

还有一些自己立下的 flag,想了想还是留下打脸吧,疼才让人清醒。

时兴技术

虽然从业不到两年,我已经感受到我们这行的特点:变化。

每时每刻 GitHub 上都有新项目创建。

稍微大点的公司都有自己的研发体系,技术框架,其中很多公司拥抱开源,把项目公开,吸引了一大波程序猿体验。

这两年时间,有些是公司要求,有些是自己好奇,也学习了解了一些时兴技术,比如 PhoneGap, CrossWalk … 现在大多随风消逝了。

现在除了收获了过时的文章,我几乎什么也没收获。有人可能觉得积极尝试、主动学习新技术才是好程序员,但我觉得新手还是打好基础再说吧,所以这一阶段也开始看看源码整理整理基础。

刚工作,还是不能心太急啊。

零散知识点

占我文章比例最多的就是一些零散知识点了,简单到 Intent传递数据和Bundle传递数据的区别,也有复杂点的 Android中View绘制流程以及invalidate()等相关方法分析

比如自定义 View,我有不下转载四五篇文章都是自定义相关,每次都是看别人的,看懂了转过来就完了。结果过段时间还得看。

零散知识记下来当然比不记强,但是不结合相关内容总结个专题,连点为面,到后来都会忘个差不多。

这次整理过程中把一些零散知识点记录下来,发现还不少,留着后续总结。

报错解决办法

写报错解决办法的文章也挺多,一般答案是我意想不到或者没见过的就总结、转载一下。

有一部分内容现在看起来很简单,就删了。

还有些第三方 SDK 使用的步骤,也过时了,删了。

有一部分内容访问量挺多,仔细一看其实都是对工具不熟悉导致的问题,比如 Eclipse 导入 .so ,Android Studio 配置,Gradle 配置。

这些工具的使用其实也值得投入一下时间,那样想必可以减少很多遇见问题、尝试、搜索、记录耽搁的时间把。

面试相关

找工作那段实际,看到面试总结就会收藏、转载,今天把不同时间段收藏的文章对比看了看,发现其实考察内容就是那么点,只不过很多时候看完面经就一笑而过,也没有整理、记录下知识点。

从我校招中工作的过程中,我就发现一件事:

那些看起来技术水平明明不如我的人,被录取的机会却比我更高。

很长一段时间我都觉得不公平,面试官面试我的时候一定是心情不好。

等工作了,接触了一些同事和网友,才知道工作中最重要的不是你开发技术怎么样,而是你是否能清楚的表达、接收、执行需求,表现在跟产品沟通、跟领导沟通、跟测试沟通、跟小伙伴沟通,到最后,最重要的还是“沟通”

而决定你沟通能力的其实是看不见的软实力,比如认识问题、分析问题、解决问题的思想高度、对待问题的态度性格还有自己的精神状态,工作中、或者面试时,这些是更重要的部分。

我以前觉得自己技术水平努力提高就完事大吉,现在觉得,以前 too naive。

最后矫情一下

我不去想身后会不会袭来寒风冷雨

既然目标是地平线

留给世界的只能是背影

我不去想未来是平坦还是泥泞

只要热爱生命

一切,都在意料之中

作者:u011240877 发表于2016/10/23 23:02:56 原文链接
阅读:1028 评论:3 查看评论

SQLite数据库常用操作

$
0
0

转载请注明出处:http://blog.csdn.net/qxuewei/article/details/52900455

SQLite

SQLite是一款轻型的嵌入式关系数据库
它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了
目前广泛应用于移动设备中存储数据(Android/iOS/WP等)
处理数据的速度非常快,效率非常高

关系数据库的特点

一个 字段(COL) 存储一个值,类似于对象的一个属性
一 行(ROW) 存储一条记录,类似于一个对象
一个 表(TABLE) 存储一系列数据,类似于对象数组
多个 表 之间存在一定 关系,类似于对象之间的关系,例如:一条微博数据中包含用户记录

SQLite的使用步骤

1.新建一个数据库->一个用于存储数据的文件
2.数据库中创建表->一个表中用于记录一系列数据
创建表时需要指定该表有哪些字段,比如用户表中需要姓名,性别,年龄,头像等字段
3.在表中添加数据
4.对表进行增删改查操作
增:给表中添加数据
删:从表中删除数据
改:修改表中原有数据
查:查询表中原有数据

常用数据类型

NULL - 空值
INTERGER - 有符号整数类型
REAL - 浮点数类型
TEXT - 字符串(其编码取决于DB的编码)
BLOB - 二进制表示

常用语句备忘

1>新增表

/**
新建用户表
*/
CREATE TABLE IF NOT EXISTS 't_User' ('ID' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,'name' TEXT,'age' INTEGER,'icon' TEXT);

创建表的格式

CREATE TABLE IF NOT EXISTS '表名' (
        '字段名' 类型(INTEGER, REAL, TEXT, BLOB)
                NOT NULL    不允许为空
                PRIMARY KEY    主键
                AUTOINCREMENT 自增长,
        '字段名2' 类型,
        ...
    )

2>新增表中字段数据

/**
新增'用户表'中字段数据
*/
INSERT INTO 't_User' ('name','age','icon') VALUES ('邱学伟',25,'http://qiuxuewei.com/icon.png')

新增数据格式

INSERT INTO 't_User' ( 字典名 ) VALUES ( 字段名对应的值 )

1>删除表

删除'用户表'
*/
DROP TABLE IF EXISTS 't_User'

删除表的格式

DROP TABLE IF EXISTS '表名';

2>删除表中字段/字段值

/**
删除名为'邱学伟'的字段
*/
DELETE FROM 't_User' WHERE name = '邱学伟'

1>更新表中字段值,注意在SQL语句中,更新语句的字段名不要加单引号
demo1:

/**  
更新'用户表'中年龄信息
*/
UPDATE 't_User' SET 'age' = 28 WHERE name = '邱学伟'

demo2:

/**
更新年龄大于25的icon字段信息
*/
UPDATE 't_User' SET 'icon' = 'http://icon.png' WHERE age > 25

更新表中字典数据的格式

UPDATE 't_student' SET 字段 = '值' WHERE 条件判断;

1>基本查询

/**
基本查询
*/
SELECT * FROM 't_User'

2>查询某些字段

/**
查询某些字段
*/
SELECT name,age FROM 't_User'

3>查询约束条件下对应的数据

/**
通过某些条件约束查询对应数据
*/
SELECT name FROM 't_User' WHERE age > 25

4>模糊查询

/**
模糊查询 使用like关键词 % - 填充未知元素
*/
SELECT * FROM 't_User' WHERE name LIKE '%珂%'

5>查询数量

查询个数
查询表内一共有多少字段
*/
SELECT count(*) FROM 't_User'
/**
查询字段内一共有多少数据(包括值为空的数据)
*/
SELECT count(name) FROM 't_User'

6>排序

/**
排序
升序 ASC(默认)
*/
SELECT * FROM 't_User' ORDER BY age
/**
降序 DESC
*/
SELECT * FROM 't_User' ORDER BY age DESC

7>分页查询 - LIMIT

/**
分页查询用 - LIMIT
格式:SELECT * FROM t_student LIMIT 数字1,数字2;
1>数字1的意思是前面跳过多少条数据
2>数字2的意思是本次查询多少条数据
*/
SELECT * FROM 't_User' LIMIT 1,3
/**
limit后只有一个数字即从头取值
*/
SELECT * from 't_User' LIMIT 2

8>起别名

/**
起别名
给字段起别名
*/
SELECT name AS n,age AS a FROM 't_User'
/**
给表起别名
*/
SELECT U.name,U.age FROM 't_User' AS U
作者:qiuxuewei2012 发表于2016/10/24 8:53:32 原文链接
阅读:211 评论:0 查看评论

一个上架了的React Native项目实战总结

$
0
0

项目源码下载:GitHub Popular

喜欢逛GitHub的小伙伴都知道,它有个查看最热项目的功能叫treding,但这个功能只能在网页上查看,
而且在手机上浏览显示效果很不友好,而我想在地铁上,餐厅,路上等空余的时间使用它,所以我需要一款带有这个功能的App,
不仅于此,我还想要在这款App上查询GitHub上我所喜欢的项目,甚至在手机没网的时候也能看到,而且我想要我的iOS和Android手机都能使用这款App,
于是GitHub Popular便诞生了。

这个项目满足了我如下3方面的需求:

  1. 在手机App上也可以使用GitHub 的treding功能来查看最热最火的开源项目。
  2. 在手机App上也可以搜索GitHub上的开源项目,并且可以进行查看、收藏、分享等操作。
  3. 可以订阅我所喜欢的标签或语言,让感兴趣的热门项目一个不漏。

githubpupular

开发环境及工具

环境:

  • OSX:10.11.6
  • Node.js:6.3.1
  • react-native:0.32.0

工具:

  • Git
  • WebStorm
  • AndroidStudio
  • Xcode

所用技术与第三方库

所用技术

  • ES5/ES6
  • React
  • Flexbox
  • AsyncStorage
  • fetch api
  • Native Modules

第三方工具

  • react-native-check-box
  • react-native-easy-toast
  • react-native-splash-screen
  • react-native-htmlview
  • react-native-parallax-scroll-view
  • react-native-scrollable-tab-view
  • react-native-sortable-listview
  • react-native-tab-navigator

功能流程图

GitHub Popular-功能结构图

总结

此项目是基于目前比较火的React Native技术架构的,也用到一些Android和iOS技术,其中Android、iOS两端代码复用率有90%之多,该项目占据我不少业余时间,不过总算研发完成,并成功上架。在此过程中填了不少的坑,包括GitHub没有开放treding的Api,需要自己动手实现它,以及自定义主题等等,后期有时间会整理出来分享给大家。

GitHub Popular的Android版本已上架,大家可以从百度手机助手应用宝上下载使用,iOS版就差一个99刀的账号就可以上架了,囊中羞涩呜呜~~~~。项目开源在GitHub上供热爱移动开发的小伙伴学习研究,喜欢的小伙伴不要忘记点个赞支持一下哦。

最后

既然来了,留下个喜欢再走吧,鼓励我继续创作(^_^)∠※

如果喜欢我的文章,那就关注我的博客@ devio.org吧,让我们一起做朋友~~

戳这里,加关注哦:

微博:第一时间获取推送
个人博客:干货文章都在这里哦
GitHub:我的开源项目

作者:fengyuzhengfan 发表于2016/10/24 9:23:28 原文链接
阅读:197 评论:2 查看评论

iOS-SQLite在项目中实际使用(Objective-C)

$
0
0

转载请注明出处:http://blog.csdn.net/qxuewei/article/details/52909411

实际开发中,掌握并熟练使用SQLite数据库对app内数据进行操作是移动开发人员至关重要的技能,iOS中封装的coreData固然也是对SQLite的二层封装,强大的https://github.com/ccgus/fmdb‘>FMDB也会帮程序员减轻很多不必要的麻烦,提高工作效率,既然是封装.性能自然不如直接操作SQL语句

创建数据库管理类SQLiteManager

设置类方法创建单例对象-OC中创建单例可以单独生成类方法创建单例对象,也可使用原始init方法创建普通对象

.h
@interface SQLiteManager : NSObject
//类方法生成单例对象
+(instancetype)shareInstance;
@end

.m
@implementation SQLiteManager
static SQLiteManager *instance;
+(instancetype)shareInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
@end

操作数据库

首先需要在项目中导入libsqlite3.tbd框架
这里写图片描述
然后在SQLiteManager数据库管理类引入头文件

#import <sqlite3.h>

打开数据库

在.h文件中暴露打开数据库的接口

//打开数据库
-(BOOL)openDB;

.m文件中轻松加愉快的实现使用原生SQLite框架的各种操作数据库的方法.
SQL语句建议直接复制粘贴备忘录中的语句,以免拼写出错(有可能是博客中最没用的一句话)

#pragma mark - 打开/创建数据库
-(BOOL)openDB{
    //app内数据库文件存放路径-一般存放在沙盒中
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *DBPath = [documentPath stringByAppendingPathComponent:@"appDB.sqlite"];
    //创建(指定路径不存在数据库文件)/打开(已存在数据库文件) 数据库文件
    //sqlite3_open(<#const char *filename#>, <#sqlite3 **ppDb#>)  filename:数据库路径  ppDb:数据库对象
    if (sqlite3_open(DBPath.UTF8String, &_db) != SQLITE_OK) {
        //数据库打开失败
        return NO;
    }else{
        //打开成功创建表
        return [self creatTable];
    }
}
-(BOOL)creatTable{
    //创建表的SQL语句
    //用户 表
    NSString *creatUserTable = @"CREATE TABLE IF NOT EXISTS 't_User' ( 'ID' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,'name' TEXT,'age' INTEGER,'icon' TEXT);";
    //车 表
    NSString *creatCarTable = @"CREATE TABLE IF NOT EXISTS 't_Car' ('ID' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,'type' TEXT,'output' REAL,'master' TEXT)";
    //项目中一般不会只有一个表
    NSArray *SQL_ARR = [NSArray arrayWithObjects:creatUserTable,creatCarTable, nil];
    return [self creatTableExecSQL:SQL_ARR];
}
-(BOOL)creatTableExecSQL:(NSArray *)SQL_ARR{
    for (NSString *SQL in SQL_ARR) {
        //参数一:数据库对象  参数二:需要执行的SQL语句  其余参数不需要处理
        if (![self execSQL:SQL]) {
            return NO;
        }
    }
    return YES;
}
#pragma 执行SQL语句
-(BOOL)execSQL:(NSString *)SQL{
    return sqlite3_exec(self.db, SQL.UTF8String, nil, nil, nil) == SQLITE_OK;
}

数据库内SQL操作

#pragma 执行SQL语句
-(BOOL)execSQL:(NSString *)SQL{
    return sqlite3_exec(self.db, SQL.UTF8String, nil, nil, nil) == SQLITE_OK;
}

如果需要更新数据库对应表中数据,直接调用SQL执行方法即可实现

-(void)updateIcon{
    //更新对应的SQL语句
    NSString *SQL = [NSString stringWithFormat:@"UPDATE 't_User' SET icon='%@' WHERE name = '%@'",@"http://qiuxuewei.com/newIcon.png",@"name_6"];
    if ([[SQLiteManager shareInstance] execSQL:SQL]) {
        NSLog(@"对应数据修改成功");
    }
}

项目中的Model自定义对象可以自定义一个将自身插入数据库的方法

-(BOOL)insertSelfToDB{
    //插入对象的SQL语句
    NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO 't_User' (name,age,icon) VALUES ('%@',%ld,'%@');",self.name,self.age,self.icon];
    return [[SQLiteManager shareInstance] execSQL:insertSQL];
}

查询数据库中对应表中所有数据

#pragma mark - 查询数据库中数据
-(NSArray *)querySQL:(NSString *)SQL{
    //准备查询
    // 1> 参数一:数据库对象
    // 2> 参数二:查询语句
    // 3> 参数三:查询语句的长度:-1
    // 4> 参数四:句柄(游标对象)
//    sqlite3_prepare_v2(<#sqlite3 *db#>, <#const char *zSql#>, <#int nByte#>, <#sqlite3_stmt **ppStmt#>, <#const char **pzTail#>)
    sqlite3_stmt *stmt = nil;
    if (sqlite3_prepare_v2(self.db, SQL.UTF8String, -1, &stmt, nil) != SQLITE_OK) {
        NSLog(@"准备查询失败!");
        return NULL;
    }
    //准备成功,开始查询数据
    //定义一个存放数据字典的可变数组
    NSMutableArray *dictArrM = [[NSMutableArray alloc] init];
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        //一共获取表中所有列数(字段数)
        int columnCount = sqlite3_column_count(stmt);
        //定义存放字段数据的字典
        NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
        for (int i = 0; i < columnCount; i++) {
            // 取出i位置列的字段名,作为字典的键key
            const char *cKey = sqlite3_column_name(stmt, i);
            NSString *key = [NSString stringWithUTF8String:cKey];

            //取出i位置存储的值,作为字典的值value
            const char *cValue = (const char *)sqlite3_column_text(stmt, i);
            NSString *value = [NSString stringWithUTF8String:cValue];

            //将此行数据 中此字段中key和value包装成 字典
            [dict setObject:value forKey:key];
        }
        [dictArrM addObject:dict];
    }
    return dictArrM;
}

在自定义模型中有必要定义个工厂方法可将数据库对应表中所有数据取出,以模型数组的形式输出

+(NSArray *)allUserFromDB{
    //查询表中所有数据的SQL语句
    NSString *SQL = @"SELECT name,age,icon FROM 't_User'";
    //取出数据库用户表中所有数据
    NSArray *allUserDictArr = [[SQLiteManager shareInstance] querySQL:SQL];
    NSLog(@"%@",allUserDictArr);
    //将字典数组转化为模型数组
    NSMutableArray *modelArrM = [[NSMutableArray alloc] init];
    for (NSDictionary *dict in allUserDictArr) {
        [modelArrM addObject:[[User alloc] initWithDict:dict]];
    }
    return modelArrM;
}

当然,github已经上传源代码:https://github.com/qxuewei/Swift-test/tree/master/SQLite%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C-0C

作者:qiuxuewei2012 发表于2016/10/24 14:55:35 原文链接
阅读:85 评论:0 查看评论

Flutter基础—第一个Flutter实例

$
0
0

学习Flutter的第一个实例:

import 'package:flutter/material.dart';
void main() {
  runApp(new Center(child: new Text('你好,世界!')));
}

这里写图片描述

上面实例中只使用了runApp函数,runApp函数接受指定的控件(Widget),并使其作为控件树(widget tree)的根控件。控件(Widget)定义一个元素(Element)的配置,在Flutter框架的层次结构中处于核心层。本实例中,控件树(widget tree)包含两个控件,Center控件使其子控件处于中间位置,Text控件打印文本内容。runApp函数强制将根控件覆盖屏幕,这意味着文本“你好,世界!”将显示在屏幕中心。

在写应用程序时,经常会使用StatelessWidget和StatefulWidget编写新控件,两者的差别在于你是否要管理控件的状态。一个控件的主要任务是实现build函数,定义控件中其他较低层次的控件。build函数将依次构建这些控件,直到底层渲染对象。

基本控件

  • Text:文本控件,在应用中创建各种样式的文本。

  • Row,Column:Flex控件,可以创建水平(Row)或垂直(Column)方向的布局,是基于Web的flexbox的布局模式设计的。

  • Stack:非线性布局(水平或垂直),控件可以堆叠在其他控件上,可以使用Positioned控件控制Stack相对顶部、右部、底部和左部的位置,是基于Web的absolute定位的布局模式。

  • Container:创建矩形的可视元素,可以用BoxDecoration来设计样式,比如背景、边框和阴影,Container也有边距、填充和大小限制,另外,还可以在三维空间利用矩阵进行变换。

结合基本控件与其他控件的实例:

import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});
  final Widget title;
  @override
  Widget build(BuildContext context) {
    return new Container(
      height: 56.0,
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: new BoxDecoration(backgroundColor: Colors.blue[500]),
      child: new Row(
        children: <Widget>[
          new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: '导航菜单',
            onPressed: null,
          ),
          new Flexible(
            child: title,
          ),
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: '搜索',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}
class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Material(
      child: new Column(
        children: <Widget>[
          new MyAppBar(
            title: new Text('实例标题', style: Typography.white.title),
          ),
          new Flexible(
            child: new Center(
              child: new Text('你好,世界!'),
            ),
          ),
        ],
      ),
    );
  }
}
void main() {
  runApp(new MaterialApp(
    title: '我的应用',
    home: new MyScaffold(),
  ));
}

这里写图片描述

执行前确认flutter.yaml文件内有“uses-material-design: true”,允许使用预定的Material icons。

name: myapp
uses-material-design: true

许多控件为了继承主题数据,需要MaterialApp才能正常显示,因此,运行一个MaterialApp应用。

MyAppBar控件创建了一个Container(容器),高度为56设备无关像素(device-independent pixels),内部左右填充8像素(pixels)。在容器内部,MyAppBar为子控件设置Row(水平)布局,中间的title控件被设置成Flexible,意味它可以使用剩余的所有空间,你可以设置多个Flexible子控件,并使用flex参数设置各自的可使用空间比例。

MyScaffold控件为子控件设置垂直布局,在垂直顶部放置一个MyAppBar的实例,将MyAppBar的Text控件作为标题使用,将控件作为参数传递给其他控件非常方便实用的,你可以创建通用的控件,以各种方式重复的使用。最后,MyScaffold使用Flexible,用一个中心文本来填充剩余的空间。

作者:hekaiyou 发表于2016/10/24 19:04:40 原文链接
阅读:17 评论:0 查看评论

Retrofit+Rxjava+okhttp 懒人方式使用一

$
0
0

Retrofit+Rxjava+okhttp 懒人方式使用一

背景

之前学习完Retrofit+Rxjava之后写了一篇关于封装的博客,发出后受到大家的关注以及使用,由于不断的完善之前的项目,所以决定把最新的项目封装过程讲解出来,供大家查看!
原博客地址:Rxjava+ReTrofit+okHttp深入浅出-终极封装

效果

这里写图片描述

懒人简单的使用方式

为什么称为懒人,因为你什么都不用做,直接按照一般案例写rx和retrofit的使用

  • 引入需要的包
    /*rx-android-java*/
    compile 'io.reactivex:rxjava:+'
    compile 'com.squareup.retrofit:adapter-rxjava:+'
    compile 'com.trello:rxlifecycle:+'
    compile 'com.trello:rxlifecycle-components:+'
    /*rotrofit*/
    compile 'com.squareup.retrofit2:retrofit:+'
    compile 'com.squareup.retrofit2:converter-gson:+'
    compile 'com.squareup.retrofit2:adapter-rxjava:+'
    compile 'com.google.code.gson:gson:+'
  • 创建一个service定义请求的接口
/**
 * service统一接口数据
 * Created by WZG on 2016/7/16.
 */
public interface HttpService {

    @retrofit.http.POST("AppFiftyToneGraph/videoLink")
    Call<RetrofitEntity> getAllVedio(@retrofit.http.Body boolean once_no);
}
  • 创建一个retrofit对象
  //手动创建一个OkHttpClient并设置超时时间
        okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);

        Retrofit retrofit = new Retrofit.Builder()
                .client(builder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(HttpManager.BASE_URL)
                .build();
  • http请求处理
//        加载框
        final ProgressDialog pd = new ProgressDialog(this);

        HttpService apiService = retrofit.create(HttpService.class);
        Observable<RetrofitEntity> observable = apiService.getAllVedioBy(true);
        observable.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        new Subscriber<RetrofitEntity>() {
                            @Override
                            public void onCompleted() {
                                if (pd != null && pd.isShowing()) {
                                    pd.dismiss();
                                }
                            }

                            @Override
                            public void onError(Throwable e) {
                                if (pd != null && pd.isShowing()) {
                                    pd.dismiss();
                                }
                            }

                            @Override
                            public void onNext(RetrofitEntity retrofitEntity) {
                                tvMsg.setText("无封装:\n" + retrofitEntity.getData().toString());
                            }

                            @Override
                            public void onStart() {
                                super.onStart();
                                pd.show();
                            }
                        }

                );

传送门-源码地址

作者:u014610664 发表于2016/10/24 9:43:50 原文链接
阅读:6249 评论:0 查看评论

Frame Animation

$
0
0

简介

Frame Animation, 逐帧动画,通过定义一系列的Drawable对象来实现动画效果,可以用来作为视图的背景。
Frame Animation在代码中体现为AnimationDrawable对象,可以通过xml文件快创建,放在在/res/drawable/目录下,设置为视图背景后,调用start()方法即可执行逐帧动画。

XML文件

Tags:
< animation-list > 作为父节点,代表Animation Drawable
< item >作为子节点,代表逐帧动画内容,一张一张图片

Attributes:

属性 含义
android:oneshot=”false true”
android:variablePadding=”false true”
android:visible=”false true”
android:drawable=”@drawable/xxxxx” item图片资源
android:duration=”xxxxx” drawable播放时间,单位ms

Res:
/res/drawable/{folder}

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/selected"
    android:oneshot="true"
    android:variablePadding="false"
    android:visible="true">

    <item android:drawable="@drawable/ic_action_add" android:duration="500"/>
    <item android:drawable="@drawable/ic_action_anchor" android:duration="500"/>
    <item android:drawable="@drawable/ic_action_alarm" android:duration="500"/>
    <item android:drawable="@drawable/ic_action_amazon" android:duration="500"/>
    <item android:drawable="@drawable/ic_action_ac" android:duration="500"/>

</animation-list>

Coding

使用XML资源

imageView.setBackgroundResource(R.drawable.frame_anim);//设置背景
Drawable bgDrawable = imageView.getBackground();//获取背景
if(bgDrawable instanceof AnimationDrawable) {
    ((AnimationDrawable) bgDrawable).start();//如果为AnimationDrawable则执行动画
}

纯代码实现

***
imageView.setBackground(createAnimationDrawable());//设置背景
Drawable bg = imageView.getBackground();
if(bg instanceof AnimationDrawable) {
    ((AnimationDrawable) bg).start();//开始动画
}

***
private AnimationDrawable createAnimationDrawable() {

    AnimationDrawable animationDrawable = new AnimationDrawable();
    animationDrawable.addFrame(getResources().getDrawable(R.drawable.ic_action_add), 500);
    animationDrawable.addFrame(getResources().getDrawable(R.drawable.ic_action_anchor), 500);
    animationDrawable.addFrame(getResources().getDrawable(R.drawable.ic_action_alarm), 500);
    animationDrawable.addFrame(getResources().getDrawable(R.drawable.ic_action_amazon), 500);
    animationDrawable.addFrame(getResources().getDrawable(R.drawable.ic_action_ac), 500);
    animationDrawable.setOneShot(false);
    animationDrawable.setVisible(true,true);
    return animationDrawable;
}

效果图

这里写图片描述

作者:poorkick 发表于2016/10/24 11:25:05 原文链接
阅读:62 评论:0 查看评论

教你一招最屌的阅读开源项目的姿势

$
0
0

相信自从看了我的 GitHub 教程很多人开始爱上了 GitHub,有些人微博也不刷了,知乎也不刷了,改刷 GitHub 了,而且刷的不亦乐乎。这是好事,多接触多了解一些开源项目,对你之后的项目中的开发效率有很大提高。但是,我要告诉你们的是,你们不是正确的姿势在阅读开源项目,今天就教你们一招最屌的姿势。

首先说明下,这里的「阅读」是泛指,对于 Android 来说,除了阅读还有运行 demo 看下项目效果等。

来看下一般人的阅读开源项目的姿势是怎么样的:

  1. 找到项目地址
  2. 点击「download zip」
  3. 解压到一个目录
  4. 打开 Android Studio
  5. 导入项目
  6. Sync gradle
  7. 运行项目
  8. 选择运行的设备
  9. 删除下载的 zip 文件

对比下你是不是这样的?这效率也太低了。

一方面是步骤繁琐,当然可能有部分人用 Git 来代替下载,稍微减少了点步骤,另一方面 Android Stduio 很吃内存,光启动都要不少时间,更别提导入、编译、运行了,我们实际开发中是不得不用,但是如果只是为了阅读源码,看下效果就运行 Android Stduio 未免消耗太大,而且经常是很多时候可能同时阅读多个项目,那么同时打开多个 Android Stduio 窗口更是对电脑是个大考验。

来看下我的阅读源码的步骤:

  1. git clone 项目地址
  2. 用 sublime(或者 atom、vim)轻量级编辑器打开阅读源码
  3. 用 gradle 命令行运行查看效果

是不是步骤大幅简化?另外也不用打开 Android Studio 这内存机器了,而且用这种轻量级编辑器想打开多少项目就打开多少项目,第三部直接命令行编译、运行更酷。

很多人肯定会问第三步的具体流程,这个就是关键点了。下面直接列出这一步的一些关键点,就不详细解释了,之前看过我 Gradle 文章的应该懂。

  • 1.首先检查开源项目的 gradle 版本,buildTools 版本以及 compile sdk 版本,确保这三个ok就好办了;

  • 2.利用项目内置的 gradle wrapper 来进行编译打包:

./gradlew clean
./gradlew assembleDebug

这两步就可以编译、打包,然后自己手动安装;

  • 3.编译打包、安装其实可以合并:

    ./gradlew clean
    ./gradlew installDebug

这两步就直接安装到你设备上了,都不需要手动安装,是不是更方便快捷了?

那有人问了,有没有一步下载、编译、打包、运行就可以搞定的?卧槽,你真是懒到家了,但是我要告诉你还真有!

GitHub 上有一个项目叫 dryrun 就可以满足你的要求,翻译过来我把它叫做「干跑」,这翻译够直白吧!

只要安装这个工具,直接执行一个命令:

$ dryrun git@github.com:cesarferreira/android-helloworld.git

上面那个 Android 的 demo 就可以直接安装到你的设备上了,是不是狂拽酷炫吊炸天?

但是本质上跟我自己的步骤一致,只不过它通过 ruby 脚本把它合并起来了。

值得注意的是:

  1. dryrun 是一个 gem,它是基于 ruby 的,如果对 ruby 不了解,本地没安装过 ruby 折腾起来挺费事的,它的安装很简单:

    gem install ruby

  2. 如果你想要运行的 Android 项目在 GitHub 上目录里 gradle 版本,buildTools 版本以及 compile sdk 版本跟你本地不一致,那么会运行失败;

  3. 它的实用价值没有那么高,就是用来装逼的,适合我这种又懂 Ruby 又懂 Android 同时又喜欢装逼的人;

  4. 有兴趣的不妨折腾下,不感兴趣的你该学会我的第二种方式,很实用,效率很高,你值得拥有!

嗯,就这样,装逼完毕!

本文原创发布于微信公众号 AndroidDeveloper,id:googdev,欢迎关注获取更多原创干货!

作者:googdev 发表于2016/10/24 14:54:02 原文链接
阅读:556 评论:2 查看评论

混合开发的大趋势之一React Native之页面跳转

$
0
0

转载请注明出处:王亟亟的大牛之路

最近事情有点多,没有长时间地连贯学习,文章也停了一个多礼拜,愧疚,有时间还是继续学习,继续写!

还是先安利:https://github.com/ddwhan0123/Useful-Open-Source-Android (最近还是保持日更,除非忙的起飞活着出去玩不然都是更的,不信你看)

这里写图片描述


废话不多,贴下运行效果

这里写图片描述

登陆前

这里写图片描述

登录成功后

这里写图片描述

部分代码借鉴:https://github.com/SpikeKing/WclNavigator


rn的页面跳转都是交由Navigator来处理,我们看下文档了解这个常用的组件Navigator

Navigator 实质上是调用的Native的任务栈通过一系列路由做推送跳转等逻辑的,所以调的还是源生内容。

他有非常多实用的回调函数,注入renderScene configureScene 等等等

Navigator正常运行需要以下几个步骤

1.初始化路由 —>initialRoute
2.配置跳转动画 —>configureScene
3.渲染场景 —>renderScene

代码是最好的注解,我们直接边看代码边解释,先是index.android.js

为了让逻辑更清晰我们把之前登录的代码 放到了login.android.js
index页面专心做”配置”

import React,{Component} from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Navigator,
  TouchableOpacity
} from 'react-native';
import Button from 'react-native-button'
import Login from './pages/login.android';

export default class WjjPro extends Component {
  /**
   * 使用动态页面加载
   * @param route 路由
   * @param navigator 导航器
   * @returns {XML} 页面
   */
  renderScene(route, navigator) {
    return <route.component navigator={navigator}  {...route.passProps} />;
  }

  /**
   * 配置场景动画
   * @param route 路由
   * @param routeStack 路由栈
   * @returns {*} 动画
   */
  configureScene(route, routeStack) {
    if (route.type == 'Bottom') {
      return Navigator.SceneConfigs.FloatFromBottom; // 底部弹出
    }
    return Navigator.SceneConfigs.PushFromRight; // 右侧弹出
  }

  render() {
    return (
      <Navigator
        style={{flex:1}}
        initialRoute={{component: Login}}
        configureScene={this.configureScene}
        renderScene={this.renderScene}/>
    );
  }
}

const styles = StyleSheet.create({
});

AppRegistry.registerComponent('WjjPro', () => WjjPro);

因为我们首页实质上不做展示而是直接跳转到login页面,所以我们先配置Navigator,初始化各个函数,然后推给Login这个我们在开始就定义的”组件”,这部分如何实现跳转的可以看知识传送门,写得很详细,我没必要再画蛇添足了

ndex其实就是做了一堆配置然后就传递给login了,但是他做了一个很重要的行为,构造了Navigator属性,然后后续的页面进行传递


登录页面

登录页面和上一个例子里的代码没什么区别,主要差异就是再判断表单之后进行跳转页面,代码如下

name是我们我们要跳转页面传给下一个页面的参数

它可以在 this.props.name得到我们login页面传递过去的值

type是我们跳转的动画效果,对应的找Navigator的configureScene方法

文件头也要申明我们下一个被跳转的组件

import Main from './main.android';
    _jump(name, type = 'Normal') {
        this.props.navigator.push({
          component: Main,
          passProps: {
            name: name
          },
          type: type
        })
      }

push类似于 我们平时的startActivity的行为,API介绍可以看http://facebook.github.io/react-native/docs/navigator.html


登陆成功了那就跳到了我们的首页

import React, {Component,Navigator} from 'react';
import {AppRegistry, View, StyleSheet, Text,} from 'react-native';

export default class Main extends Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '',
        }
    }

    componentDidMount() {
        this.setState({name: this.props.name});
    }
    render() {
        return (
            <View>
                <Text>获得的参数: value = {this.state.name}</Text>
            </View>
        );
    }
}

AppRegistry.registerComponent('Main', () => Main);

我们在首页的componentDidMount方法里把传来的参数给Main页面的name字段赋值,然后呈现在Text上就行了

总结:

这是个很强大的控件,可用于页面跳转。

要是用只需要
1.构造Navigator
2.配置Navigator结合业务逻辑
3.调用push,jump等方法进行跳转

源码地址:https://github.com/ddwhan0123/ReactNativeDemo

作者:ddwhan0123 发表于2016/10/24 18:16:45 原文链接
阅读:128 评论:0 查看评论

Android开发之神奇的Fading Edge,让你的View更有层次感!

$
0
0

最近在研究Android Framework层源码,发现我们对源码的理解应该建立在对API的理解之上,如果有一些API你没用过,那么即使你在源码中见到这个东西都不知道是干嘛的,更谈不上理解了。一直以来我都很想把View的绘制方法draw详细的走一遍,但是这里涉及到的细节问题是在是太多了,因此,今天我们还是先来看看Fading Edge,为draw方法详解继续打基础。

在这篇博客之前,我已经陆续推出了五篇关于View绘制的文章,相信这五篇博客对你理解本篇博客会有帮助。

1.View绘制详解,从LayoutInflater谈起

2.View绘制详解(二),从setContentView谈起

3.View绘制详解(三),扒一扒View的测量过程

4.View绘制详解(四),谝一谝layout过程

5.View绘制详解(五),draw方法细节详解之View的滚动/滑动问题


为什么要说Fading Edge?这个在我们平时开发中并不怎么起眼的API其实是View绘制过程中重要的一步,绕不过的坎!可是要理解源码,我们就得先知道这个Fading Edge到底是干什么的?从字面来理解,这个是实现边缘渐变效果的,OK,那我们先来看看效果图:

我这里以一个滚动的TexView为例,小伙伴们看到了这种带阴影的效果。Fading Edge也可以使用在ListView上,效果类似。OK,那我们就先来看看上面这种效果怎么实现。

一般来说,要想实现TextView的滚动效果,很多小伙伴首先想到的方法可能都是ScrollView中嵌套一个TextView,但是实际上,不这样做我们依然可以实现TextView的滚动效果,我们先来看看上文中蓝色TextView的滚动效果如何实现:

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="128dp"
        android:background="@color/colorPrimary"
        android:fadingEdgeLength="50dp"
        android:requiresFadingEdge="vertical"
        android:text="@string/content"/>

fadingEdgeLength表示阴影部分的高度,requiresFadingEdge表示阴影的方向。方向可以是水平的,也可以是垂直的。水平效果我一会再说。当然,如果我们只是在xml文件中这样写,TextView还是无法滚动起来,还需要在Activity中添加如下一行代码,TextView才能滚动起来,如下:

tv.setMovementMethod(new ScrollingMovementMethod());

当然,我们也可以借助ScrollView让TextView滚动起来,这个时候只需把Fading Edge相关的属性添加到ScrollView中即可:

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="128dp"
        android:layout_marginTop="20dp"
        android:fadingEdgeLength="50dp"
        android:requiresFadingEdge="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="72dp"
            android:scrollbars="vertical"
            android:text="@string/content"/>

    </ScrollView>

相关属性的含义不用我再说了吧!

OK,接下来我们再来看一个水平方向上的效果:

越到末尾的时候字的颜色慢慢变淡直到消失。OK,那么这个效果要怎么实现呢?看下面:

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="none"
        android:background="@color/colorPrimary"
        android:fadingEdgeLength="200dp"
        android:requiresFadingEdge="horizontal"
        android:text="@string/content"/>

OK,除此之外,最后我们再来看看Fading Edge在ListView中的使用吧。先来看看效果图:


其实在Fading Edge使用时候,ListView中这种效果算是最容易实现的一种了,我们来看看代码:

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fadingEdge="vertical"
        android:fadingEdgeLength="200dp"
        android:requiresFadingEdge="vertical"></ListView>

数据绑定就是普通的绑定方式,不赘述。


OK,这就是我们View中Fading Edge的一个简单使用,先记录下来,为后面全面分析draw方法打下基础。



以上。



作者:u012702547 发表于2016/10/24 20:19:21 原文链接
阅读:117 评论:0 查看评论

Android简易实战教程--第三十五话《音乐播放》

$
0
0

已经好几天不更新博客了,今天轻松一点模拟个简单的“音乐播放器”。1分钟看完~

整个简单布局,加几个控制按钮:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/play"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Play" />

    <Button
        android:id="@+id/pause"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Pause" />

    <Button
        android:id="@+id/stop"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Stop" />

</LinearLayout>

主活动代码也是soeasy:

package com.example.playaudiotest;

import java.io.File;

import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener {

	private Button play;

	private Button pause;

	private Button stop;

	private MediaPlayer mediaPlayer = new MediaPlayer();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		play = (Button) findViewById(R.id.play);
		pause = (Button) findViewById(R.id.pause);
		stop = (Button) findViewById(R.id.stop);
		play.setOnClickListener(this);
		pause.setOnClickListener(this);
		stop.setOnClickListener(this);
		initMediaPlayer();
	}

	private void initMediaPlayer() {
		try {
			File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");
			//设置播放路径,资源
			mediaPlayer.setDataSource(file.getPath());
			//播放前的准备工作
			mediaPlayer.prepare();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.play:
			if (!mediaPlayer.isPlaying()) {
				//开始播放
				mediaPlayer.start();
			}
			break;
		case R.id.pause:
			if (mediaPlayer.isPlaying()) {
				//暂停播放
				mediaPlayer.pause();
			}
			break;
		case R.id.stop:
			if (mediaPlayer.isPlaying()) {
				//Resets the MediaPlayer to its uninitialized state. After calling this method, you will have to initialize it again by setting the data source and calling prepare(). 
				//重置,如果调用此方法,必须人为的再次调用设置资源、准备方法
				mediaPlayer.reset();
				initMediaPlayer();
			}
			break;
		default:
			break;
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (mediaPlayer != null) {
			mediaPlayer.stop();
			mediaPlayer.release();
		}
	}

}

需要重点关注的是mediaPlayer.reset();方法。看文档说明:

Resets the MediaPlayer to its uninitialized state. After calling this method, you will have to initialize it again by setting the data source and calling prepare().
大致意思是:重置,如果调用此方法,必须人为的再次调用设置资源、准备方法

既然调用了就在此调用initMediaPlayer();

运行,开始听音乐吧~~

对于MediaPlayer这个类的一些认识,可以参考Android初级教程的一篇博客:MediaPlayer的生命周期

作者:qq_32059827 发表于2016/10/24 23:02:51 原文链接
阅读:282 评论:0 查看评论

swift 可选型的使用

$
0
0

一、基本用法
可选性是swift提供的一个特殊类型,它为我们编写程序提供便利的条件
swift是强类型语言,当我们需要使用一个变量,既可以为String,也可以为nil时,这时候就需要我们使用可选型。

// 使用特殊值作为“无”可能产生歧义
//var errorCode = 404
//errorCode = 0


// 使用可选型
var errorCode:Int? = 404
print( errorCode )

errorCode = nil
print( errorCode )


var color: UIColor? = nil


// Int? 和 Int 不是一种类型
let imInt = 405
errorCode = imInt
//imInt = errorCode


// 必须显示地声明可选型的类型
//var imOptional = nil
var imOptional: String? = nil

二、可选型解包使用

var errorCode:String? = "404"
print( errorCode )

// 可选型不可以直接使用
//"The errorCode is" + errorCode


// 强制解包
"The errorCode is " + errorCode!

// 强制解包是危险的
errorCode = nil
//"The errorCode is" + errorCode!

// 判断不是nil
if errorCode != nil{
    "The errorCode is " + errorCode!
}
else{
    "No error"
}


// if let 解包
if let unwrappedErrorCode = errorCode{
    "The errorCode is " + unwrappedErrorCode
}
else{
    "No error"
}

// 可以使用相同的变量名
if let errorCode = errorCode{
    "The errorCode is " + errorCode
}
else{
    "No error"
}


// 使用if-let同时解包多个变量
var errorMessage:String? = "Not found"

if let errorCode = errorCode{
    if let errorMessage = errorMessage{
        "The errorCode is " + errorCode + "\nThe errorMessage is " + errorMessage
    }
}

三、多层解包(**swift3.0有变化)

//多层解包(swift 2.0使用)
if let errorCode = errorCode , errorMessage = errorMessage{
    "The errorCode is " + errorCode + "\nThe errorMessage is " + errorMessage
}

//以上多层解包方法在swift3.0中应写为

if let errorCode = errorCode , let errorMessage = errorMessage{
    "The errorCode is " + errorCode + "\nThe errorMessage is " + errorMessage
}


// where(swift 2.0使用)
if let errorCode = errorCode , errorMessage = errorMessage where errorCode == "404"{
    print("Page not found")
}

//在swift3.0中应写为  
// where
if let errorCode = errorCode , let errorMessage = errorMessage , errorCode == "404"{
    print("Page not found")
}

四、Optional chaining的使用
可选型?这种写法叫做Optional chaining
这种方法可以进行尝试解包,使代码简洁

var errorMessage: String? = "Not Found"
if let errorMessage = errorMessage{
    errorMessage.uppercased()
}

//使用 Optional chaining
errorMessage?.uppercased()

var uppercaseErrorMessage = errorMessage?.uppercased()
//uppercaseErrorMessage为可选型

if let errorMessage = errorMessage?.uppercased(){
    errorMessage
}

// Optional chaining 也可以使用!
let uppercaseErrorMessage2 = errorMessage!.uppercased()
//此时 uppercaseErrorMessage2 为String类型。此时为强制解包,容易出现错误

五、Nil-Coalescing 的使用

// Nil-Coalescing
var errorMessage: String? = nil

let message: String
if let errorMessage = errorMessage{
    message = errorMessage
}
else{
    message = "No error"
}

// 使用三目运算符
let message2 = errorMessage == nil ? "No error" : errorMessage!
// 注意: 此时使用errorMessage需要强制解包.以保证message2是一个String,而非String?
// 三目运算符?:的实质就是一个if else


// 使用 ??
let message3 = errorMessage ?? "No error"
// 注意: 此时使用errorMessage不需要强制解包. 
// ??符号已经保证了访问到errorMessage时, errorMessage不是nil
// Swift将为我们自动解包, 保证message2永远是一个String, 而不是String?
//这种用法叫做 Nil-Coalescing
作者:u012903898 发表于2016/10/25 0:34:34 原文链接
阅读:218 评论:0 查看评论

【Android自定义View实战】之获取验证码倒计时按钮

$
0
0

在Android开发中,我们不可避免的会做到注册功能,而现在的注册大多数都是用手机去注册的,那么注册的时候都会要求用获取验证码的方式去验证,我们接下来就来实战一下自定义获取验证码倒计时按钮:

1.先看效果图

这里写图片描述

2.我们涉及到的变量

  • 倒计时时长,可设置

    /**
     * 倒计时时长,默认倒计时时间60秒;
     */
    private long length = 60 * 1000;
  • 在点击按钮之前按钮所显示的文字

    /**
     * 在点击按钮之前按钮所显示的文字,默认是获取验证码
     */
    private String beforeText = "获取验证码";
  • 在开始倒计时之后那个秒数数字之后所要显示的字

    /**
     * 在开始倒计时之后那个秒数数字之后所要显示的字,默认是秒
     */
    private String afterText = "秒";

3.利用什么倒计时Timer

在Java中定时器任务的执行需要两个基本的类:
java.util.Timer;
java.util.TimerTask;
要运行一个定时任务,最基本的步骤如下:
1、建立一个要执行的任务TimerTask。
2、创建一个Timer实例,通过Timer提供的schedule()方法,将 TimerTask加入到定时器Timer中,同时设置执行的规则即可。
当程序执行了Timer初始化代码后,Timer定时任务就会按照设置去执行。
Timer中的schedule()方法是有多种重载格式的,以适应不同的情况。该方法的格式如下:
void schedule(TimerTask task, Date time)
安排在指定的时间执行指定的任务。
void schedule(TimerTask task, Date firstTime, long period)
安排指定的任务在指定的时间开始进行重复的固定延迟执行。
void schedule(TimerTask task, long delay)
安排在指定延迟后执行指定的任务。
void schedule(TimerTask task, long delay, long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
Timer是线程安全的,此类可扩展到大量同时安排的任务(存在数千个都没有问题)。其所有构造方法都启动计时器线程。可以调用cancel() 终止此计时器,丢弃所有当前已安排的任务。purge()从此计时器的任务队列中移除所有已取消的任务。此类不提供实时保证:它使用 Object.wait(long) 方法来安排任务。
TimerTask是一个抽象类,由 Timer 安排为一次执行或重复执行的任务。它有一个抽象方法run()—-计时器任务要执行的操作。因此,每个具体的任务类都必须继承TimerTask类,并且重写run()方法。另外它还有两个非抽象的方法:
boolean cancel()
取消此计时器任务。
long scheduledExecutionTime()
返回此任务最近实际 执行的安排 执行时间。

4.代码

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;

import java.util.Timer;
import java.util.TimerTask;

/**
 * 自定义倒计时按钮
 * <p/>
 *
 * @author Dylan
 *         [佛祖保佑 永无BUG]
 *         Created by Dylan on 2016/10/5 0005.
 */
public class CountdownButton extends Button implements View.OnClickListener {
    /**
     * 倒计时时长,默认倒计时时间60秒;
     */
    private long length = 60 * 1000;
    /**
     * 开始执行计时的类,可以在每秒实行间隔任务
     */
    private Timer timer;
    /**
     * 每秒时间到了之后所执行的任务
     */
    private TimerTask timerTask;
    /**
     * 在点击按钮之前按钮所显示的文字,默认是获取验证码
     */
    private String beforeText = "获取验证码";
    /**
     * 在开始倒计时之后那个秒数数字之后所要显示的字,默认是秒
     */
    private String afterText = "秒";
    /**
     * 按钮点击事件
     */
    private OnClickListener onClickListener;


    public CountdownButton(Context context) {
        super(context);
        initView();
    }

    public CountdownButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public CountdownButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    /**
     * 初始化操作
     */
    private void initView() {
        if (!TextUtils.isEmpty(getText())) {
            beforeText = getText().toString().trim();
        }
        this.setText(beforeText);
        setOnClickListener(this);
    }

    /**
     * 初始化时间
     */
    private void initTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {
            @Override
            public void run() {
                handler.sendEmptyMessage(1);
            }
        };
    }

    /**
     * 设置倒计时时长
     *
     * @param length 默认毫秒
     */
    public void setLength(long length) {
        this.length = length;
    }

    /**
     * 设置未点击时显示的文字
     *
     * @param beforeText
     */
    public void setBeforeText(String beforeText) {
        this.beforeText = beforeText;
    }

    /**
     * 设置未点击后显示的文字
     *
     * @param beforeText
     */
    public void setAfterText(String beforeText) {
        this.afterText = afterText;
    }

    /**
     * 设置监听按钮点击事件
     *
     * @param onclickListener
     */
    @Override
    public void setOnClickListener(OnClickListener onclickListener) {
        if (onclickListener instanceof CountdownButton) {
            super.setOnClickListener(onclickListener);
        } else {
            this.onClickListener = onclickListener;
        }
    }

    /**
     * 点击按钮后的操作
     *
     * @param v
     */
    @Override
    public void onClick(View v) {
        start();
        if (onClickListener != null) {
            onClickListener.onClick(v);
        }
    }

    /**
     * 开始倒计时
     */
    public void start() {
        initTimer();
        this.setText(length / 1000 + afterText);
        this.setEnabled(false);
        timer.schedule(timerTask, 0, 1000);
    }


    /**
     * 更新显示的文本
     */
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            CountdownButton.this.setText(length / 1000 + afterText);
            length -= 1000;
            if (length < 0) {
                CountdownButton.this.setEnabled(true);
                CountdownButton.this.setText(beforeText);
                clearTimer();
                length = 60 * 1000;
            }
        }
    };

    /**
     * 清除倒计时
     */
    private void clearTimer() {
        if (timerTask != null) {
            timerTask.cancel();
            timerTask = null;
        }
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    /**
     * 记得一定要在activity或者fragment消亡的时候清除倒计时,
     * 因为如果倒计时没有完的话子线程还在跑,
     * 这样的话就会引起内存溢出
     */
    @Override
    protected void onDetachedFromWindow() {
        clearTimer();
        super.onDetachedFromWindow();
    }
}

5.用法,超级简单

 <com.bm.ykzx.view.CountdownButton
                android:id="@+id/cdb_register_timer"
                android:layout_width="120dp"
                android:textAllCaps="false"
                android:layout_height="match_parent"
                android:background="@android:color/transparent"
                android:gravity="center"
                android:paddingLeft="3dp"
                android:paddingRight="3dp"
                android:text="获取验证码"
                android:textColor="#1179c4"
                android:textSize="@dimen/txt14sp" />
作者:u010785585 发表于2016/10/25 8:54:07 原文链接
阅读:202 评论:2 查看评论

Unity UGUI图文混排(六) -- 超链接

$
0
0

图文混排更新到超链接这儿,好像也差不多了,不过就在最后一点,博主也表现得相当不专业,直接整合了山中双木林同学提供的超链接的解决方案,博主甚至没来得及细看就直接复制了,但感觉还是挺好用的。

博主已经将超链接的功能直接整合到了之前的InlineText和InlineSpriteText的两个脚本中


1.定义超链接的正则表达式和事件监听

#region 超链接
    /// <summary>
    /// 超链接信息列表
    /// </summary>
    private readonly List<HrefInfo> m_HrefInfos = new List<HrefInfo>();

    /// <summary>
    /// 文本构造器
    /// </summary>
    private static readonly StringBuilder s_TextBuilder = new StringBuilder();

    /// <summary>
    /// 超链接正则
    /// </summary>
    private static readonly Regex s_HrefRegex =
        new Regex(@"<a href=([^>\n\s]+)>(.*?)(</a>)", RegexOptions.Singleline);

    [System.Serializable]
    public class HrefClickEvent : UnityEvent<string> { }

    [SerializeField]
    private HrefClickEvent m_OnHrefClick = new HrefClickEvent();

    /// <summary>
    /// 超链接点击事件
    /// </summary>
    public HrefClickEvent onHrefClick
    {
        get { return m_OnHrefClick; }
        set { m_OnHrefClick = value; }
    }

    /// <summary>
    /// 获取超链接解析后的最后输出文本
    /// </summary>
    /// <returns></returns>
    protected string GetOutputText()
    {
        s_TextBuilder.Length = 0;
        m_HrefInfos.Clear();
        var indexText = 0;
        foreach (Match match in s_HrefRegex.Matches(text))
        {
            s_TextBuilder.Append(text.Substring(indexText, match.Index - indexText));
            s_TextBuilder.Append("<color=blue>");  // 超链接颜色

            var group = match.Groups[1];
            var hrefInfo = new HrefInfo
            {
                startIndex = s_TextBuilder.Length * 4, // 超链接里的文本起始顶点索引
                endIndex = (s_TextBuilder.Length + match.Groups[2].Length - 1) * 4 + 3,
                name = group.Value
            };
            m_HrefInfos.Add(hrefInfo);

            s_TextBuilder.Append(match.Groups[2].Value);
            s_TextBuilder.Append("</color>");
            indexText = match.Index + match.Length;
        }
        s_TextBuilder.Append(text.Substring(indexText, text.Length - indexText));
        return s_TextBuilder.ToString();
    }

    /// <summary>
    /// 点击事件检测是否点击到超链接文本
    /// </summary>
    /// <param name="eventData"></param>
    public void OnPointerClick(PointerEventData eventData)
    {
        Vector2 lp;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            rectTransform, eventData.position, eventData.pressEventCamera, out lp);

        foreach (var hrefInfo in m_HrefInfos)
        {
            var boxes = hrefInfo.boxes;
            for (var i = 0; i < boxes.Count; ++i)
            {
                if (boxes[i].Contains(lp))
                {
                    m_OnHrefClick.Invoke(hrefInfo.name);
                    return;
                }
            }
        }
    }

    /// <summary>
    /// 超链接信息类
    /// </summary>
    private class HrefInfo
    {
        public int startIndex;

        public int endIndex;

        public string name;

        public readonly List<Rect> boxes = new List<Rect>();
    }
    #endregion
2.在文本绘制完成后处理超链接的包围盒

 #region 处理超链接的包围盒
        // 处理超链接包围框
        UIVertex vert = new UIVertex();
        foreach (var hrefInfo in m_HrefInfos)
        {
            hrefInfo.boxes.Clear();
            if (hrefInfo.startIndex >= toFill.currentVertCount)
            {
                continue;
            }

            // 将超链接里面的文本顶点索引坐标加入到包围框
            toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex);
            var pos = vert.position;
            var bounds = new Bounds(pos, Vector3.zero);
            for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++)
            {
                if (i >= toFill.currentVertCount)
                {
                    break;
                }

                toFill.PopulateUIVertex(ref vert, i);
                pos = vert.position;
                if (pos.x < bounds.min.x) // 换行重新添加包围框
                {
                    hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
                    bounds = new Bounds(pos, Vector3.zero);
                }
                else
                {
                    bounds.Encapsulate(pos); // 扩展包围框
                }
            }
            hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
        }
        #endregion
3.看一下文中中超链接的输入规则


4.简单写了一个测试脚本,用来监听点击事件

using UnityEngine;
using System.Collections;

public class TestClickInlineText : MonoBehaviour {
    private InlieText _text;
    
    void Awake()
    {
        _text = GetComponent<InlieText>();
    }

    void OnEnable()
    {
        _text.onHrefClick.AddListener(OnHrefClick);
    }

    void OnDisable()
    {
        _text.onHrefClick.RemoveListener(OnHrefClick);
    }

    private void OnHrefClick(string hrefName)
    {
        Debug.Log("点击了 " + hrefName);
      //  Application.OpenURL("www.baidu.com");
    }
}
5.运行截图:


6.更新速度实在太慢,为了早点完结图文混排,这里的功能是复制的,有什么疑问的话,可以再讨论,这里的功能也就更新得差不多了,最后再给一个最新的源码链接,短时间没有特殊的功能或者bug,就不打算再更新了

工程源码链接:https://github.com/coding2233/TextInlineSprite


作者:qq992817263 发表于2016/10/25 11:14:44 原文链接
阅读:141 评论:0 查看评论

从此刻开始拥抱 Swift 3.0

$
0
0

API is shortened
++ / –
Swift 2.2 的时候对 a ++ 或者 a – 已经给出warning 提示将在Swift3.0中将废除。

不嫌麻烦可以重载运算符来实现

postfix operator ++

postfix func ++(a: Int) -> Int {
    return a + 1
}

a++ // 11

更彻底点可以考虑使用引用,加上 inout

函数中的 var
为了修改函数参数,添加 var 关键字

func setName(var name: String) {

}

现已删除

func setName(name: String, na: String) {
    name += na


}

无法再修改参数, inout可以

Repeat

var i = 0

repeat {

    print("Hello")
    i = i + 1
} while( i < 10 )

替代 do while 感觉主要是避免与 do catch冲突

每个函数的参数标签一致:


func getPerson(name: String, age: Int) {
    //....
}

Swift 2.2 中:

getPerson("Vic", age: 10)

Swift 3 中:

getPerson(name: "Vic", age: 10)

除非手动添加 _ 否则参数名会保持一致性原则

一直在改变的Selector
Swift 3:

button.addTarget(Responder(), action: #selector(Responder.tap), for: .touchUpInside)

将确定selector的过程从运行时移到了编译时。

KVO

#KeyPath()//形式类似于Selector

只适用于非Objc对象。

class Object : NSObject {

    @objc var name = ""
    var age = 0
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let sss = p.value(forKey: #keyPath(Object.age))

@discardableResult
如果未使用函数返回值,在没有 @discardableResult的前提下,会报错

fileprivate: 让私有变得更具体
之前 Swift 2.2 及之前版本,private 并不是严格的私有,在同一文件,依然可以访问到.

为了严格化, Swift3.0 中 设置了 fileprivate 来取代之前 private 的作用。 而新的 private 则是严格的私有权限。

fileprivate func sss() {

}

private func aaa() {

}

private 变得更加严格。

class Access {

    private var a = 0
    private func getA() -> Int {
        return a;
    }

    fileprivate func desc() {
        print("\(a) time")
    }

}

let p = Access()
// 报错,提示getA() 是私有成员
p.getA()

上述p只能访问desc()

Swift现有 访问级别
公开(public)
内部(internal)
文件外私有(fileprivate)
私有(private)

inout 变了

之前的写法

func saaa(inout a: Int) {

}

现在作为一种修饰符而存在

func saaa(a: inout Int) {

}

**关联类型声明由 typealias 变为 associatedtype
先说下 Swift的关联类型**

Swift支持泛型,class struct enum 都支持泛型, 而协议呢?

Swift是一门面向协议的语言。

protocol hahah<T> {

}

Protocols do not allow generic parameters, use associated types instead

泛型协议

protocol Haha {
    associatedtype Element

    var s: Int { get set }

    func getElement() -> Element

}

在 Swift 3 中, 不再允许 typealias 来构造泛型。

告别C经典循环方式

for (int i = 0; i < 10; i ++)

Swift Loop Style

// i is not mutable
for i in 1...3 {
   // i += 1   
    print(i)

}

// i is mutable
for var i in 1...10 {   
    i += 1
    print(i)
}

// 枚举
(1...3).forEach{i in
    print(i)
}

告别柯里化函数

Xcode 7.3 的 Swift 2.2 版本中已有提示柯里化函数不再使用。

func add(a: Int)(b: Int) -> Int {
    return a + b
}

当然函数式编程依然完美解决

func add(_ a: Int) -> (Int)-> Int {
    return { b in return a + b }
}
var s = add(10)(20)
作者:u012903898 发表于2016/10/25 11:22:52 原文链接
阅读:183 评论:0 查看评论

渐变色圆角Button

$
0
0

简介

总结下之前看的自定义View的内容,结合一个简单的例子,阐述下基本用法和大致的使用流程,这个例子比较简单,更复杂的自定义View,随着自己的学习,后面再慢慢添加。作为一个Android开发者,这部分应该是不可或缺的。

自定义属性

位置:res/values/attrs.xml
格式:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="name_of_style">
        <attr name="name_of_attr" format="reference|string|color|boolean|dimension|enum|flag|float|fraction|integer"/>
    </declare-styleable>
</resources>
format 意义
reference 参考某一资源ID, 如R.drawable.xxx
string 字符串
color 颜色
boolean 布尔值
dimension 尺寸值
enum 枚举值,例如这里写图片描述
flag 位或运算,例如:这里写图片描述
float 浮点数
fraction 百分数,例如pivotX,pivotY这一类属性
integer 整数

获取自定义属性

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.name_of_style);

Color mColor = typedArray.getColor(R.styleable.name_of_style_name_of_attr, Color.BLUE);//其他的属性获取类似
typedArray.recycle();//记得回收


名字:
R.styleable.{name_of_style}
R.styleable.{name_of_style}_{name_of_attr}

举个栗子

举个栗子,实现一个背景为渐变色的圆角按钮,圆角半径,开始颜色,中心颜色,结束颜色,渐变方向用户可自定义。
attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CornerButton">
        <attr name="corner_radius" format="dimension" />
        <attr name="background_start_color" format="color" />
        <attr name="background_center_color" format="color" />
        <attr name="background_end_color" format="color" />
        <attr name="backgrouund_gradient_orientation">
            <enum name="TOP_BOTTOM" value="0" />
            <enum name="TR_BL" value="1" />
            <enum name="RIGHT_LEFT" value="2" />
            <enum name="BR_TL" value="3" />
            <enum name="BOTTOM_TOP" value="4" />
            <enum name="BL_TR" value="5" />
            <enum name="LEFT_RIGHT" value="6" />
            <enum name="TL_BR" value="7" />
        </attr>
    </declare-styleable>
</resources>

CornerButton

public class CornerButton extends Button {

    private GradientDrawable mBg;
    private float mRandius;
    private int mStartColor;
    private int mCenterColor;
    private int mEndColor;
    private int mOrientation;

    public CornerButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CornerButton);
        mRandius = typedArray.getDimension(R.styleable.CornerButton_corner_radius, 10);
        mStartColor = typedArray.getColor(R.styleable.CornerButton_background_start_color, Color.BLUE);
        mCenterColor = typedArray.getColor(R.styleable.CornerButton_background_center_color, Color.GREEN);
        mEndColor = typedArray.getColor(R.styleable.CornerButton_background_end_color, Color.BLACK);
        mOrientation = typedArray.getInt(R.styleable.CornerButton_backgrouund_gradient_orientation, 0);
        typedArray.recycle();

        int[] colors = {mStartColor, mCenterColor, mEndColor};
        mBg = new GradientDrawable();
        mBg.setCornerRadius(mRandius);
        mBg.setOrientation(Orientation.TR_BL);
        mBg.setColors(colors);
        switch (mOrientation) {
            case 0:
                mBg.setOrientation(Orientation.TOP_BOTTOM);
                break;
            case 1:
                mBg.setOrientation(Orientation.TR_BL);
                break;
            case 2:
                mBg.setOrientation(Orientation.RIGHT_LEFT);
                break;
            case 3:
                mBg.setOrientation(Orientation.BR_TL);
                break;
            case 4:
                mBg.setOrientation(Orientation.BOTTOM_TOP);
                break;
            case 5:
                mBg.setOrientation(Orientation.BL_TR);
                break;
            case 6:
                mBg.setOrientation(Orientation.LEFT_RIGHT);
                break;
            case 7:
                mBg.setOrientation(Orientation.TL_BR);
                break;
        }
        this.setBackground(mBg);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:yidong="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="gs.com.customview.MainActivity">

    <gs.com.customview.CornerButton
        android:id="@+id/cb_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        yidong:background_start_color="#CCFF0000"
        yidong:background_center_color="#CCAADD00"
        yidong:background_end_color="#CC00EEFF"
        yidong:corner_radius="100dp"
        yidong:backgrouund_gradient_orientation="BOTTOM_TOP"
        />
</RelativeLayout>

xmlns:yidong=”http://schemas.android.com/apk/res-auto”

yidong:corner_radius=”100dp”
XML命名空间和属性的Tag对应。

实际效果
这里写图片描述 这里写图片描述

作者:poorkick 发表于2016/10/25 14:43:59 原文链接
阅读:117 评论:0 查看评论

iOS-SQLite在项目中实际使用(Swift3)

$
0
0

转载请注明出处:http://blog.csdn.net/qxuewei/article/details/52913622

这里写图片描述

创建数据库管理类SQLiteManager

设置类方法创建单例对象
Swift 中单例对象可以直接输出定义的自身类内的成员变量.

 class SQLiteManager: NSObject {
    //MARK: - 创建类的静态实例变量即为单例对象 let-是线程安全的
    static let instance = SQLiteManager()
    //对外提供创建单例对象的接口
    class func shareInstance() -> SQLiteManager {
        return instance
    }
}

操作数据库

首先需要在项目中导入libsqlite3.tbd框架
这里写图片描述

SQLite3 框架是一套 C 语言的框架,直接在swift中使用首先需要添加桥接文件
在swift中使用OC/C++/C等文件都需要这个桥接文件.
这里写图片描述

创建完桥接头文件还需要将桥接头文件配置到项目中
这里写图片描述

然后就可以在swift项目中愉快的使用C语言的各种接口方法了.

打开数据库

开启数据库

class SQLiteManager: NSObject {
    //MARK: - 创建类的静态实例变量即为单例对象 let-是线程安全的
    static let instance = SQLiteManager()
    //对外提供创建单例对象的接口
    class func shareInstance() -> SQLiteManager {
        return instance
    }
    //MARK: - 数据库操作
    //定义数据库变量
    var db : OpaquePointer? = nil
    //打开数据库
    func openDB() -> Bool {
        //数据库文件路径
        let dicumentPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last
        let DBPath = (dicumentPath! as NSString).appendingPathComponent("appDB.sqlite")
        let cDBPath = DBPath.cString(using: String.Encoding.utf8)
        //打开数据库
        //第一个参数:数据库文件路径  第二个参数:数据库对象
//        sqlite3_open(<#T##filename: UnsafePointer<Int8>!##UnsafePointer<Int8>!#>, <#T##ppDb: UnsafeMutablePointer<OpaquePointer?>!##UnsafeMutablePointer<OpaquePointer?>!#>)
        if sqlite3_open(cDBPath, &db) != SQLITE_OK {
            print("数据库打开失败")
        }
        return creatTable();
    }
    //创建表
    func creatTable() -> Bool {
        //建表的SQL语句
        let creatUserTable = "CREATE TABLE IF NOT EXISTS 't_User' ( 'ID' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,'name' TEXT,'age' INTEGER,'icon' TEXT);"
        let creatCarTable = "CREATE TABLE IF NOT EXISTS 't_Car' ('ID' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,'type' TEXT,'output' REAL,'master' TEXT);"
        //执行SQL语句-创建表 依然,项目中一般不会只有一个表
        return creatTableExecSQL(SQL_ARR: [creatUserTable,creatCarTable])
    }
    //执行建表SQL语句
    func creatTableExecSQL(SQL_ARR : [String]) -> Bool {
        for item in SQL_ARR {
            if execSQL(SQL: item) == false {
                return false
            }
        }
        return true
    }
    //执行SQL语句
    func execSQL(SQL : String) -> Bool {
        // 1.将sql语句转成c语言字符串
        let cSQL = SQL.cString(using: String.Encoding.utf8)
        //错误信息
        let errmsg : UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
        if sqlite3_exec(db, cSQL, nil, nil, errmsg) == SQLITE_OK {
            return true
        }else{
            print("SQL 语句执行出错 -> 错误信息: 一般是SQL语句写错了 \(errmsg)")
            return false
        }
    }
}

一般在app启动开启数据库并建表

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        //一般在程序启动时开始数据库并建表
        if SQLiteManager.shareInstance().openDB() {
            print("开启数据库成功!")
        }
        return true
    }

数据库内SQL操作

//执行SQL语句
    func execSQL(SQL : String) -> Bool {
        // 1.将sql语句转成c语言字符串
        let cSQL = SQL.cString(using: String.Encoding.utf8)
        //错误信息
        let errmsg : UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
        if sqlite3_exec(db, cSQL, nil, nil, errmsg) == SQLITE_OK {
            return true
        }else{
            print("SQL 语句执行出错 -> 错误信息: 一般是SQL语句写错了 \(errmsg)")
            return false
        }
    }

项目中的Model自定义对象可以自定义一个将自身插入数据库的方法

//将自身插入数据库接口
    func insertSelfToDB() -> Bool {
        //插入SQL语句
        let insertSQL = "INSERT INTO 't_User' (name,age,icon) VALUES ('\(name!)',\(age),'\(icon!)');"
        if SQLiteManager.shareInstance().execSQL(SQL: insertSQL) {
            print("插入数据成功")
            return true
        }else{
            return false
        }
    }

模拟插入若干用户

for i in 1...7 {
            let name = "name_\(i)"
            let age = arc4random_uniform(18).hashValue + i
            let icon = "http://qiuxuewei.com/icon\(i).png"
            let user : User = User(name: name, age: age, icon: icon)
            if user.insertSelfToDB() {
                print("第 \(i) 个用户插入成功!")
            }
        }
    }

如果需要更新数据库对应表中数据,直接调用SQL执行方法即可实现

//修改头像封装方法
func changeIcon(newIcon : String) {
        //包装修改头像的SQL语句
        let changeIconSQL = "UPDATE 't_User' SET icon='\(newIcon)' WHERE name='name_6'"
        if SQLiteManager.shareInstance().execSQL(SQL: changeIconSQL) {
            print("name_6 头像修改成功!")
        }
    }

查询数据库中对应表中所有数据

在SQLiteManager中封装一个类方法,可以直接传入SQL语句输出数据库中存储的数据
其中swift3中对 UnsafePointer 转 String 做了改动
let s = String(cString: yourCharPointer)
参考:http://stackoverflow.com/questions/39533320/swift-3-convert-a-null-terminated-unsafepointeruint8-to-a-string

//查询数据库中数据
    func queryDBData(querySQL : String) -> [[String : AnyObject]]? {
        //定义游标对象
        var stmt : OpaquePointer? = nil

        //将需要查询的SQL语句转化为C语言
        if querySQL.lengthOfBytes(using: String.Encoding.utf8) > 0 {
            let cQuerySQL = (querySQL.cString(using: String.Encoding.utf8))!
            //进行查询前准备操作
            // 1> 参数一:数据库对象
            // 2> 参数二:查询语句
            // 3> 参数三:查询语句的长度:-1
            // 4> 参数四:句柄(游标对象)

            if sqlite3_prepare_v2(db, cQuerySQL, -1, &stmt, nil) == SQLITE_OK {
                //准备好之后进行解析
                var queryDataArrM = [[String : AnyObject]]()
                while sqlite3_step(stmt) == SQLITE_ROW {
                    //1.获取 解析到的列(字段个数)
                    let columnCount = sqlite3_column_count(stmt)
                    //2.遍历某行数据
                    var dict = [String : AnyObject]()
                    for i in 0..<columnCount {
                        // 取出i位置列的字段名,作为字典的键key
                        let cKey = sqlite3_column_name(stmt, i)
                        let key : String = String(validatingUTF8: cKey!)!

                        //取出i位置存储的值,作为字典的值value
                        let cValue = sqlite3_column_text(stmt, i)
                        let value =  String(cString:cValue!)
                        dict[key] = value as AnyObject
                    }
                    queryDataArrM.append(dict)
                }
                return queryDataArrM
            }
        }
        return nil
    }

在自定义模型中有必要定义个工厂方法可将数据库对应表中所有数据取出,以模型数组的形式输出

 //MARK: - 类方法
    //将本对象在数据库内所有数据全部输出
    class func allUserFromDB() -> [User]? {
        let querySQL = "SELECT name,age,icon FROM 't_User'"
        //取出数据库中用户表所有数据
        let allUserDictArr = SQLiteManager.shareInstance().queryDBData(querySQL: querySQL)
        print(allUserDictArr)

        //将字典数组转化为模型数组
        if let tempUserDictM = allUserDictArr {
            // 判断数组如果有值,则遍历,并且转成模型对象,放入另外一个数组中
            var userModelArrM = [User]()
            for dict in tempUserDictM {
                userModelArrM.append(User(dict: dict))
            }
            return userModelArrM
        }
        return nil
    }

当然,github已经上传源代码:https://github.com/qxuewei/Swift-test/tree/master/SQLite%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C-Swift

作者:qiuxuewei2012 发表于2016/10/25 14:50:21 原文链接
阅读:88 评论:0 查看评论

【Android】Broadcast广播机制总结

$
0
0

原文链接: Android总结篇系列:Android广播机制

1. Android广播机制概述

Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器)。广播作为Android组件间的通信方式,可以使用的场景如下:

  1. 同一app内部的同一组件内的消息通信(单个或多个线程之间);

  2. 同一app内部的不同组件之间的消息通信(单个进程);

  3. 同一app具有多个进程的不同组件之间的消息通信;

  4. 不同app之间的组件之间消息通信;

  5. Android系统在特定情况下与App之间的消息通信。

从实现原理看上,Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型。因此,从实现的角度来看,Android中的广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。具体实现流程要点粗略概括如下:

  1. 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;

  2. 广播发送者通过binder机制向AMS发送广播;

  3. AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;

  4. 消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

对于不同的广播类型,以及不同的BroadcastReceiver注册方式,具体实现上会有不同。但总体流程大致如上。

由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。

在上文说列举的广播机制具体可以使用的场景中,现分析实际应用中的适用性

  • 第一种情形:同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉,会显太“重”;

  • 第二种情形:同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些教复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松解耦。可以参见文件《Android各组件/控件间通信利器之EventBus》

  • 第三、四、五情形:由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜。下面主要针对Android广播中的具体知识点进行总结。

2. BroadcastReceiver

自定义BroadcastReceiver

自定义广播接收器需要继承基类BroadcastReceivre,并实现抽象方法onReceive(context, intent)方法。广播接收器接收到相应广播后,会自动回到onReceive(..)方法。默认情况下,广播接收器也是运行在UI线程,因此,onReceive方法中不能执行太耗时的操作。否则将因此ANR。一般情况下,根据实际业务需求,onReceive方法中都会涉及到与其他组件之间的交互,如发送Notification、启动service等。
下面代码片段是一个简单的广播接收器的自定义:

public class MyBroadcastReceiver extends BroadcastReceiver {
    public static final String TAG = "MyBroadcastReceiver";
    public static int m = 1;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.w(TAG, "intent:" + intent);
        String name = intent.getStringExtra("name");
        Log.w(TAG, "name:" + name + " m=" + m);
        m++;

        Bundle bundle = intent.getExtras();

    }
}

BroadcastReceiver注册类型

BroadcastReceiver总体上可以分为两种注册类型:静态注册和动态注册。

1). 静态注册:

直接在AndroidManifest.xml文件中进行注册。规则如下:

<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>

其中,需要注意的属性

  • android:exported — 此broadcastReceiver能否接收其他App的发出的广播,这个属性默认值有点意思,其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。(同样的,activity/service中的此属性默认值一样遵循此规则)同时,需要注意的是,这个值的设定是以application或者application user id为界的,而非进程为界(一个应用中可能含有多个进程);

  • android:name — 此broadcastReceiver类名;

  • android:permission — 如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;

  • android:process — broadcastReceiver运行所处的进程。默认为app的进程。可以指定独立的进程(Android四大基本组件都可以通过此属性指定自己的独立进程)

常见的注册形式有:

<receiver android:name=".MyBroadcastReceiver" >
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

其中,intent-filter由于指定此广播接收器将用于接收特定的广播类型。本示例中给出的是用于接收网络状态改变或开启启动时系统自身所发出的广播。当此App首次启动时,系统会自动实例化MyBroadcastReceiver,并注册到系统中。

之前常说:静态注册的广播接收器即使app已经退出,主要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立。具体分析详见本文后面部分。

2). 动态注册:

动态注册时,无须在AndroidManifest中注册组件。直接在代码中通过调用Context的registerReceiver函数,可以在程序中动态注册BroadcastReceiver。registerReceiver的定义形式如下:

registerReceiver(BroadcastReceiver receiver, IntentFilter filter)

registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)

典型的写法示例如下:

public class MainActivity extends Activity {
    public static final String BROADCAST_ACTION = "com.example.corn";
    private BroadcastReceiver mBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_ACTION);
        registerReceiver(mBroadcastReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mBroadcastReceiver);
    }

}

注:Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister。因此,上例在onDestroy()回到中需要unregisterReceiver(mBroadcastReceiver)。

当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中。当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。

3. 广播发送及广播类型

经常说”发送广播“和”接收“,表面上看广播作为Android广播机制中的实体,实际上这一实体本身是并不是以所谓的”广播“对象存在的,而是以”意图“(Intent)去表示。定义广播的定义过程,实际就是相应广播”意图“的定义过程,然后通过广播发送者将此”意图“发送出去。被相应的BroadcastReceiver接收后将会回调onReceive()函数。

下段代码片段显示的是一个普通广播的定义过程,并发送出去。其中setAction(..)对应于BroadcastReceiver中的intentFilter中的action。

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);

根据广播的发送方式,可以将其分为以下几种类型:

  1. Normal Broadcast:普通广播

  2. System Broadcast : 系统广播

  3. Ordered broadcast:有序广播

  4. Sticky Broadcast:粘性广播(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)

  5. Local Broadcast:App应用内广播

下面分别总结下各种类型的发送方式及其特点。

1). Normal Broadcast:普通广播

此处将普通广播界定为:开发者自己定义的intent,以context.sendBroadcast_”AsUser”(intent, …)形式。具体可以使用的方法有:
sendBroadcast(intent)/sendBroadcast(intent, receiverPermission)/sendBroadcastAsUser(intent, userHandler)/sendBroadcastAsUser(intent, userHandler,receiverPermission)。
普通广播会被注册了的相应的感兴趣(intent-filter匹配)接收,且顺序是无序的。如果发送广播时有相应的权限要求,BroadCastReceiver如果想要接收此广播,也需要有相应的权限。

2). System Broadcast: 系统广播

Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,有系统自动发出。

3). Ordered broadcast:有序广播

有序广播的有序广播中的“有序”是针对广播接收者而言的,指的是发送出去的广播被BroadcastReceiver按照先后循序接收。有序广播的定义过程与普通广播无异,只是其的主要发送方式变为:sendOrderedBroadcast(intent, receiverPermission, …)。

对于有序广播,其主要特点总结如下:

  • a. 多个具当前已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。

  • b. 先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。

4)Sticky Broadcast:粘性广播

在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated。既然已经deprecated,此处不再多做总结。

5)Local Broadcast:App应用内广播(此处的App应用以App应用进程为界)

由前文阐述可知,Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:

  1. 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;

  2. 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:

  1. 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;

  2. 在广播发送和接收时,都增加上相应的permission,用于权限验证;

  3. 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。

相比于全局广播,App应用内广播优势体现在:

  1. 安全性更高;

  2. 更加高效。

为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。

代码片段如下:

//registerReceiver(mBroadcastReceiver, intentFilter);
//注册应用内广播接收器
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//unregisterReceiver(mBroadcastReceiver);
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("name", "qqyumidi");
//sendBroadcast(intent);
//发送应用内广播
localBroadcastManager.sendBroadcast(intent);

4. 不同注册方式的广播接收器回调onReceive(context, intent)中的context具体类型

  • 1). 对于静态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是ReceiverRestrictedContext;

  • 2). 对于全局广播的动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Activity Context;

  • 3). 对于通过LocalBroadcastManager动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Application Context。

注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。

5. 不同Android API版本中广播机制相关API重要变迁

1). Android5.0/API level 21开始粘滞广播和有序粘滞广播过期,以后不再建议使用;

2). ”静态注册的广播接收器即使app已经退出,主要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立“

Android 3.1开始系统在Intent与广播相关的flag增加了参数,分别是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。

  • FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)

  • FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包

主要原因如下:

自Android3.1开始,系统本身则增加了对所有app当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的app,同样无法接收到广播。

详情参加Android官方文档:http://developer.android.com/about/versions/android-3.1.html#launchcontrols

由此,对于系统广播,由于是系统内部直接发出,无法更改此intent flag值,因此,3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。

但是对于自定义的广播,可以通过复写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是重新新建的。

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);

注1:对于动态注册类型的BroadcastReceiver,由于此注册和取消注册实在其他组件(如Activity)中进行,因此,不受此改变影响。

注2:在3.1以前,相信不少app可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即时app已经退出,仍然能接收到,可以启动service等..),3.1后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将Service与App本身设置成不同的进程已经成为实现此类需求的可行替代方案。

作者:u010983881 发表于2016/10/25 16:06:35 原文链接
阅读:86 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>