Long Luo's Life Notes

每一天都是奇迹

By Long Luo

最近几天学习了下网页开发,通过学习,完成了第一个Web前端开发项目:天气应用

数据源是Yahoo! Weather

使用了jQuery库开发,虽然现在啥都不会,也就到处copy。

Created by Long Luo at 2014-09-07 11:36:20 @Shenzhen, China.

By Long Luo

一、重构前的一些缺点

  1. 频道显示在 VideoListActivity 实现的,代码结构不够清晰;
  2. 频道页面需要根据不同频道目前存在矩阵式显示和列表式显示 2 种方式,矩阵式显示嵌入在 ListView 中实现的,造成加载时绘制矩阵式页面需要更长的时间;
  3. 后续频道页面会参考第三方视频应用,不同频道会有不同的展示方式,而目前功能可扩展性不够好;

二、重构方案

重构方案:

  1. 将频道页面独立出来,新建一个 ChannelListActivity ,作为所有频道页面的 Activity ,负责所有频道界面的绘制,便于后续扩展;
  2. 针对不同频道,具有不同的布局方式,比如对于需要矩阵式显示的频道,使用矩阵式布局;
  3. 矩阵式布局,替换原有的 ListViewGridView 显示,提高加载速度;

三、具体实现

技术实现方案如下所示:

  1. 新建 ChannelListActivity ,在 VideoListActivity 中用进入频道页的接口替换原有的接口,将相关处理在 ChannelListActivity 中实现;
  2. 根据传入的频道 ID 不同,使用 setContentView() 加载不同的布局方式,实现不同频道不同布局显示;
  3. 新建 ChannelGridAdapter ,替换原有的 ChannelListAdapterChannelGridListAdapter 方式,提高加载速度;

四、数据对比

我们通过 TraceView 工具比较前后 2 种方案的区别:

4.1 启动时间:

比较两者启动时间,可以看出重构之后的方案是有大概20ms的提升。

Startup Time

4.2 TraceView对比:

通过 DDMS TraceView 工具,我们再比较下两者性能上的区别:

4.2.1 重构之前:

重构之前的在绘制页面时需要绘制 ListView ,然后在每一个 List 中在绘制 GridView ,在 TraceView 获取到的数据如下:

ChannelListAdapter
ChannelGridListAdapter

从以上可以分析,在 ChannelListAdaptergetView() 就占据了1.0%的 Incl Cpu Time , GridViewgetView() 占据了6.9%的 Incl Cpu Time ,合计占用了7.9%的 CPU Time。

4.2.2 重构之后:

重构之后,我们仅需要在 ChannelGridAdapter 中绘制 View,测量数据如下:

ChannelGridAdapter

从上图可以看出, getView() 占据的 CPU 时间仅为3.2%,效率大大提高。

文章修改历史

  • Created by Long Luo at 2014/7/1 15:45:01
  • Completed by Long Luo at 2014/7/2 14:42:29
  • Modified by Long Luo at 2018年9月27日22点40分 at Hangzhou, China.
  • 修改图片图床 2024.03.03 in Shenzhen.

By Long Luo

一、在线搜索

之前搜索页面的一些缺陷:

  1. 具体实现位于 VideoListActivity 中,一方面会造成 VideoListActivity 代码过于庞大臃肿,另外一方面不便于后续功能扩展,结构不清晰;
  2. 依赖了大量系统控件,不便于后续解耦界面定制
  3. 今后搜索界面会参考第三方视频应用的实现,之前不便于增加搜索记录,或搜索独立出来,用于搜索本地视频,甚至将此搜索移植应用于其他应用中;

1.1 在线搜索实现效果

在线搜索因为是和第三方合作,涉及到很多网络相关的操作,简单来说就是利用 Http 协议向相关接口发起一次网络请求,服务器如果返回了正确的响应,App 会解析服务器返回的内容,并展示出来。

1.1.1 热词界面

热词界面是当搜索文本框文字为空时会弹出热词界面,会展示最近一段时间内搜索频率很高的词语。一方面可以节省大家输入文字,另外一方面你也可以了解当前的一些热点。

当你点击列表中的某个热词时,就会发起一次以此为关键词的搜索。

热词显示

1.1.2 关联词

搜索文本输入框含有文字时,会获取当前输入文字,以此为关键词获取网络的一些联想词,可以点击此联想词发起一次搜索。

关联词显示

1.1.3 搜索结果分类浏览

发起一次搜索之后,如果得到了服务器的正确响应,而且确实有相关视频内容。那么我们会将搜索结果展示在手机页面上。

搜索到的结果可以分不同频道浏览,会根据具体内容进行动态变化,有的可能有十几个频道,有的也就一个频道。频道页面可以滑动浏览,也可以选择在顶部页面选中或者滑动。

分类浏览时,第一个展示的页面是搜索到的全部视频内容,之后的会根据结果动态变化。

如下图所示:

搜索结果

分频道浏览:

搜索结果分类

1.1.4 语音搜索

语音搜索图标只有当搜索框里文字为空才会出现,否则出现搜索图标

点击语音搜索图标将会启动 VoiceSearch 这个 apk ,然后你可以说话,如果被正确识别之后,会发起一次搜索,并将结果展示出来。

语音搜索

1.1.5 语音搜索结果

语音搜索结果
阅读全文 »

翻译 By Long Luo

原文链接:Android Audio: Play a WAV file on an AudioTrack

译者注: 1. 由于这是技术文章,所以有些词句使用原文,表达更准确。 2. 由于水平有效,有些地方可能翻译的不够准确,如有不当之处,敬请批评指正. 3. 针对某些语句,适当补充了上下文及更适合中文阅读,尽量做到信达雅。


如果你已经成功地了解了关于AudioTrack一些话题,那么你可能享受它带来的好处,例如低延迟(在STATIC(静态)模式),能够生成流式音频(在STREAM(流)模式)以及在播放之前,就能够访问和修改原始声音数据。

不过,现在的问题是如何从源获取数据。许多应用需要使用的AudioTrack并不能简单的生成PCM音频(一个例子,比如Ethereal Dialpad或者其他类似的App)。你可能需要从文件源去加载数据,例如WAVMP3文件。

不要期望使用MediaPlayer,去解码WAV文件和MP3音频。虽然MediaPlayer播放这些文件非常好,但是其播放逻辑完全在Native层,同时并没有为我们提供额外选项,允许我们使用其他解码器实现我们的目的。因此,我们必须从手动地从音频文件进行解码出PCM

在这篇文章中,将会讨论WAV格式文件。而在下一课中,我们将会更进一步,讨论如何从MP3文件读取音频。

背景知识: 一些数字音频术语

如果你的App不是专门为数字音频设计,那么在继续我们的讨论之前,你可能需要先了解一些基本的缩略语。别担心,都很简单,我们不需要对此做深入挖掘。

  • PCM(脉冲调制方式) - 实现一个物理音频信号变成数字化最简单方法。基本原理就是信号变成了一个数字阵列,而其中每个数字代表的是声音在特定的时间瞬间的电平也可以说是能量(振幅)。(如果这种解释在科学上可能不会很准确,那我就只能说声抱歉了)。信不信由你,你可以使用这种方法表示任何复杂的声音,而且回放出来也非常精准。在这里,我们将只会谈到线性PCM。在线性PCM中,其中阵列中的每个数字都是原始声音振幅的线性表示。在某些情况下,对数映射能够更好地表示原来的声音幅度比例情况 - 但是我们不会讨论那些情况。

  • Sampling rate(采样率):- 每秒你的数字声音有多少样本(声音幅度用数字表示)。样本越多,你能得到声音质量越好。目前在消费类音频系统目前使用的采样率通常是22050,44100和48000Hz/s。

  • 每个样品分辨率/采样大小/位 - 定义表示振幅数字的大小和格式。例如,如果您使用的是8位整数,你只能表达出256级的幅度,所以原来的物理波形将被简化为256个离散电平,与此同时,你将失去一些声音精度也可以说是质量。如果你使用16位,那么声音质量变得更好。事实上,大部分时间你可能会使用16位音频。其他选项包括24位,32位(这些都是Android现在不支持的),或是使用浮点数。

  • 声道 - 既可以是单声道,也可以是立体声(2个声道),或者更多声道(但是Android不支持)。如果你想要有立体声,你需要有立体声音频,就必须要在每个声道都需要有一个独立的PCM数组,相应的信息量也会翻倍。

上述定义也有助于你理解特定的格式和长度的音频缓冲区的数据量,以便提前预备缓冲区。也就是你需要一个缓冲区,以用于存储5秒长度以44100Hz采样率的立体声16-bit线性PCM数据。数据计算公式如下所示:

5 sec * 44100 samples per sec * 2 bytes per sample * 2 channels = 882,000 bytes

这一数额所需的内存可能会让初学者感到惊讶,因为当你往你的磁盘上存储的音频时,一个MP3文件,一个880KB的文件就可以容纳以相同的采样率和分辨率1分钟时长的音轨。这是为什么呢?因为先进的格式,比如MP3格式。因为我们大脑无法分辨识别出一些音频的内容,所以使用了很多复杂的方式在压缩的过程中去掉了这些内容。然而,大多数低等级的音频API,包括Android的AudioTrack只能接受线性PCM。这就是为什么如果我们不能把整个样品都放在内存中,我们需要将要处理的数据流,循环缓冲区和其他聪明的方式来使用音频API。

希望这样的解释并没有让你产生困惑,现在让我们继续来实际做一些与Android上的数字音频有关的工作吧!

WAV文件格式

我们的目标是用一个InputStream,由其从一个WAV文件加载PCM数据,来提供原始字节数据。然后我们就可以将原始的PCM数据直接推送到使用已经正确的配置好了的AudioTrack.write,通过使用AudioTrack.write()这个API。

WAV文件包含一个文件头和具体数据会。我们需要读取文件头以知道诸如采样速率,分辨率等信息。另外,我们通过文件头,也可以知道此格式是否支持。WAV可以封装成多种格式,我们无法全部支持。也许,只是合理的采样率,分辨率和通道的线性PCM格式。

WAV格式的细节在互联网上都可以找到,你仅仅需要在Google上搜索下。但是,遗憾的是,我并没有搜索到一个很好的Java库来读取WAV文件,而且可以移植到Android下。因此,我自己写了一些简单的代码。

下面这个方法就是如何读取一个WAV文件的头部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static final String RIFF_HEADER = "RIFF";
private static final String WAVE_HEADER = "WAVE";
private static final String FMT_HEADER = "fmt ";
private static final String DATA_HEADER = "data";

private static final int HEADER_SIZE = 44;

private static final String CHARSET = "ASCII";

/* ... */

public static WavInfo readHeader(InputStream wavStream) throws IOException,
DecoderException {

ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN);

wavStream.read(buffer.array(), buffer.arrayOffset(), buffer.capacity());

buffer.rewind();
buffer.position(buffer.position() + 20);
int format = buffer.getShort();
checkFormat(format == 1, "Unsupported encoding: " + format); // 1 means
// Linear
// PCM
int channels = buffer.getShort();
checkFormat(channels == 1 || channels == 2, "Unsupported channels: "
+ channels);
int rate = buffer.getInt();
checkFormat(rate <= 48000 && rate >= 11025, "Unsupported rate: " + rate);
buffer.position(buffer.position() + 6);
int bits = buffer.getShort();
checkFormat(bits == 16, "Unsupported bits: " + bits);
int dataSize = 0;
while (buffer.getInt() != 0x61746164) { // "data" marker
Log.d(TAG, "Skipping non-data chunk");
int size = buffer.getInt();
wavStream.skip(size);

buffer.rewind();
wavStream.read(buffer.array(), buffer.arrayOffset(), 8);
buffer.rewind();
}
dataSize = buffer.getInt();
checkFormat(dataSize > 0, "wrong datasize: " + dataSize);

return new WavInfo(new FormatSpec(rate, channels == 2), dataSize);
}

上面的代码中,缺少的部分应该是显而易见的。正如你所看到的,仅仅支持16位,但在你可以修改代码以支持8位(AudioTrack不支持任何其他分辨率的)。

下面这个方法,则是用来读取文件剩余的部分 - 音频数据

1
2
3
4
5
6
public static byte[] readWavPcm(WavInfo info, InputStream stream)
throws IOException {
byte[] data = new byte[info.getDataSize()];
stream.read(data, 0, data.length);
return data;
}

我们读取的WavInfo结构体,包含采样率,分辨率和声道数已经足够让我们去播放我们读取的音频了。

如果我们不需要将全部音频数据一次性放入内存中,我们可以使用一个InputStream,一点一点地读取。

阅读全文 »

By Long Luo

2014.06.03 Apple举行了万众瞩目的WWDC2014,发布了OS X Yosimite和iOS8,最近几天看了一部分Keynote,从网上搜集了一部分资料,将这次WWDC14的一些新功能点汇总出来,同时加入了自己的一些想法,探讨下码农群体应该注意哪些趋势。

Continuity & Seamlessly

总体来说:

系统功能更加完善,更加人性化,更加开放,与其他iOS、Mac设备整合度扩大,并且预示了未来与可穿戴设备、智能家居的紧密结合的趋势,预计Health和Home两大产业将发生重大变革,将有一系列的新App和硬件改变我们的生活。

WWDC的报道会分别讲解Mac OS Yosemite和iOS 8的各项功能。可是整场看下来,觉得这两个系统的更新都有一个指针:统一。爷就是关键的2个词,Continuity & Seamlessly

Mac OS Yosemite的扁平化和透明统一了Mac OS和iOS的视觉风格。 Mac OS也追加了iOS上特有的功能,比如打电话、IM。

两个系统在视觉和功能上统一的同时,通过 iCloud 的升级,实现了数据同步。 「视觉」,「功能」和「数据」统一了之后, Handoff 实现了Mac OS和iOS工作的无缝切换。

最终,所有“苹果设备”都将无缝的融合到一起。随时拿起iPad,打开Macbook或者掏出 iPhone,你都能看到同样的视觉风格,得到一致的数据,使用同样的功能。

iOS8 新功能点

HealthKit

健康管理软件

这是一个私人健康数据平台,它可以整合其他第三方健康应用数据,也就说可以在HealthKit中统一浏览其他应用监测的数据。同时,它也将与梅奥诊所和其他医疗机构合作,允许医疗机构接受或传输你的数据。

HomeKit

把iPhone变成智能家居中心控制器
可以实现远程遥控智能家居设备,如智能门锁、灯泡或是监控摄像头。操作非常简单,只需对siri说:“我要上床睡觉了”,灯光就会变暗、门就会锁上。

Mail

1. 支持直接从邮件中添加日历事件。
2. 在邮件中将会引入全新的邮件手势,下滑消息可触发新动作。  
3. 图像编辑和草图分享,能够直接通过浏览器来提供强悍的图片编辑能力,并快速进行邮件分享。
4. 支持群组通信。群发功能——你可以直接在会话中添加或移除对象。                                                                                   
5. 轻松切换和快速访问联系人。                                                                   
6. 当你在手机上写了一半邮件之后,还可以在就近的电脑上继续完成  
7. 在当前消息中打开另一封邮件,然后再切换回来(工作进度依然保存着)。                                                                                                           
8. 可以在通知中直接为Facebook上的消息点赞!  
                            

iMessage

变身为一个没有朋友圈和公众号的微信

1. 支持群组通信。你可以直接在会话中添加或移除对象。                      
2. 支持多达14个国家和地区的语言,加快打字速度
3. 发送语音消息的功能很有趣。 
4. 图片支持以“相册”形式快速查看。
5. 支持语音信息,与对话的人共享位置。                                               
6. 支持发短视频                                                                                        
7. 在群聊短信设置中有“不要打扰”的相关选项

优化输入法

1. iOS8中在键盘中能够实现的第三方输入法,QuickType可智能预测。

当用户输入完当前单词之后,QuickType会提供3个智能预测的单词供用户选择。当然,如果你不喜欢这种“输入建议”,也可以轻松移除。

2. 在键盘中还引入了全新的Tap to Talk功能

新的Tap to Talk功能,语音输入短信,用户能够录制一段音频或视频并将其发送给收件人。

阅读全文 »
0%