老司机种菜


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 公益404

  • 搜索

音视频参数之Profile

发表于 2017-09-12 | 分类于 media

前两天发现公司产品 触发 有导入从微信等下载的外部小视频时失败的情况, 触发 导入视频时使用开源库media-for-mobile对视频进行重新编码.media-for-mobile使用android系统接口android.media.MediaExtractor对mp4文件解复用,使用android硬件编解码接口android.media.MediaCodec对视频解码-处理-编码操作,最后通过Android系统APIandroid.media.MediaMuxer将视频写成文件.

反馈的两个有问题视频,一个视频导入时报BufferOverFlowException错误,另一个视频不报错但是导入时进度一直为0%.

经分析出问题的两个视频的Video profile为High,普通视频为Baseline,报BufferOverFlowException错误的视频的Audio profile为HE-AAC,普通视频的Audio profile为LC. 详细了解了一下H264 与AAC profile:

H264各种profile

作为行业标准,H.264编码体系定义了4种不同的Profile(类):Baseline(基线类),Main(主要类), Extended(扩展类)和High Profile(高端类)(它们各自下分成许多个层):

  1. Baseline Profile: 提供I/P帧,仅支持progressive(逐行扫描)和CAVLC;
  2. Extended Profile: 提供I/P/B/SP/SI帧,仅支持progressive(逐行扫描)和CAVLC;
  3. Main Profile: 提供I/P/B帧,支持progressive(逐行扫描)和interlaced(隔行扫描),提供CAVLC或CABAC;
  4. High Profile: (也就是FRExt)在Main Profile基础上新增:8x8 intra prediction(8x8 帧内预测), custom quant(自定义量化), lossless video coding(无损视频编码), 更多的yuv格式(4:4:4…);

H.264在高清中具有最小体积。在同等图像质量下,采用H.264技术压缩后的数据量只有MPEG2的1/8,MPEG4的1/3,相对Xvid、Divx等属于MPEG4编码而言,其体积优势明显,在互联网上,H.264资源处在爆发趋势。而High Profile是H.264解码中的高端类型,拥有最完善的支持程度、最优秀的特性,可以说是高清视频编码中的劳斯莱斯,只有征服了这个优秀的编码,MP4才能将H.264完全掌控,也才能充分享受到高清视频带来的视觉震撼,意义非凡。

针对当前高清是视频会议行业的主流发展趋势,而目前高清普及的主要阻力之一就是带宽的限制。而High Profile H.264技术在同等视频质量的情况下可节省50%的带宽,为客户节省大量的网络带宽成本。据业内专家介绍,此次H.264 High Profile的推出是视频技术的一个巨大进步,其意义不亚于2003年从H.263向H.264过渡的价值。它将为目前正在推广的高清视频通信应用扫清了令人头疼的网络带宽障碍,不仅如此,这些变化对于包括CIF、标清等品质的视频通信应用也同样适用。

鉴于High Profile H.264对于视频会议行业的非凡影响,目前已有国内外厂商尝鲜,宣布其旗下产品全面支持该项技术,包括宝利通、华平、华腾网讯。从这一趋势来看,未来视频会议产品全面支持High Profile H.264也经成为不可逆转的潮流,而高清普及也在技术层面更近了一步。

参考: H264 各种profile

AAC各种profile

AAC历史

如果说目前H.264是视频CODEC的实际霸主,那么AAC就是音频CODEC的女王。主流的音视频格式都是H.264搭配AAC,无论是非实时的媒体文件还是实时的媒体流。 Advanced Audio Coding (AAC) 是一个有损压缩的音频编码集(其实新的编码工具也支持无损)。其设计目标是替代原有MP3编码标准,在与MP3在相似的码率下希望质量优于MP3。这一目标已达到并且由ISO和IEC标准组织标准化在MPEG-2和MPEG-4中。

AAC已被广泛支持并应用到各种设备和系统中 YouTube, iPhone, iPod, iPad, Nintendo DSi, Nintendo 3DS, iTunes,DivX Plus Web Player and PlayStation 3. It is supported on PlayStation Vita,Wii (with the Photo Channel 1.1 update installed), Sony Walkman MP3 series andlater, Android and BlackBerry等等。

  1. 1997年,AAC第一次出现在标准MPEG-2 Part 7,(ISO/IEC 13818-7:1997)。和视频CODEC标准类似,AAC在MPEG-2 Part 7就有三个profiles他们分别是。
    • Low-Complexity profile(AAC-LC / LC-AAC)
    • Main profile (AAC Main)
    • Scalable Sampling Rateprofile (AAC-SSR) 从此可知AAC-LC出现最早,所以AAC-LC的应用最广泛,兼容性最好。
  2. 1999年, AAC从原有标准升级并且合入标准MPEG-4Part 3(ISO/IEC14496-3:1999) 这次升级一个重要变化是引入 Audio Object Types(AOT) 并且把AOT概念合并到profiles中。这时profile也变成4个。
    • Main (which includes most of the MPEG-4 Audio Object Types)
    • Scalable (AAC LC, AAC LTP, CELP, HVXC, TwinVQ, Wavetable Synthesis,TTSI),
    • Speech (CELP, HVXC, TTSI)
    • Low Rate Synthesis (Wavetable Synthesis, TTSI)合成语音。
  3. 2000年,版本更新到2,MPEG-4 AudioVersion 2 (ISO/IEC 14496-3:1999/Amd 1:2000),标准定义了一种新的AOT, 低时延AAC,the low delay AAC(AAC-LD)。
  4. 2001年,标准化High-Efficiency Advanced Audio Coding (HE-AAC) ISO/IEC 14496-3:2001。
  5. 2003年,标准化HE-AAC v2 Profile (AAC LC with SBR and Parametric Stereo) ISO/IEC14496-3:2005
  6. 目前AAC的标准化的版本是 ISO/IEC 14496-3:2009。

从上面标准化历史可知,AAC不在一单纯的一个编码器了,而是一个庞大的音频编码工具集合。

AOT(Audio Object Types)

AOT就是MPEG-4 Audio Object Types的缩写。能力集协商时用的是AOT ID。 也正是由于AAC的AOT繁多,导致识别使用AAC的用户很困扰。 AAC-LC 可认为是AOT为2的AAC。 下表是AOT的对应表。

object type ID Audio Object Type z description
0 Null
1 AAC Main 1999 contains AACLC
2 AAC LC (Low Complexity) 1999 used in the “AAC Profile”.MPEG-4 AAC LC Audio Object Type is based on the MPEG-2 Part7 Low Complexity profile(LC)combined with Perceptual Noise Subsitution(PNS)(define in MPEG-4 part 3 Subpart 4)
3 AAC SSR (Scalable Sample Rate) 1999 Mpeg4 AAC SSR Audio Object Type is based on the MPEG-2 Parg 7 Scalable Sampling Rate profile(SSR)combined with Perceptual Noise Subsitution(PNS)(defined in MPEG-4 Parg 3 Subpart 4)
4 AAC LTP (Long Term Prediction) 1999 contains AAC LC
5 SBR (Spectral Band Replication) 2003 used with AAC LC in the “High Efficiency AAC Profile”(HE-AAC v1)
6 AAC Scalable 1999
7 TwinVQ 1999 audio coding at very low bitrates
8 CELP (Code Excited Linear Prediction) 1999 speech coding
9 HXVC (Harmonic Vector eXcitation Coding) speech coding
10 Reserved
11 Reserved
12 TTSI (Text-To-Speech Interface)
13 Main Synthesis
14 Wavetable Synthesis
15 General MIDI
16 Algorithmic Synthesis and Audio Effects
17 ER (Error Resilient) AAC LC
18 Reserved
19 ER AAC LTP
20 ER AAC Scalable
21 ER TwinVQ
22 ER BSAC (Bit-Sliced Arithmetic Coding)
23 ER AAC LD (Low Delay)
24 ER CELP
25 ER HVXC
26 ER HILN (Harmonic and Individual Lines plus Noise)
27 ER Parametric
28 SSC (SinuSoidal Coding)
29 PS (Parametric Stereo)
30 MPEG Surround
31 (Escape value)
32 Layer-1
33 Layer-2
34 Layer-3
35 DST (Direct Stream Transfer)
36 ALS (Audio Lossless)
37 SLS (Scalable LosslesS)
38 SLS non-core
39 ER AAC ELD (Enhanced Low Delay)
40 SMR (Symbolic Music Representation) Simple
41 SMR Main
42 USAC (Unified Speech and Audio Coding) (no SBR)
43 SAOC (Spatial Audio Object Coding)
44 LD MPEG Surround
45 USAC
MPEG-4 Audio Profile

MPEG-4在音频编码方向对音频能力集合的描述称为Audio Profiles,音频能力描述基于AOT image

  1. MPEG-2 AAC LC: Advanced Audio Coding Low-Complexity,(AAC-LC / LC-AAC)格式是MPEG-2格式,设计用于数字电视。AAC-LC用于存储空间和计算能力有限的情况。这种类型没有使用预测和增益控制这两种工具,瞬时噪声整形的阶数也比较低。 AAC-LC是充分利用心理声学原理,对人类对音频信号的感知存在不相干性和统计冗余的特性,最大程度的减少用于表达信号的比特数据 ,实现音频信号快速有效地压缩,而不再追求输出信号和原始信号相似度。 AAC-LC的重要技术点有如下一些。
  • Temporal Noise Shaping:瞬时噪声整形是用来控制量化噪声的瞬时形态,解决掩蔽阈值和量化噪声的错误匹配的问题。TNS利用时频对偶性,即时域平稳的信号会在频域上变比剧烈,而频域平稳的信号会在时域上变化剧烈。对时域的瞬态信号可以对频谱系数进行预测编码。对频谱系数进行预测,可以及时调节量化器以适应输入信号的时域状态,可以有效的控制量化噪声,
  • Intensity Stereo:利用心理声学原理提高编码效率的一种方法。由于人耳对高频信号的相位不敏感,只要信号的能量和频谱相似,在感知上没有什么区别,所以当一对声道的信号相关性较高时,可以对高频部分进行一定的处理,只在一个声道中编码传输数据,而不会影响解码后的重建音质。 AAC-LC把6kHz作为声强立体声处理的起始频率,在这个频率上的都进行声强立体声处理。计算出左右声道各个子带的能量和总能量,然后计算左声道能量和总能量的比值并换算成一个强度因子,按照这个强度因子对了带内的所有频谱进行左右声道求和并归一化,右声道的数据则全部置零,这样只需要对左声道数据进行量化编码。
  • Perceptual Noise Substitution:感知噪声替代用于频谱成分分类似噪声(功率谱密度是均匀的)时,用人造噪声代替。当判断某个频带需要进行感知噪声替代后,只用把该频带的能量作为参数编码传输,而不需要对子带内的频谱值进行编码,解码时解出子带能量和随机矢量生成函数产生的类似噪声。 Middle/Side:立体声编码,是利用一对声道的信号之间的相关性去冗余,降低编码比特率的方法。AAC-LD编码器中对左右声道的数据相关性较大时,可以用Middle=(L+R)/ 2,Side = (L-R)/2来代替左右声道的数据进行编码。这样能量集中在一个声道数据中,而另外一个声道只要少量比特数据,这样实现了数据压缩。
  1. MPEG-2 AAC Main: 主规格
  2. MPEG-2 AAC SSR: 可变采样率规格(Scaleable Sample Rate)
  3. MPEG-4 AAC LC: 低复杂度规格(Low Complexity)——现在的手机比较常见的MP4文件中的音频部份就包括了该规格音频文件
  4. MPEG-4 AAC Main: 主规格 ——包含了除增益控制之外的全部功能,其音质最好
  5. MPEG-4 AAC SSR: 可变采样率规格(Scaleable Sample Rate)
  6. MPEG-4 AAC LTP: 长时期预测规格(Long Term Predicition)
  7. MPEG-4 AAC LD: AAC是感知型音频编解码器,可以在较低的比特率下提供很高质量的主观音质。但是这样的编解码器在低比特率下的算法延时往往超过100ms,所以并不适合实时的双向通信。而基于G.722的语音编解码方案因为其较小的算法延时而适合于双向通信。但是这种基于语音的编解码方案只能针对语音信号提供较好的主观质量,并不适合更为复杂的音频信号,而且即使在很高的比特率下,该编解码方案给出的结果也很难达到良好的音质。 常用的感知音频编码器的延时包括:
  • Framing delay:进行块变换需要的块长;
  • Filterbank delay:分析-综合滤波器所需要的延时;
  • Look-ahead delay for block switching:块切换为检测瞬态而需要的延时;
  • Use of bit reservoir:比特池大小相对于平均比特率所需要的延时。 总延时计算公式: image 如下面的AAC-LC为例: iamge 在AAC-LD中,为了减少延时,将原来的1024的帧长改为512;没有了窗切换功能,减少了为进行窗切换所需要的前瞻延时;同时为了增强对瞬态信号的编码质量,引入了窗型切换机制,窗型包括一般的SINE窗和一个少重叠的窗,该窗与后面的窗有很少的重叠,这样通过对TNS工具的优化来消除瞬态信号产生的预回声效应。 MPEG-4 Low Delay Audio Coder (AAC-LD)是直接源于MPEG-2 AAC,并且结合了感知音频编码和双向通信必须的低延时要求。它可以保证最大的20ms的算法延时和包括语音和音乐的信号的很好的音质。现在的MPEG-4 AAC LD支持最大采样率48kHz,最大声道数目是2(可以扩展为多声道)。
  1. MPEG-4 AAC HE: 高效率规格(High Efficiency)—–这种规格适合用于低码率编码,有Nero ACC 编码器支持

14496-3标准,里面定义的profile除了上述的一些规格,还有如Scalable 、 TwinVQ、 CELP、 HVXC等更多其他的profile。

目前听到用的比较多的应该是LC和HE(适合低码率)。流行的Nero AAC的命令行编码程序就支持LC,HE,HEv2这三种,试用后,用MediaInfo分析了编码后的AAC音频,发现规格显示都是LC,当时就感到奇怪,不是说支持三种规格吗?然后才又查资料发现,原来HE其实就是AAC(LC)+SBR技术,HEv2就是AAC(LC)+SBR+PS技术,难怪用MediaInfo分析后,HE规格的文件即显示:

1
2
3
格式简介:LC
格式设置,SBR:是
格式设置,PS:否
关于HE与HEv2
  • HE:“high efficiency”(高效性)。HE-AAC v1(又称AACPlusV1,SBR)用容器的方法加了原AAC(LC)+SBR技术。SBR其实代表的是Spectral Band Replication(频段复制)。简单概括一下,音乐的主要频谱集中在低频段,高频段幅度很小(但很重要,决定了音质),如果对整个频段编码,要么为了保护高频造成低频段编码过细以致文件巨大,要么为了保存了低频的主要成分而失去高频成分以致丧失音质。SBR把频谱切割开来,低频单独编码保存主要成分,高频单独放大编码保存音质,“统筹兼顾”了,在减少文件大小的情况下还保存了音质,完美的化解了一对矛盾

  • HEv2 它用容器的方法包含了HE-AAC v1和PS技术。PS指“parametric stereo”(参数立体声)。这个其实好理解,原来的立体声文件,文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性,根据香农信息熵编码定理,相关性应该被去掉才能减小文件大小。所以PS技术存储了一个声道的全部信息,然后,花很少的字节用参数描述另一个声道和它不同的地方 这样,HEv1和HEv2用个图简单表示下就是:(图中的AAC即指的是原来的AAC-LC) image 由于NERO AAC编码后产生的是经过MP4容器封装后的,而我们的decoder需要处理的是未经封装的AAC流,因此还需要处理从MP4封装格式中extract出AAC流的步骤;哦,这里提到了MP4容器封装,就再把我看到的一些关于MP4容器的心得插入在此也说下:

其实.mp4格式规范是MPEG4 Part 1标准定义的。但是这个格式本身相当通用,并不是只能用来存贮MPEG4视频格式。举个例子,一个.mp4文件中包含的可能是H.263的视频轨及AMR的音频轨。这样它和MPEG4视频压缩算法就半点边都沾不上。但它绝对是一个合法的.mp4文件。从这个意义上讲,.mp4是一个独立的封包格式。也许它的原始设计意图是仅用于MPEG4,但事实上大家觉得它很好用,已经把它扩展成可以包容其它格式了。现在市场上比如某产品号称“支持MP4播放”,到底是什么意思呢?如果它是指可以播放.mp4这种文件,那里面的音频和视频格式它能支持多少种组合呢?没说清楚吧。举个极端的例子,假设一台设备仅支持“视频为未压缩YUV以及不带音频轨的.mp4文件,但它的文件名确实可以是.mp4,是不是也可以在盒子上印上“支持MP4”呢?那么,买回去,复制一个网上下载的.mp4文件(MPEG4视频和AAC音频应该是个比较流行的组合),结果却发现根本不能播放。就算不举这么极端的例子,一般.mp4文件中常见的视频音频格式也有多种,一个产品要做到支持所有的格式是很难的。所以,如果要准确的描述,应该写清楚类似“支持视频格式为MPEG4或H.264/AVC,音频为AMR或AAC的*.mp4文件”。其实更严格一些,还应该写清楚MPEG4支持到哪种profile, AMR是NB还是WB,AAC是LC还是HE等更多细节。当然,这种误导型的说明应该在减少,不过如果有比较确切的格式需求,最好还是先搞清楚这些细节。看到网上还有人说到N73,其实只支持视频为MPEG4 Simple Profile / Advanced Simple Profile及H.263 Profile 0 & 3,音频为AMR-NB/WB或者AAC-LC, HE-AAC的mp4文件。如果你放一个视频格式为H.264/AVC的mp4上去,是无法播放出画面来的。

在网上找了一些工具,如MP4UI,MP4BOX,Yamb(mp4box的GUI程序),采用它们进行extract操作后发现,原来的SBR和PS等信息咋没有了,都变成LC规格的AAC文件啦。好容易准备的测试流,难道还是不能用?于是一番苦寻发现,可能是SBR和PS等信息在ADTS头中是无法体现的,所以分析ADTS格式头的AAC,就无法判别是否是HE和HEv2啦。但是我总觉得SBR和PS等技术信息在AAC流中应该还是存在的。因为我还在一个国外的论坛上看到这么几句话:There’s no requirement for MP4 with AAC to have SBR indicated in the headers. It’s still correct not to have it marked and have SBR or PS data in the stream anyway. Likewise, decoding a frame and not seeing any SBR or PS info doesn’t mean you can’t find it further up in the stream anyway(我理解就是说SBR OR PS信息不一定在Header中有,但是并不意味着你不能进一步在stream中发现它)。

HE-AAC的.mp4码流,经过extract出AAC(ADTS)后,44.1KHZ的变成了22.05KHZ。HEv2-AAC的.mp4码流,经过extract出AAC(ADTS)后,不但44.1KHZ的变成了22.05KHZ(一半),连2channels也变成了1channels,这个问题更奇怪了,在论坛上找,发现也有人有此问题:“I get 22050Hz, 1 channel for audio that is in fact 44100Hz, 2channels and having both SBR and PS”。

后来看到MSDN中的AAC Decoder的描述中有这么一小段话: The media type gives the sample rate and number of channels prior to the application of spectral band replication (SBR) and parametric stereo (PS) tools, if present. The effect of the SBR tool is to double the decoded sample rate relative to the core AAC-LC sample rate. The effect of the PS tool is to decode stereo from a mono-channel core AAC-LC stream. 我的理解是AAC的decoder如果支持SBR和PS,会将AAC-HEV1(SBR)中的sample rate提高一倍,而会将AAC-HEV2(SBR+PS)中不仅sample rate提高一倍,单声道也提高至双声道了。结合前面提到的SBR(频段复制)和PS(参数立体声)技术的简单介绍,好像觉得这样是有点儿道理的哦~~ 用IPP example提供的解码工具simple_player简单试了下,对于44.1khz,stereo的HEv2-AAC的.mp4码流,经过extract出22.05KHZ,mono 的AAC(ADTS)后,再使用simple_player进行音频解码测试,解完后,果然发现又恢复了44.1khz和stereo。(但目前也测试了好几种extract出的HE和HEv2的aac码流,有的能将sample rate和channel 又double回来,有的又不能,这个具体原因是不是由于Ipp example提供的解码器的问题还不确定)。

另外,用simple_player如果直接decoder编码出的经过封装的.mp4格式的AAC音频的话,发现:其它都正常,只AAC-HEv2格式的.mp4音频解码后变成了单声道。难道是解码器中的PS tools没能发挥作用?初步估计应该是IPP 的那个小解码器的问题吧。

AAC封装格式

image 以常用的两个格式为例:

  • ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
  • ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。 简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,具体的组织结构在这里就不详说了。

参考: AAC的各种规格

AAC格式和M4A格式

AAC(Advanced Audio Coding),中文称为“高级音频编码”,出现于1997年,基于 MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、Sony(索尼)等公司共同开发,目的是取代MP3格式。2000年,MPEG-4标准出现后,AAC 重新集成了其特性,加入了SBR技术和PS技术,为了区别于传统的 MPEG-2 AAC 又称为 MPEG-4 AAC。AAC编码的主要扩展名有三种:

  • .AAC - 使用MPEG-2 Audio Transport Stream( ADTS,参见MPEG-2 )容器,区别于使用MPEG-4容器的MP4/M4A格式,属于传统的AAC编码(FAAC默认的封装,但FAAC亦可输出 MPEG-4 封装的AAC)
  • .MP4 - 使用了MPEG-4 Part 14(第14部分)的简化版即3GPP Media Release 6 Basic (3gp6,参见3GP ) 进行封装的AAC编码(Nero AAC 编码器仅能输出MPEG-4封装的AAC);
  • .M4A - 为了区别纯音频MP4文件和包含视频的MP4文件而由苹果(Apple)公司使用的扩展名,Apple iTunes 对纯音频MP4文件采用了”.M4A”命名。M4A的本质和音频MP4相同,故音频MP4文件亦可直接更改扩展名为M4A。是 MPEG-4 Audio 标准容器,而 AAC 作为一个容器是 MPEG-2 标准的。现在的 AAC-LC 就是以前制定的 MPEG-2 时代的 AAC 的更名延续,而 MPEG-4 时代的 AAC 叫 AAC-HE .AAC-LC 可以用 AAC (ADTS) 作容器也可以用 MP4 做容器,两者可以用 MP4Box 的一个命令直接转换,而 AAC-HE 只能用 MP4 做容器。
Sampling Frequencies

There are 13 supported frequencies:

  • 0: 96000 Hz
  • 1: 88200 Hz
  • 2: 64000 Hz
  • 3: 48000 Hz
  • 4: 44100 Hz
  • 5: 32000 Hz
  • 6: 24000 Hz
  • 7: 22050 Hz
  • 8: 16000 Hz
  • 9: 12000 Hz
  • 10: 11025 Hz
  • 11: 8000 Hz
  • 12: 7350 Hz
  • 13: Reserved
  • 14: Reserved
  • 15: frequency is written explictly
Channel Configurations

These are the channel configurations:

  • 0: Defined in AOT Specifc Config
  • 1: 1 channel: front-center
  • 2: 2 channels: front-left, front-right
  • 3: 3 channels: front-center, front-left, front-right
  • 4: 4 channels: front-center, front-left, front-right, back-center
  • 5: 5 channels: front-center, front-left, front-right, back-left, back-right
  • 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
  • 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
  • 8-15: Reserved
术语说明
  • AAC: Advanced Audio Coding 高级音频编码
  • AAC LC: AAC with Low Complexity AAC的低复杂度配置
  • AAC plus: 也叫HE-AAC, AAC+,MPEG4 AAC LC加入SBR模块后形成的一个AAC版本
  • MPEG:Motion Picture Expert Group
  • IMDCT:反离散余弦变换
  • ADIF:Audio Data Interchange Format 音频数据交换格式
  • ADTS:Audio Data Transport Stream 音频数据传输流
  • SCE: Single Channel Element单通道元素
  • CPE: Channel Pair Element 双通道元素
  • CCE: Coupling Channel Element 藕合通道元素
  • DSE: Data Stream Element 数据流元素
  • PCE: Program Config Element 程序配置元素
  • FIL: Fill Element 填充元素
  • ICS: Individual Channel Stream 独立通道流
  • PNS: Perceptual Noise Substitution 知觉噪声替换
  • SBR: Spectral Band Replication 频段复制
  • TNS: Temporal Noise Shaping 瞬时噪声整形
  • ch:channel 通道
最后

AAC-LC 是什么格式?和 AAC 有什么区别? AAC是标准化在MPEG2和MPEG4的音频编码集合的总称。 AAC-LC是标准化AAC中AOT为2的一种音频编解码,它的特点是运算复杂度低,对内存占用小,标准化的时间早,对通性好,兼容性好,使用广。不足是算法时延高,不利于实时的音频通讯。

问题原因分析

首先BufferOverFlowException问题,由于音频采用AAC-HE导致MediaExtractor解析出的音频采样率为本身采样率的一半,同时创建出的解码OutputBuffer的大小是编码InputBuffer的2倍,而media-for-mobile开源项目直接将从解码器OutputBuffer取出的数据塞入编码器InputBuffer中,2倍的数据放入一倍的Buffer导致溢出,暂时的解决办法手动指定编码器InputBuffer max-size为一个较大值(10*1024).同时,由于MediaExtractor不能获取正确的audio profile,也无法确认获取到的采样率是否不正确采样率的一半,所以采用ffmpeg接口获取profile,但是ffmpeg调用avcodec_open2获取到的profile为-99,而且采样率依然为正常采样率一半,只有在解码一帧音频后才能得到正确的profile与采样率.

其次,转码进度不增加的问题,主要是High的H264视频,media-for-mobile使用一个MediaExtractor一次抽取音视频帧,如果是音频则交音频解码器,如果为视频则交视频解码器,解码后将解码帧转交编码器,编码后将数据写入MediaMuxer,将数据写入MediaMuxer的前提是吊用过addTrack,将音视频track加入到Muxer中,而addTrack需要在音视频解码若干帧后产生INFO_OUTPUT_FORMAT_CHANGED,若没有addTrack会导致编码后数据如法写入Muxer,卡死编码器,Baseline视频只需要少量帧就可以产生INFO_OUTPUT_FORMAT_CHANGED,但High视频产生INFO_OUTPUT_FORMAT_CHANGED需要更多的帧,而media-for-mobile中,MediaExtractor首先一直获取到的是音频数据,音频一直解码编码,但是输出的muxer时,videotrack未被添加,所以无法将音频编码器的数据写入muxer,音频解码器被卡死,而mediasource一直被产生的audio数据无法被消费,无法获取到视频数据导致视频INFO_OUTPUT_FORMAT_CHANGED一直无法产生,最终产生死锁,导致audio等待video的INFO_OUTPUT_FORMAT_CHANGED,video等待audio被读完后读到video解码产生INFO_OUTPUT_FORMAT_CHANGED.

最后将audio和video使用两个MediaExtractor各读取各自内容.

android自动化测试(一):UiAutomator官方介绍

发表于 2017-08-30 | 分类于 autotest

了解android测试需要查询android官方文档,android官方培训教程Getting Started with Testing介绍了android提供的测试类型,测试接口等,相较与网上总结的android自动化测试框架,官方文档显然分类更合理,定位更准确.

https://developer.android.com/training/testing/

两种测试类型

在使用Android Studio创建模块时会在src下生成androidTest和test两个用于测试的的目录,对应下面两种测试类型. image

本地单元测试(Local unit tests)

位于module-name/src/test/java/.下,运行在PC端本地的JVM虚拟机上,并且不能访问Android框架的接口. 参考Building Local Tests

设备化测试

位于module-name/src/androidTest/java/.下,必须运行在Android物理设备和虚拟机上. 参考Building Instrumented Unit Tests

Instrumented unit tests are tests that run on physical devices and emulators, and they can take advantage of the Android framework APIs and supporting APIs, such as the Android Testing Support Library. You should create instrumented unit tests if your tests need access to instrumentation information (such as the target app’s Context) or if they require the real implementation of an Android framework component (such as a Parcelable or SharedPreferences object).

Using instrumented unit tests also helps to reduce the effort required to write and maintain mock code. You are still free to use a mocking framework, if you choose, to simulate any dependency relationships.

设备化单元测试分为:

  • 设备化单元测试(Instrumented Unit Test):Building Instrumented Unit Tests: Build complex unit tests with Android dependencies that cannot be satisfied with mock objects.
  • 组件集成测试:Automating User Interface Tests: Create tests to verify that the user interface behaves correctly for user interactions within a single app or for interactions across multiple apps.
  • app集成测试:Testing App Component Integrations: Verify the behavior of components that users do not directly interact with, such as a Service or aContent Provider.

android自动化测试(N):UiAutomator官方介绍

发表于 2017-08-30 | 分类于 autotest

Automating User Interface Tests

User interface (UI) testing lets you ensure that your app meets its functional requirements and achieves a high standard of quality such that it is more likely to be successfully adopted by users.

One approach to UI testing is to simply have a human tester perform a set of user operations on the target app and verify that it is behaving correctly. However, this manual approach can be time-consuming, tedious, and error-prone. A more efficient approach is to write your UI tests such that user actions are performed in an automated way. The automated approach allows you to run your tests quickly and reliably in a repeatable manner.

Note: It is strongly encouraged that you use Android Studio for building your test apps, because it provides project setup, library inclusion, and packaging conveniences. This class assumes you are using Android Studio.

To automate UI tests with Android Studio, you implement your test code in a separate Android test folder (src/androidTest/java). The Android Plug-in for Gradle builds a test app based on your test code, then loads the test app on the same device as the target app. In your test code, you can use UI testing frameworks to simulate user interactions on the target app, in order to perform testing tasks that cover specific usage scenarios.

For testing Android apps, you typically create these types of automated UI tests:

  • UI tests that span a single app: This type of test verifies that the target app behaves as expected when a user performs a specific action or enters a specific input in its activities. It allows you to check that the target app returns the correct UI output in response to user interactions in the app’s activities. UI testing frameworks like Espresso allow you to programmatically simulate user actions and test complex intra-app user interactions.
  • UI tests that span multiple apps: This type of test verifies the correct behavior of interactions between different user apps or between user apps and system apps. For example, you might want to test that your camera app shares images correctly with a 3rd-party social media app, or with the default Android Photos app. UI testing frameworks that support cross-app interactions, such as UI Automator, allow you to create tests for such scenarios. The lessons in this class teach you how to use the tools and APIs in the Android Testing Support Library to build these types of automated tests. Before you begin building tests using these APIs, you must install the Android Testing Support Library, as described in Downloading the Android Testing Support Library.

UI Testing

In addition to unit testing the individual components that make up your Android application (such as activities, services, and content providers), it is also important that you test the behavior of your application’s user interface (UI) when it is running on a device. UI testing ensures that your application returns the correct UI output in response to a sequence of user actions on a device, such as entering keyboard input or pressing toolbars, menus, dialogs, images, and other UI controls.

Functional or black-box UI testing does not require testers to know the internal implementation details of the app, only its expected output when a user performs a specific action or enters a specific input. This approach allows for better separation of development and testing roles in your organization.

One common approach to UI testing is to run tests manually and verify that the app is behaving as expected. However, this approach can be time-consuming, tedious, and error-prone. A more efficient and reliable approach is to automate the UI testing with a software testing framework. Automated testing involves creating programs to perform testing tasks (test cases) to cover specific usage scenarios, and then using the testing framework to run the test cases automatically and in a repeatable manner.

Overview

he Android SDK provides the following tools to support automated, functional UI testing on your application:

  • uiautomatorviewer - A GUI tool to scan and analyze the UI components of an Android application.

  • uiautomator - A Java library containing APIs to create customized functional UI tests, and an execution engine to automate and run the tests. To use these tools, you must have the following versions of the Android development tools installed:

  • Android SDK Tools, Revision 21 or higher

  • Android SDK Platform, API 16 or higher

Workflow for the the uiautomator testing framework Here’s a short overview of the steps required to automate UI testing:

  1. Prepare to test by installing the app on a test device, analyzing the app’s UI components, and ensuring that your application is accessible by the test automation framework.
  2. Create automated tests to simulate specific user interactions on your application.
  3. Compile your test cases into a JAR file and install it on your test device along with your app.
  4. Run the tests and view the test results.
  5. Correct any bugs or defects discovered in testing.

Analyzing Your Application’s UI

Before you start writing your test cases, it’s helpful to familiarize yourself with the UI components (including the views and controls) of the targeted application. You can use the uiautomatorviewer tool to take a snapshot of the foreground UI screen on any Android device that is connected to your development machine. The uiautomatorviewer tool provides a convenient visual interface to inspect the layout hierarchy and view the properties of the individual UI components that are displayed on the test device. Using this information, you can later create uiautomator tests with selector objects that target specific UI components to test.

To analyze the UI components of the application that you want to test:

  1. Connect your Android device to your development machine.

  2. Open a terminal window and navigate to /tools/.

  3. Run the tool with this command:

    1
    $ uiautomatorviewer
  4. To capture a screen for analysis, click the Device Screenshot button in the GUI of the uiautomatorviewer tool.

    Note: If you have more than one device connected, specify the device for screen capture by setting the ANDROID_SERIAL environment variable:

a. Find the serial numbers for your connected devices by running this command:

1
2
3
$ adb devices
```
b. Set the ANDROID_SERIAL environment variable to select the device to test:

#In Windows: set ANDROID_SERIAL=

#In UNIX: export ANDROID_SERIAL=

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
49
50
51
52
53
54
55
56
57
If you are connected to only a single device, you do not need to set the ANDROID_SERIAL environment variable.

5. View the UI properties for your application:
- Hover over the snapshot in the left-hand panel to see the UI components identified by the uiautomatorviewer tool. You can view the component’s properties listed in the lower right-hand panel, and the layout hierarchy in the upper right-hand panel.
- Optionally, click on the Toggle NAF Nodes button to see UI components that are not accessible to the uiautomator testing framework. Only limited information may be available for these components.

#### Preparing to Test
Before using the uiautomator testing framework, complete these pre-flight tasks:

##### Load the application to a device

If you are reading this document, chances are that the Android application that you want to test has not been published yet. If you have a copy of the APK file, you can install the APK onto a test device by using the adb tool. To learn how to install an APK file using the adb tool, see the [adb](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/adb.html#move) documentation.

##### Identify the application’s UI components

Before writing your `uiautomator` tests, first identify the UI components in the application that you want to test. Typically, good candidates for testing are UI components that are visible and that users can interact with. The UI components should also have visible text labels, `android:contentDescription` values, or both.

You can inspect the visible screen objects in an application conveniently by using the `uiautomatorviewer` tool. For more information about how to analyze an application screen with this tool, see the section [Analyzing Your Application’s UI](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/testing/testing_ui.html#uianalaysis). For more information about the common types of UI components provided by Android, see [User Interface](https://stuff.mit.edu/afs/sipb/project/android/docs/guide/topics/ui/index.html).

##### Ensure that the application is accessible

This step is required because the `uiautomator` tool depends on the accessibility features of the Android framework to execute your functional UI tests. You should include these minimum optimizations to support the `uiautomator` tool:
- Use the `android:contentDescription` attribute to label the `ImageButton`, `ImageView`, `CheckBox` and other user interface controls.
- Provide an `android:hint` attribute instead of a content description for `EditText` fields
- Associate an `android:hint` attribute with any graphical icons used by controls that provide feedback to the user (for example, status or state information).
- Make sure that all the user interface elements are accessible with a directional controller, such as a trackball or D-pad.
- Use the `uiautomatorviewer` tool to ensure that the UI component is accessible to the testing framework. You can also test the application by turning on accessibility services like TalkBack and Explore by Touch, and try using your application using only directional controls.

For more information about implementing and testing accessibility, see [Making Applications Accessible](https://stuff.mit.edu/afs/sipb/project/android/docs/guide/topics/ui/accessibility/apps.html).

> Note: To identify the non-accessible components in the UI, click on the Toggle NAF Nodes option in the `uiautomatorviewer` tool.

Generally, Android application developers get accessibility support for free, courtesy of the `View` and `ViewGroup` classes. However, some applications use custom view components to provide a richer user experience. Such custom components won't get the accessibility support that is provided by the standard Android UI components. If this applies to your application, ensure that the application developer exposes the custom drawn UI components to Android accessibility services, by implementing the `AccessibilityNodeProvider` class. For more information about making custom view components accessible, see [Making Applications Accessible](https://stuff.mit.edu/afs/sipb/project/android/docs/guide/topics/ui/accessibility/apps.html#custom-views).

##### Configure your development environment
If you're developing in Eclipse, the Android SDK provides additional tools that help you write test cases using `uiautomator` and buiild your JAR file. In order to set up Eclipse to assist you, you need to create a project that includes the `uiautomator` client library, along with the Android SDK library. To configure Eclipse:

1. Create a new Java project in Eclipse, and give your project a name that is relevant to the tests you’re about to create (for example, "MyAppNameTests"). In the project, you will create the test cases that are specific to the application that you want to test.
2. From the Project Explorer, right-click on the new project that you created, then select Properties > Java Build Path, and do the following:
- Click Add Library > JUnit then select JUnit3 to add JUnit support.
- Click Add External JARs... and navigate to the SDK directory. Under the platforms directory, select the latest SDK version and add both the uiautomator.jar and android.jar files.
If you did not configure Eclipse as your development environment, make sure that the `uiautomator.jar` and `android.jar` files from the `<android-sdk>/platforms/<sdk>` directory are in your Java class path.

Once you have completed these prerequisite tasks, you're almost ready to start creating your `uiautomator` tests.

#### Creating uiautomator Tests
To build a test that runs in the `uiautomator` framework, create a test case that extends the `UiAutomatorTestCase` class. In Eclipse, the test case file goes under the `src` directory in your project. Later, you will build the test case as a JAR file, then copy this file to the test device. The test JAR file is not an APK file and resides separately from the application that you want to test on the device.

Because the `UiAutomatorTestCase` class extends `junit.framework.TestCase`, you can use the `JUnit` Assert class to test that UI components in the app return the expected results. To learn more about JUnit, you can read the documentation on the `junit.org` home page.

The first thing your test case should do is access the device that contains the target app. It’s also good practice to start the test from the Home screen of the device. From the Home screen (or some other starting location you’ve chosen in the target app), you can use the classes provided by the `uiautomator` API to simulate user actions and to test specific UI components. For an example of how to put together a `uiautomator` test case, see the sample test case.

##### uiautomator API
The `uiautomator` API is bundled in the `uiautomator.jar` file under the `<android-sdk>/platforms/` directory. The API includes these key classes that allow you to capture and manipulate UI components on the target app:
- [UiDevice](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/UiDevice.html)
Represents the device state. In your tests, you can call methods on the UiDevice instance to check for the state of various properties, such as current orientation or display size. Your tests also can use the UiDevice instance to perform device level actions, such as forcing the device into a specific rotation, pressing the d-pad hardware button, or pressing the Home and Menu buttons.
To get an instance of UiDevice and simulate a Home button press:

getUiDevice().pressHome();

1
2
- [UiSelector](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/UiSelector.html)
Represents a search criteria to query and get a handle on specific elements in the currently displayed UI. If more than one matching element is found, the first matching element in the layout hierarchy is returned as the target UiObject. When constructing a UiSelector, you can chain together multiple properties to refine your search. If no matching UI element is found, a `UiAutomatorObjectNotFoundException` is thrown. You can use the childSelector() method to nest multiple UiSelector instances. For example, the following code example shows how to specify a search to find the first ListView in the currently displayed UI, then search within that ListView to find a UI element with the text property Apps.

UiObject appItem = new UiObject(new UiSelector() .className(“android.widget.ListView”).instance(1) .childSelector(new UiSelector().text(“Apps”)));

1
2
3
4

- [UiObject](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/UiObject.html)
Represents a UI element. To create a UiObject instance, use a UiSelector that describes how to search for, or select, the UI element.
The following code example shows how to construct UiObject instances that represent a Cancel button and a OK button in your application.

UiObject cancelButton = new UiObject(new UiSelector().text(“Cancel”)); UiObject okButton = new UiObject(new UiSelector().text(“OK”));

1
2
You can reuse the UiObject instances that you have created in other parts of your app testing, as needed. Note that the `uiautomator` test framework searches the current display for a match every time your test uses a UiObject instance to click on a UI element or query a property.
In the following code example, the `uiautomator` test framework searches for a UI element with the text property OK. If a match is found and if the element is enabled, the framework simulates a user click action on the element.

if(okButton.exists() && okButton.isEnabled()) { okButton.click(); }

1
You can also restrict the search to find only elements of a specific class. For example, to find matches of the Button class:

UiObject cancelButton = new UiObject(new UiSelector().text(“Cancel”) .className(“android.widget.Button”)); UiObject okButton = new UiObject(new UiSelector().text(“OK”) .className(“android.widget.Button”));

1
2
- [UiCollection](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/UiCollection.html)
Represents a collection of items, for example songs in a music album or a list of emails in an inbox. Similar to a UiObject, you construct a UiCollection instance by specifying a UiSelector. The UiSelector for a UiCollection should search for a UI element that is a container or wrapper of other child UI elements (such as a layout view that contains child UI elements). For example, the following code snippet shows how to construct a UiCollection to represent a video album that is displayed within a FrameLayout:

UiCollection videos = new UiCollection(new UiSelector() .className(“android.widget.FrameLayout”));

1
If the videos are listed within a LinearLayout view, and you want to to retrieve the number of videos in this collection:

int count = videos.getChildCount(new UiSelector() .className(“android.widget.LinearLayout”));

1
If you want to find a specific video that is labeled with the text element Cute Baby Laughing from the collection and simulate a user-click on the video:

UiObject video = videos.getChildByText(new UiSelector() .className(“android.widget.LinearLayout”), “Cute Baby Laughing”); video.click();

1
2

Similarly, you can simulate other user actions on the UI object. For example, if you want to simulate selecting a checkbox that is associated with the video:

UiObject checkBox = video.getChild(new UiSelector() .className(“android.widget.Checkbox”)); if(!checkBox.isSelected()) checkbox.click();

1
2
3
- [UiScrollable](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/UiScrollable.html)
Represents a scrollable collection of UI elements. You can use the UiScrollable class to simulate vertical or horizontal scrolling across a display. This technique is helpful when a UI element is positioned off-screen and you need to scroll to bring it into view.
For example, the following code shows how to simulate scrolling down the Settings menu and clicking on an About tablet option:

UiScrollable settingsItem = new UiScrollable(new UiSelector() .className(“android.widget.ListView”)); UiObject about = settingsItem.getChildByText(new UiSelector() .className(“android.widget.LinearLayout”), “About tablet”); about.click()

1
2
3
4
For more information about these APIs, see the uiautomator reference.

##### A sample uiautomator test case
The following code example shows a simple test case which simulates a user bringing up the Settings app in a stock Android device. The test case mimics all the steps that a user would typically take to perform this task, including opening the Home screen, launching the All Apps screen, scrolling to the Settings app icon, and clicking on the icon to enter the Settings app.

package com.uia.example.my;

// Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class LaunchSettings extends UiAutomatorTestCase {

public void testDemo() throws UiObjectNotFoundException {

// Simulate a short press on the HOME button.
getUiDevice().pressHome();

// We’re now in the home screen. Next, we want to simulate
// a user bringing up the All Apps screen.
// If you use the uiautomatorviewer tool to capture a snapshot
// of the Home screen, notice that the All Apps button’s
// content-description property has the value “Apps”.  We can
// use this property to create a UiSelector to find the button.
UiObject allAppsButton = new UiObject(new UiSelector()
   .description("Apps"));

// Simulate a click to bring up the All Apps screen.
allAppsButton.clickAndWaitForNewWindow();

// In the All Apps screen, the Settings app is located in
// the Apps tab. To simulate the user bringing up the Apps tab,
// we create a UiSelector to find a tab with the text
// label “Apps”.
UiObject appsTab = new UiObject(new UiSelector()
   .text("Apps"));

// Simulate a click to enter the Apps tab.
appsTab.click();

// Next, in the apps tabs, we can simulate a user swiping until
// they come to the Settings app icon.  Since the container view
// is scrollable, we can use a UiScrollable object.
UiScrollable appViews = new UiScrollable(new UiSelector()
   .scrollable(true));

// Set the swiping mode to horizontal (the default is vertical)
appViews.setAsHorizontalList();

// Create a UiSelector to find the Settings app and simulate      
// a user click to launch the app.
UiObject settingsApp = appViews.getChildByText(new UiSelector()
   .className(android.widget.TextView.class.getName()),
   "Settings");
settingsApp.clickAndWaitForNewWindow();

// Validate that the package name is the expected one
UiObject settingsValidation = new UiObject(new UiSelector()
   .packageName("com.android.settings"));
assertTrue("Unable to detect Settings",
   settingsValidation.exists());   

}
}

1
2
3
4

#### Building and Deploying Your uiautomator Tests
1. Once you have coded your test, follow these steps to build and deploy your test JAR to your target Android test device:
Create the required build configuration files to build the output JAR. To generate the build configuration files, open a terminal and run the following command:

/tools/android create uitest-project -n -t 1 -p

1
2
3
4
5
The <name> is the name of the project that contains your uiautomator test source files, and the <path> is the path to the corresponding project directory.
2. From the command line, set the ANDROID_HOME variable:
- In Windows:`set ANDROID_HOME=<path_to_your_sdk>`
- In UNIX:`export ANDROID_HOME=<path_to_your_sdk>`
3. Go to the project directory where your build.xml file is located and build your test JAR.

ant build

1
4. Deploy your generated test JAR file to the test device by using the adb push command:

adb push /data/local/tmp/

1
Here’s an example:

adb push ~/dev/workspace/LaunchSettings/bin/LaunchSettings.jar /data/local/tmp/

1
2
3

#### Running uiautomator Tests
Here’s an example of how to run a test that is implemented in the `LaunchSettings.jar` file. The tests are bundled in the `com.uia.example.my` package:

adb shell uiautomator runtest LaunchSettings.jar -c com.uia.example.my.LaunchSettings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
To learn more about the syntax, subcommands, and options for uiautomator, see the [uiautomator](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/index.html) reference.

#### Best Practices
Here are some best practices for functional UI testing with the uiautomator framework:

- Ensure that you validate the same UI functions on your application across the various types of devices that your application might run on (for example, devices with different screen densities).
- You should also test your UI against common scenarios such as in-coming phone calls, network interruptions, and user-initiated switching to other applications on the device.

### [uiautomator tools](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/help/uiautomator/index.html)
The uiautomator testing framework lets you test your user interface (UI) efficiently by creating automated functional UI testcases that can be run against your app on one or more devices.

For more information on testing with the uiautomator framework, see [UI Testing](https://stuff.mit.edu/afs/sipb/project/android/docs/tools/testing/testing_ui.html).

#### Syntax
To run your testcases on the target device, you can use the `adb shell` command to invoke the `uiautomator` tool. The syntax is:

adb shell uiautomator runtest -c [options]

1
Here’s an example:

adb shell uiautomator runtest LaunchSettings.jar -c com.uia.example.my.LaunchSettings


#### Command-line Options
The following table describes the subcommands and options for uiautomator.
Table 1. Command-line options for uiautomator

|Subcommand|    Option|    Description|
|---|---|---|
|runtest|    `<jar>`|    Required. The <jar> argument is the name of one or more JAR files that you deployed to the target device which contain your uiautomator testcases. You can list more than one JAR file by using a space as a separator.|
||`-c <test_class_or_method>`    |Required. The <test_class_or_method> argument is a list of one or more specific test classes or test methods from the JARs that you want uiautomator to run.Each class or method must be fully qualified with the package name, in one of these formats: package_name.class_name package_name.class_name#method_name You can list multiple classes or methods by using a space as a separator.|
||--nohup    |Runs the test to completion on the device even if its parent process is terminated (for example, if the device is disconnected).|
||-e |<NAME> <VALUE>|Specify other name-value pairs to be passed to test classes. May be repeated.Note: The -e options cannot be combined; you must prefix each option with a separate -e flag.
||-e debug [true|false]    |Wait for debugger to connect before starting.|
||dump    [file]    |Generate an XML file with a dump of the current UI hierarchy. If a filepath is not specified, by default, the generated dump file is stored on the device in this location `/storage/sdcard0/window_dump.xml`.|
|events||         Prints out accessibility events to the console until the connection to the device is terminated|

### [UiAutomation api](https://developer.android.com/reference/android/app/UiAutomation.html)
Class for interacting with the device's UI by simulation user actions and introspection of the screen content. It relies on the platform accessibility APIs to introspect the screen and to perform some actions on the remote view tree. It also allows injecting of arbitrary raw input events simulating user interaction with keyboards and touch devices. One can think of a UiAutomation as a special type of AccessibilityService which does not provide hooks for the service life cycle and exposes other APIs that are useful for UI test automation.
这是一个通过模拟用户操作来与设备用户界面交互以及获取屏幕内容的类。它依赖于平台的辅助功能APIs来在远程的控件树上获取屏幕内容以及执行一些操作。同时它也允许通过注入原生事件(译者注:指的就是InputEvent. KeyEvent也是继承于InputEvent的,所以说它是原生事件)来模拟用户的按键和触屏操作。我们可以认为UiAutomation就是一个特殊类型的AccessibilityService,其既不会为控制服务的生命周期而提供钩子函数,也不会暴露任何其他可以直接用于用户界面测试自动化的APIs.

The APIs exposed by this class are low-level to maximize flexibility when developing UI test automation tools and libraries. Generally, a UiAutomation client should be using a higher-level library or implement high-level functions. For example, performing a tap on the screen requires construction and injecting of a touch down and up events which have to be delivered to the system by a call to injectInputEvent(InputEvent, boolean).
这个类暴露出来的APIs是很低层的,目的就是为了在开发用户界面测试自动化框架和库时提供最大的弹性。总的来说,一个UiAutomation客户端应该使用一些(基于UiAutomation的)更高层次的库或者实现更高层次的方法。比如,模拟一个用户在屏幕上的点击事件需要构造并注入一个按下和一个弹起事件,然后必须调用UiAutomation的一个injectInputEvent(InputEvent, boolean)的调用来发送给操作系统。

The APIs exposed by this class operate across applications enabling a client to write tests that cover use cases spanning over multiple applications. For example, going to the settings application to change a setting and then interacting with another application whose behavior depends on that setting.
这个类暴露出来的APIs可以跨应用,这样用户就可以编写可以跨越多个应用的测试用例脚本了。比如,打开系统的设置应用去修改一些设置然后再与另外一个依赖于该设置的应用进行交互(译者注:这个在instrumentation这个框架可以做不到的)

android自动化测试之(N):测试支持库

发表于 2017-08-30 | 分类于 autotest

Android官方文档-测试支持库

Android 测试支持库提供了大量用于测试 Android 应用的框架。此库提供了一组 API,让您可以为应用快速构建何运行测试代码,包括 JUnit 4 和功能性用户界面 (UI) 测试。您可以从 Android Studio IDE 或命令行运行使用这些 API 创建的测试。

Android 测试支持库通过 Android SDK 管理器提供。如需了解详细信息,请参阅测试支持库设置

本页介绍了 Android 测试支持库提供了哪些工具、如何在测试环境中使用这些工具,以及库版本的相关信息。

测试支持库功能

Android 测试支持库包括以下自动化测试工具:

  • AndroidJUnitRunner:适用于 Android 且与 JUnit 4 兼容的测试运行器
  • Espresso:UI 测试框架;适合应用中的功能性 UI 测试
  • UI Automator:UI 测试框架;适合跨系统和已安装应用的跨应用功能性 UI 测试

AndroidJUnitRunner

AndroidJUnitRunner 类是一个 JUnit 测试运行器,可让您在 Android 设备上运行 JUnit 3 或 JUnit 4 样式测试类,包括使用 Espresso 和 UI Automator 测试框架的设备。测试运行器可以将测试软件包和要测试的应用加载到设备、运行测试并报告测试结果。此类将替换 InstrumentationTestRunner 类,后者仅支持 JUnit 3 测试。

此测试运行器的主要功能包括:

  • JUnit 支持
  • 访问仪器信息
  • 测试筛选
  • 测试分片 要求 Android 2.2(API 级别 8)或更高版本。
JUnit 支持

测试运行器与 JUnit 3 和 JUnit 4(最高版本为 JUnit 4.10)测试兼容。不过,请勿在同一软件包中混用 JUnit 3 和 JUnit 4 测试代码,因为这可能会导致意外结果。如果要创建一个 JUnit 4 仪器测试类以在设备或模拟器上运行,则测试类必须以 @RunWith(AndroidJUnit4.class) 注解作为前缀。

以下代码段显示了如何编写 JUnit 4 仪器测试来验证 CalculatorActivity 类中的 add 操作是否正常工作。

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
import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.AndroidJUnitRunner;
import android.test.ActivityInstrumentationTestCase2;

@RunWith(AndroidJUnit4.class)
public class CalculatorInstrumentationTest
extends ActivityInstrumentationTestCase2<CalculatorActivity> {

@Before
public void setUp() throws Exception {
super.setUp();

// Injecting the Instrumentation instance is required
// for your test to run with AndroidJUnitRunner.
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
}

@Test
public void typeOperandsAndPerformAddOperation() {
// Call the CalculatorActivity add() method and pass in some operand values, then
// check that the expected value is returned.
}

@After
public void tearDown() throws Exception {
super.tearDown();
}
}
访问仪器信息

您可以使用 InstrumentationRegistry 类访问与测试运行相关的信息。此类包括 Instrumentation 对象、目标应用 Context 对象、测试应用 Context 对象,以及传递到测试中的命令行参数。使用 UI Automator 框架编写测试或编写依赖于 Instrumentation 或 Context 对象的测试时,此数据非常有用。

测试筛选

在 JUnit 4.x 测试中,您可以使用注解对测试运行进行配置。此功能可将向测试中添加样板文件和条件代码的需求降至最低。除了 JUnit 4 支持的标准注解外,测试运行器还支持 Android 特定的注解,包括:

  • @RequiresDevice:指定测试仅在物理设备而不在模拟器上运行。
  • @SdkSupress:禁止在低于给定级别的 Android API 级别上运行测试。例如,要禁止在低于 18 的所有 API 级别上运行测试,请使用注解 - - @SDKSupress(minSdkVersion=18)。
  • @SmallTest、@MediumTest 和 @LargeTest:指定测试的运行时长以及运行频率。
    测试分片
    测试运行器支持将单一测试套件拆分成多个碎片,因此您可以将属于同一碎片的测试作为一个组在同一 Instrumentation 实例下运行。每个分片由一个索引号进行标识。运行测试时,使用 -e numShards 选项指定要创建的独立分片数量,并使用 -e shardIndex 选项指定要运行哪个分片。

例如,要将测试套件拆分成 10 个分片,且仅运行第二个碎片中的测试,请使用以下命令:

adb shell am instrument -w -e numShards 10 -e shardIndex 2 要详细了解如何使用此测试运行器,请参阅 API 参考。

Espresso

Espresso 测试框架提供了一组 API 来构建 UI 测试,用于测试应用中的用户流。利用这些 API,您可以编写简洁、运行可靠的自动化 UI 测试。Espresso 非常适合编写白盒自动化测试,其中测试代码将利用所测试应用的实现代码详情。

Espresso 测试框架的主要功能包括:

  • 灵活的 API,用于目标应用中的视图和适配器匹配。如需了解详细信息,请参阅视图匹配。
  • 一组丰富的操作 API,用于自动化 UI 交互。如需了解详细信息,请参阅操作 API。
  • UI 线程同步,用于提升测试可靠性。如需了解详细信息,请参阅 UI 线程同步。 要求 Android 2.2(API 级别 8)或更高版本。
视图匹配

利用 Espresso.onView() 方法,您可以访问目标应用中的 UI 组件并与之交互。此方法接受 Matcher 参数并搜索视图层次结构,以找到符合给定条件的相应 View 实例。您可以通过指定以下条件来优化搜索:

  • 视图的类名称
  • 视图的内容描述
  • 视图的 R.id
  • 在视图中显示的文本 例如,要找到 ID 值为 my_button 的按钮,可以指定如下匹配器:
    1
    onView(withId(R.id.my_button));

如果搜索成功,onView() 方法将返回一个引用,让您可以执行用户操作并基于目标视图对断言进行测试。

适配器匹配

在 AdapterView 布局中,布局在运行时由子视图动态填充。如果目标视图位于某个布局内部,而该布局是从 AdapterView(例如 ListView 或 GridView)派生出的子类,则 onView() 方法可能无法工作,因为只有布局视图的子集会加载到当前视图层次结构中。

因此,请使用 Espresso.onData() 方法访问目标视图元素。Espresso.onData() 方法将返回一个引用,让您可以执行用户操作并根据 AdapterView 中的元素对断言进行测试。

操作 API

通常情况下,您可以通过根据应用的用户界面执行某些用户交互来测试应用。借助 ViewActions API,您可以轻松地实现这些操作的自动化。您可以执行多种 UI 交互,例如:

  • 视图点击
  • 滑动
  • 按下按键和按钮
  • 键入文本
  • 打开链接 例如,要模拟输入字符串值并按下按钮以提交该值,您可以像下面一样编写自动化测试脚本。ViewInteraction.perform() 和 DataInteraction.perform() 方法采用一个或多个 ViewAction 参数,并以提供的顺序运行操作。
    1
    2
    3
    4
    5
    6
    // Type text into an EditText view, then close the soft keyboard
    onView(withId(R.id.editTextUserInput))
    .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());

    // Press the button to submit the text change
    onView(withId(R.id.changeTextBt)).perform(click());
UI 线程同步

由于计时问题,Android 设备上的测试可能随机失败。此测试问题称为测试不稳定。在 Espresso 之前,解决方法是在测试中插入足够长的休眠或超时期或添加代码,以便重试失败的操作。Espresso 测试框架可以处理 Instrumentation 与 UI 线程之间的同步;这就消除了对之前的计时解决方法的需求,并确保测试操作与断言更可靠地运行。

要详细了解如何使用 Espresso,请参阅 API 参考和测试单个应用的 UI 培训。

UI Automator

UI Automator 测试框架提供了一组 API 来构建 UI 测试,用于在用户应用和系统应用中执行交互。利用 UI Automator API,您可以执行在测试设备中打开“设置”菜单或应用启动器等操作。UI Automator 测试框架非常适合编写黑盒自动化测试,其中的测试代码不依赖于目标应用的内部实现详情。

UI Automator 测试框架的主要功能包括:

  • 用于检查布局层次结构的查看器。如需了解详细信息,请参阅 UI Automator 查看器。
  • 在目标设备上检索状态信息并执行操作的 API。如需了解详细信息,请参阅访问设备状态。
  • 支持跨应用 UI 测试的 API。如需了解详细信息,请参阅 UI Automator API。 要求 Android 4.3(API 级别 18)或更高版本。
UI Automator 查看器

uiautomatorviewer 工具提供了一个方便的 GUI,可以扫描和分析 Android 设备上当前显示的 UI 组件。您可以使用此工具检查布局层次结构,并查看在设备前台显示的 UI 组件属性。利用此信息,您可以使用 UI Automator(例如,通过创建与特定可见属性匹配的 UI 选择器)创建控制更加精确的测试。

uiautomatorviewer 工具位于 <android-sdk>/tools/目录中。

访问设备状态

UI Automator 测试框架提供了一个 UiDevice 类,用于在目标应用运行的设备上访问和执行操作。您可以调用其方法来访问设备属性,如当前屏幕方向或显示尺寸。UiDevice 类还可用于执行以下操作:

  • 更改设备旋转
  • 按 D-pad 按钮
  • 按“返回”、“主屏幕”或“菜单”按钮
  • 打开通知栏
  • 对当前窗口进行屏幕截图 例如,要模拟按下“主屏幕”按钮,请调用 UiDevice.pressHome() 方法。
UI Automator API

利用 UI Automator API,您可以编写稳健可靠的测试,而无需了解目标应用的实现详情。您可以使用这些 API 在多个应用中捕获和操作 UI 组件:

  • UiCollection:枚举容器的 UI 元素以便计算子元素个数,或者通过可见的文本或内容描述属性来指代子元素。
  • UiObject:表示设备上可见的 UI 元素。
  • UiScrollable:为在可滚动 UI 容器中搜索项目提供支持。
  • UiSelector:表示在设备上查询一个或多个目标 UI 元素。
  • Configurator:允许您设置运行 UI Automator 测试所需的关键参数。 例如,以下代码显示了如何编写可在设备中调用默认应用启动器的测试脚本:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Initialize UiDevice instance
    mDevice = UiDevice.getInstance(getInstrumentation());

    // Perform a short press on the HOME button
    mDevice.pressHome();

    // Bring up the default launcher by searching for
    // a UI component that matches the content-description for the launcher button
    UiObject allAppsButton = mDevice
    .findObject(new UiSelector().description("Apps"));

    // Perform a click on the button to bring up the launcher
    allAppsButton.clickAndWaitForNewWindow();

要详细了解如何使用 UI Automator,请参阅 API 参考和测试多个应用的 UI 培训。

测试支持库设置

Android 测试支持库软件包在最新版本的 Android 支持存储库中提供,后者可作为辅助组件通过 Android SDK 管理器下载。

要通过 SDK 管理器下载 Android 支持存储库,请执行以下操作:

  • 启动 Android SDK 管理器。
  • 在 SDK 管理器窗口中,滚动到 Packages 列表末尾,找到 Extras 文件夹并展开(如有必要)以显示其内容。
  • 选择 Android Support Repository 项。
  • 点击 Install packages… 按钮。

下载后,此工具会将支持存储库文件安装到您现有的 Android SDK 目录中。库文件位于 SDK 的以下子目录中:<sdk>/extras/android/m2repository 目录。

Android 测试支持库的类位于 android.support.test 软件包中。

要在 Gradle 项目中使用 Android 测试支持库,请在 build.gradle 文件中添加这些依赖关系:

1
2
3
4
5
6
7
8
9
dependencies {
androidTestCompile 'com.android.support.test:runner:0.4'
// Set this dependency to use JUnit 4 rules
androidTestCompile 'com.android.support.test:rules:0.4'
// Set this dependency to build and run Espresso tests
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
// Set this dependency to build and run UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}

要将 AndroidJUnitRunner 设置为 Gradle 项目中的默认测试仪器运行器,请在 build.gradle 文件中指定此依赖关系:

`` android { defaultConfig { testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner” } }

android自动化测试之(N):adb工具

发表于 2017-08-29 | 分类于 autotest

Android手机自动化测试过程离不开adb工具,介绍几个常用的adb命令.

1. adb forward

命令示例:

1
adb forward tcp:8000 tcp:9000

作用: 把PC端8000端口的数据, 转发到Android端的9000端口上,PC端的8000端口会被 adb 监听, 这个时候我们只需要往8000端口写数据, 这个数据就会发送到手机端的9000端口上.

2. adb connect

命令示例:

1
adb connect + IP

作用: 通过无线网络在PC端adb连接手机端

注意:

  1. 要链接的IP ,必须和自己的PC的网络在同一个局域网内,adb 不能跨局域网链接设备
  2. 如果通过usb链接Android设备,通过adb devices 可以看见设备列表,但是使用不了,可以参考下面的命令说明手机端的服务未开启,需要连接usb开启手机服务,默认端口5555:adb tcpip 5555,开启后拔掉usb通过adb connect 192.168.0.101:5555即可连接

RecyclerView实现单选列表

发表于 2017-08-24 | 分类于 Android

常规方法: 在Javabean里增加一个boolean isSelected字段, 并在Adapter里根据这个字段的值设置“CheckBox”的选中状态。 在每次选中一个新优惠券时,改变数据源里的isSelected字段, 并notifyDataSetChanged()刷新整个列表。 这样实现起来很简单,代码量也很少,唯一不足的地方就是性能有损耗,不是最优雅。 So作为一个有追求 今天比较闲 的程序员,我决心分享一波优雅方案。

本文会列举分析一下在ListView和RecyclerView中, 列表实现单选的几种方案,并推荐采用定向刷新 部分绑定的方案,因为更高效and优雅

1常规方案:

常规方案 请光速阅读,直接上码: Bean结构:

1
2
3
4
5
6
7
public class TestBean extends SelectedBean {
private String name;
public TestBean(String name,boolean isSelected) {
this.name = name;
setSelected(isSelected);
}
}

我项目里有好多单选需求,懒得写isSelected字段,所以弄了个父类供子类继承。

1
2
3
4
5
6
7
8
9
public class SelectedBean {
private boolean isSelected;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}

Acitivity 和Adapter其他方法都是最普通的不再赘述。 Adapter的onBindViewHolder()如下:

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
Log.d("TAG", "onBindViewHolder() called with: holder = [" + holder + "], position = [" + position + "]");
holder.ivSelect.setSelected(mDatas.get(position).isSelected());//“CheckBox”
holder.tvCoupon.setText(mDatas.get(position).getName());//TextView
holder.ivSelect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//实现单选,第一种方法,十分简单, Lv Rv通用,因为它们都有notifyDataSetChanged()方法
// 每次点击时,先将所有的selected设为false,并且将当前点击的item 设为true, 刷新整个视图
for (TestBean data : mDatas) {
data.setSelected(false);
}
mDatas.get(position).setSelected(true);
notifyDataSetChanged();


}
});
ViewHolder:

public static class CouponVH extends RecyclerView.ViewHolder {
private ImageView ivSelect;
private TextView tvCoupon;

public CouponVH(View itemView) {
super(itemView);
ivSelect = (ImageView) itemView.findViewById(R.id.ivSelect);
tvCoupon = (TextView) itemView.findViewById(R.id.tvCoupon);
}
}
  • 方案优点:简单粗暴
  • 方案缺点: 其实需要修改的Item只有两项: 一个当前处于选中状态的Item->普通状态 再将当前手指点击的这个Item->选中状态 但采用普通方案,则会刷新整个一屏可见的Item,重走他们的getView()/onBindViewHolder()方法。 其实一个屏幕一般最多可见10+个Item,遍历一遍也无伤大雅。 但咱们还是要有追求优雅的心,所以我们继续往下看。

2 利用Rv的notifyItemChanged()定向刷新:

本方案可以中速阅读

  1. 本方案需要在Adapter里新增一个字段:

    1
    private int mSelectedPos = -1;//实现单选  方法二,变量保存当前选中的position
  2. 在设置数据集时(构造函数,setData()方法等:),初始化 mSelectedPos 的值。

    1
    2
    3
    4
    5
    6
    //实现单选方法二: 设置数据集时,找到默认选中的pos
    for (int i = 0; i < mDatas.size(); i++) {
    if (mDatas.get(i).isSelected()) {
    mSelectedPos = i;
    }
    }
  3. onClick里代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //实现单选方法二: notifyItemChanged() 定向刷新两个视图
    //如果勾选的不是已经勾选状态的Item
    if (mSelectedPos!=position){
    //先取消上个item的勾选状态
    mDatas.get(mSelectedPos).setSelected(false);
    notifyItemChanged(mSelectedPos);
    //设置新Item的勾选状态
    mSelectedPos = position;
    mDatas.get(mSelectedPos).setSelected(true);
    notifyItemChanged(mSelectedPos);
    }

本方案由于调用了notifyItemChanged(),所以还会伴有“白光一闪”的动画。

  • 方案优点: 本方案,较优雅了,不会重走一屏可见的Item的getView()/onBindViewHolder()方法, 但仍然会重走需要修改的两个Item的getView()/onBindViewHolder()方法,

  • 方案缺点: 我们实际上需要修改的,只是里面“CheckBox”的值, 按照在DiffUtil一文学习到的姿势,术语应该是“Partial bind “, (安利时间,没听过DiffUtil和Partial bind的 戳->:【Android】详解7.0带来的新工具类:DiffUtil) 我们需要的只是部分绑定。

一个疑点: 使用方法2 在第一次选中其他Item时,切换selected状态时, 查看log,并不是只重走了新旧Item的onBindViewHolder()方法,还走了两个根本不在屏幕范围里的Item的onBindViewHolder()方法, 如,本例中 在还有item 0-3 在屏幕里,默认勾选item1,我选中item0后,log显示postion 4,5,0,1 依次执行了onBindViewHolder()方法。 但是再次切换其他Item时, 会符合预期:只走需要修改的两个Item的getView()/onBindViewHolder()方法。 原因未知,有朋友知道烦请告知,多谢。

3 Rv 实现部分绑定(推荐):

利用RecyclerView的 findViewHolderForLayoutPosition()方法,获取某个postion的ViewHolder,按照源码里这个方法的注释,它可能返回null。所以我们需要注意判空,(空即在屏幕不可见)。 与方法2只有onClick里的代码不一样,核心还是利用mSelectedPos 字段搞事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//实现单选方法三: RecyclerView另一种定向刷新方法:不会有白光一闪动画 也不会重复onBindVIewHolder
CouponVH couponVH = (CouponVH) mRv.findViewHolderForLayoutPosition(mSelectedPos);
if (couponVH != null) {//还在屏幕里
couponVH.ivSelect.setSelected(false);
}else {
//add by 2016 11 22 for 一些极端情况,holder被缓存在Recycler的cacheView里,
//此时拿不到ViewHolder,但是也不会回调onBindViewHolder方法。所以add一个异常处理
notifyItemChanged(mSelectedPos);
}
mDatas.get(mSelectedPos).setSelected(false);//不管在不在屏幕里 都需要改变数据
//设置新Item的勾选状态
mSelectedPos = position;
mDatas.get(mSelectedPos).setSelected(true);
holder.ivSelect.setSelected(true);
  • 方案优点: 定向刷新两个Item,只修改必要的部分,不会重走onBindViewHolder(),属于手动部分绑定。代码量也适中,不多。
  • 方案缺点: 没有白光一闪动画???(如果这算缺点)

4 Rv 利用payloads实现部分绑定(不推荐):

本方案属于开拓思维,是在方案2的基础上,利用payloads和notifyItemChanged(int position, Object payload)搞事情。 不知道payloads是什么的,看不懂此方案的,我又要安利:(戳->:【Android】详解7.0带来的新工具类:DiffUtil) onClick代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//实现单选方法四:
if (mSelectedPos != position) {
//先取消上个item的勾选状态
mDatas.get(mSelectedPos).setSelected(false);
//传递一个payload
Bundle payloadOld = new Bundle();
payloadOld.putBoolean("KEY_BOOLEAN", false);
notifyItemChanged(mSelectedPos, payloadOld);
//设置新Item的勾选状态
mSelectedPos = position;
mDatas.get(mSelectedPos).setSelected(true);
Bundle payloadNew = new Bundle();
payloadNew.putBoolean("KEY_BOOLEAN", true);
notifyItemChanged(mSelectedPos, payloadNew);
}

需要重写三参数的onBindViewHolder() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onBindViewHolder(CouponVH holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
Bundle payload = (Bundle) payloads.get(0);
if (payload.containsKey("KEY_BOOLEAN")) {
boolean aBoolean = payload.getBoolean("KEY_BOOLEAN");
holder.ivSelect.setSelected(aBoolean);
}
}
}
  • 方案优点: 同方法3

  • 方案缺点: 代码量多,实现效果和方法三一样,仅做开拓思维用,所以选择方法三。

作者:张旭童 链接:http://www.jianshu.com/p/1ac13f74da63 來源:简书

Android Activity任务和返回栈

发表于 2017-08-24 | 分类于 Android

问题:homeActivity为正常启动模式activity,另一个activity为singleTask Activity.从package installers启动homeaty,再启动另一个activity,桌面,再点击桌面图标进入应用,会重新打开homeaty,而不是另一个activity.但是如果是从桌面首次进入home再进入另一个activity,切换到到桌面回来是在正常的另一个activity.同事发现homeaty, 另一个aty,桌面重新回来的homeaty, taskid相同.

解决办法是使用application记录front activity.

实例化launcher activity

这个问题表现:

  1. 在package installers 安装界面安装完一个应用后,直接打开app,然后进入了 Activity_1, 此时再通过此activity用startActivity(intent)的方法打开 Activity_2.

  2. 然后按home键返回桌面,在桌面点击app图标进入,你觉得应该进入的是 Activity_2 ,实际上却是launcher Activity_1 .

  3. 然而还没完,这时候你按 back 返回键,会发现返回到了之前打开的 Activity_2,再按返回,又出现 launcherActivity_1. 也就是说系统重复实例化了Activity_1.

  4. 退出app后再次点击桌面图标进入,反复试验,没有再出现这个问题。也就是说,这个问题(bug ?)只出现在操作步骤(1)后才会产生.

以上问题我在一些知名厂商的app 上发现也存在这个BUG :

百度云

陌陌

去哪儿旅行

…QQ没有出现这个问题

另外,如果以root方式静默安装的话不会出现这个问题,在eclipse里直接发布到模拟器上运行也没有出现这个问题

解决方案: 在super.onCreate(…)方法之后插入代码:

1
2
3
4
5
6
7
8
9
if(!this.isTaskRoot()) { //判断该Activity是不是任务空间的源Activity,“非”也就是说是被系统重新实例化出来  
//如果你就放在launcher Activity中话,这里可以直接return了
Intent mainIntent=getIntent();
String action=mainIntent.getAction();
if(mainIntent.hasCategory(Intent.CATEGORY_LAUNCHER) && action.equals(Intent.ACTION_MAIN)) {
finish();
return;//finish()之后该活动会继续执行后面的代码,你可以logCat验证,加return避免可能的exception
}
}

来源google : https://code.google.com/p/android/issues/detail?id=14262

https://code.google.com/p/android/issues/detail?id=2373#c40

还有另一种方案:

http://stackoverflow.com/questions/3042420/home-key-press-behaviour/4782423#4782423

以下引用自google官方文档

应用通常包含多个 Activity。每个 Activity 均应围绕用户可以执行的特定操作设计,并且能够启动其他 Activity。 例如,电子邮件应用可能有一个 Activity 显示新邮件的列表。用户选择某邮件时,会打开一个新 Activity 以查看该邮件。

一个 Activity 甚至可以启动设备上其他应用中存在的 Activity。例如,如果应用想要发送电子邮件,则可将 Intent 定义为执行“发送”操作并加入一些数据,如电子邮件地址和电子邮件。 然后,系统将打开其他应用中声明自己处理此类 Intent 的 Activity。在这种情况下,Intent 是要发送电子邮件,因此将启动电子邮件应用的“撰写”Activity(如果多个 Activity 支持相同 Intent,则系统会让用户选择要使用的 Activity)。发送电子邮件时,Activity 将恢复,看起来好像电子邮件 Activity 是您的应用的一部分。 即使这两个 Activity 可能来自不同的应用,但是 Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。

任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。

设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。

当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。 图 1 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。 image 图 1. 显示任务中的每个新 Activity 如何向返回栈添加项目。 用户按“返回”按钮时,当前 Activity 随即被销毁,而前一个 Activity 恢复执行。

如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。

任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。 尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如图 2 中所示。然后,任务可以返回到“前台”,用户就能够回到离开时的状态。 例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。 用户先按“主页”按钮,然后从应用启动器启动新应用。 显示主屏幕时,任务 A 进入后台。新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务 B)。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。 此时,用户还可以通过转到主屏幕并选择启动该任务的应用图标(或者,通过从概览屏幕选择该应用的任务)切换回任务 B。这是 Android 系统中的一个多任务示例。 image 图 2. 两个任务:任务 B 在前台接收用户交互,而任务 A 则在后台等待恢复。

注:后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。请参阅下面有关 Activity 状态的部分。

由于返回栈中的 Activity 永远不会重新排列,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。 因此,应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务),如图 3 所示。因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。 但是,如果您不希望 Activity 多次实例化,则可修改此行为。 具体操作方法将在后面的管理任务部分中讨论。 image 图 3. 一个 Activity 将多次实例化。

Activity 和任务的默认行为总结如下:

  • 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。
  • 用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。 系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。
  • 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。 堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统不会保留该 Activity 的状态。
  • 即使来自其他任务,Activity 也可以多次实例化。

保存 Activity 状态

正如上文所述,当 Activity 停止时,系统的默认行为会保留其状态。 这样一来,当用户导航回到上一个 Activity 时,其用户界面与用户离开时一样。 但是,在 Activity 被销毁且必须重建时,您可以而且应当主动使用回调方法保留 Activity 的状态。

系统停止您的一个 Activity 时(例如,新 Activity 启动或任务转到前台),如果系统需要回收系统内存资源,则可能会完全销毁该 Activity。 发生这种情况时,有关该 Activity 状态的信息将会丢失。如果发生这种情况,系统仍会知道该 Activity 存在于返回栈中,但是当该 Activity 被置于堆栈顶部时,系统一定会重建 Activity(而不是恢复 Activity)。 为了避免用户的工作丢失,您应主动通过在 Activity 中实现 onSaveInstanceState() 回调方法来保留工作。

如需了解有关如何保存 Activity 状态的详细信息,请参阅 Activity 文档。

管理任务

Android 管理任务和返回栈的方式(如上所述,即:将所有连续启动的 Activity 放入同一任务和“后进先出”堆栈中)非常适用于大多数应用,而您不必担心 Activity 如何与任务关联或者如何存在于返回栈中。 但是,您可能会决定要中断正常行为。 也许您希望应用中的 Activity 在启动时开始新任务(而不是放置在当前任务中);或者,当启动 Activity 时,您希望将其现有实例上移一层(而不是在返回栈的顶部创建新实例);或者,您希望在用户离开任务时,清除返回栈中除根 Activity 以外的所有其他 Activity。

通过使用 <activity> 清单文件元素中的属性和传递给 startActivity() 的 Intent 中的标志,您可以执行所有这些操作以及其他操作。

在这一方面,您可以使用的主要 <activity> 属性包括:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

您可以使用的主要 Intent 标志包括:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

在下文中,您将了解如何使用这些清单文件属性和 Intent 标志定义 Activity 与任务的关联方式,以及 Activity 在返回栈中的行为方式。

此外,我们还单独介绍了有关如何在概览屏幕中显示和管理任务与 Activity 的注意事项。 如需了解详细信息,请参阅概览屏幕。 通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。

注意:大多数应用都不得中断 Activity 和任务的默认行为: 如果确定您的 Activity 必须修改默认行为,当使用“返回”按钮从其他 Activity 和任务导航回到该 Activity 时,请务必要谨慎并确保在启动期间测试该 Activity 的可用性。请确保测试导航行为是否有可能与用户的预期行为冲突。

定义启动模式

启动模式允许您定义 Activity 的新实例如何与当前任务关联。 您可以通过两种方法定义不同的启动模式:

  1. 使用清单文件 在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。
  2. 使用 Intent 标志 调用 startActivity() 时,可以在 Intent 中加入一个标志,用于声明新 Activity 如何(或是否)与当前任务关联。

因此,如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义)。

注:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent 标志的启动模式无法在清单文件中定义。

使用清单文件

在清单文件中声明 Activity 时,您可以使用 <activity> 元素的 launchMode 属性指定 Activity 应该如何与任务关联。

launchMode 属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 launchMode 属性的启动模式共有四种:

  • “standard”(默认模式) 默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
  • “singleTop” 如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。 例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D 具有默认的 “standard” 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 “singleTop”,则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 “singleTop” 也是如此。

注:为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。 但是,当 Activity 的现有实例处理新 Intent 时,则在新 Intent 到达 onNewIntent() 之前,用户无法按“返回”按钮返回到 Activity 的状态。

  • “singleTask” 系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。

    注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。

  • “singleInstance”. 与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。

我们再来看另一示例,Android 浏览器应用声明网络浏览器 Activity 应始终在其自己的任务中打开(通过在 元素中指定 singleTask 启动模式)。这意味着,如果您的应用发出打开 Android 浏览器的 Intent,则其 Activity 与您的应用位于不同的任务中。相反,系统会为浏览器启动新任务,或者如果浏览器已有任务正在后台运行,则会将该任务上移一层以处理新 Intent。

无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”按钮始终会转到前一个 Activity。 但是,如果启动指定 singleTask 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。 图 4 显示了这种情况。 image 图 4. 显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。 如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。

如需了解有关在清单文件中使用启动模式的详细信息,请参阅 元素文档,其中更详细地讨论了 launchMode 属性和可接受的值。

注:使用 launchMode 属性为 Activity 指定的行为可由 Intent 附带的 Activity 启动标志替代,下文将对此进行讨论。

使用 Intent 标志

启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括:

  • FLAG_ACTIVITY_NEW_TASK 在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。 正如前文所述,这会产生与 “singleTask”launchMode 值相同的行为。

  • FLAG_ACTIVITY_SINGLE_TOP 如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 现有实例会接收对 onNewIntent() 的调用,而不是创建 Activity 的新实例。 正如前文所述,这会产生与 “singleTop”launchMode 值相同的行为。

  • FLAG_ACTIVITY_CLEAR_TOP 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。 产生这种行为的 launchMode 属性没有值。

    FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。

    注:如果指定 Activity 的启动模式为 “standard”,则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 “standard” 时,将始终为新 Intent 创建新实例。

处理关联

“关联”指示 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。 因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。 不过,您可以修改 Activity 的默认关联。 在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。

可以使用 元素的 taskAffinity 属性修改任何给定 Activity 的关联。

taskAffinity 属性取字符串值,该值必须不同于在 元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。

在两种情况下,关联会起作用:

  • 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志。 默认情况下,新 Activity 会启动到调用 startActivity() 的 Activity 任务中。它将推入与调用方相同的返回栈。 但是,如果传递给 startActivity() 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求。 如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。 否则,将开始新任务。 如果此标志导致 Activity 开始新任务,且用户按“主页”按钮离开,则必须为用户提供导航回任务的方式。 有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将 FLAG_ACTIVITY_NEW_TASK 放入传递给 startActivity() 的 Intent 中。请注意,如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务,例如,使用启动器图标(任务的根 Activity 具有 CATEGORY_LAUNCHER Intent 过滤器;请参阅下面的启动任务部分)。

  • Activity 将其 allowTaskReparenting 属性设置为 “true”。 在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。

    提示:如果从用户的角度来看,一个 .apk 文件包含多个“应用”,则您可能需要使用 taskAffinity 属性将不同关联分配给与每个“应用”相关的 Activity。

清理返回栈

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用下列几个 Activity 属性修改此行为:

  • alwaysRetainTaskState 如果在任务的根 Activity 中将此属性设置为 “true”,则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
  • clearTaskOnLaunch 如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
  • finishOnTaskLaunch 此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 “true” 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。

    启动任务

    通过为 Activity 提供一个以 “android.intent.action.MAIN” 为指定操作、以 “android.intent.category.LAUNCHER” 为指定类别的 Intent 过滤器,您可以将 Activity 设置为任务的入口点。 例如:
    1
    2
    3
    4
    5
    6
    7
    <activity ... >
    <intent-filter ... >
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
    </activity>

此类 Intent 过滤器会使 Activity 的图标和标签显示在应用启动器中,让用户能够启动 Activity 并在启动之后随时返回到创建的任务中。

第二个功能非常重要:用户必须能够在离开任务后,再使用此 Activity 启动器返回该任务。 因此,只有在 Activity 具有 ACTION_MAIN 和 CATEGORY_LAUNCHER 过滤器时,才应该使用将 Activity 标记为“始终启动任务”的两种启动模式,即 “singleTask” 和 “singleInstance”。例如,我们可以想像一下如果缺少过滤器会发生什么情况: Intent 启动一个 “singleTask” Activity,从而启动一个新任务,并且用户花了些时间处理该任务。然后,用户按“主页”按钮。 任务现已发送到后台,而且不可见。现在,用户无法返回到任务,因为该任务未显示在应用启动器中。

如果您并不想用户能够返回到 Activity,对于这些情况,请将 元素的 finishOnTaskLaunch 设置为 “true”(请参阅清理堆栈)。

有关如何在概览屏幕中显示和管理任务与 Activity 的更多信息,请参阅概览屏幕。

home后从service重新启动activity延迟问题

发表于 2017-08-24 | 分类于 Android

问题: 在Activity界面,按下HOME键后,点击悬浮层按钮,再启动Activity, Activity要延时5S后才出来。 经验证,这个问题不是应用自身的BUG。那怕该Activity是空的,也会有这个问题

Android为了避免应用在按下HOME键退出后还可以强制把自己启动,特意加的限制。 在现有的API情况下,不能解决这个问题,除非你的应用是一个启动器(Launcher), 添加了home/ launcher intent filter。如果你不是启动器,又要从悬浮层启动一个Activity,就把该Activity也改成悬浮层吧

1. 不从后台启动 Activity 准则:

在谷歌的 Android API Guides 中,特意提醒开发者不要在后台启动 activity,包括在 Service 和 BroadcastReceiver 中,这样的设计是为了避免在用户毫不知情的情况下突然中断用户正在进行的工作,在 http://developer.android.com/guide/practices/seamlessness.html#interrupt 中有如下解释:

That is, don’t call startActivity() from BroadcastReceivers or Services running in the background. Doing so will interrupt whatever application is currently running, and result in an annoyed user. Perhaps even worse, your Activity may become a “keystroke bandit” and receive some of the input the user was in the middle of providing to the previous Activity. Depending on what your application does, this could be bad news.

2. 需要违反“不从后台启动 Activity”准则的特例:

特例:即便如此,手机厂商的开发者们在开发基于系统级的应用的时候,可能仍然需要有从 Service 或 BroadcastReceiver 中 startActivity 的需求,往往这样的前提是连这样的 Service 或 BroadcastReceiver 也是由用户的某些操作而触发的,Service 或 BroadcastReceiver 只是充当了即将启动 activity 之前的一些代理参数检查工作以便决定是否需要 start 该 activity。

除非是上述笔者所述的特殊情况,应用开发者都应该遵循 “不要从后台启动 Activity”准则。

一个需要特别注意的问题是,特例中所述的情况还会遇到一个问题,就是当通过 home 键将当前 activity 置于后台时,任何在后台startActivity 的操作都将会延迟 5 秒,除非该应用获取了 “android.permission.STOP_APP_SWITCHES” 权限。

关于延迟 5 秒的操作在 com.android.server.am.ActivityManagerService 中的 stopAppSwitches() 方法中,系统级的应用当获取了 “android.permission.STOP_APP_SWITCHES” 后将不会调用到这个方法来延迟通过后台启动 activity 的操作,事实上 android 原生的 Phone 应用就是这样的情况,它是一个获取了”android.permission.STOP_APP_SWITCHES” 权限的系统级应用,当有来电时,一个从后台启动的 activity 将突然出现在用户的面前,警醒用户有新的来电,这样的设计是合理的。

所以,当你需要开发类似 Phone 这样的应用时,需要做如下工作:

  1. root 你的手机;
  2. 在 AndroidManifest.xml 中添加 “android.permission.STOP_APP_SWITCHES” 用户权限;
  3. 将你开发的应用程序 push 到手机的 /system/app 目录中。

3. 参考资料:

无缝的设计之——不要中断你的用户 stackoverflow 中关于后台 startActivity 被延迟 5 秒的探讨

Linux下C开发使用小技巧

发表于 2017-08-06 | 分类于 language

基础类

整形,字符串互转

C语言提供了几个标准库函数,可以将任意类型(整型、长整型、浮点型等)的数字转换为字符串,下面列举了各函数的方法及其说明。 ● itoa():将整型值转换为字符串。 ● ltoa():将长整型值转换为字符串。 ● ultoa():将无符号长整型值转换为字符串。 ● gcvt():将浮点型数转换为字符串,取四舍五入。 ● ecvt():将双精度浮点型值转换为字符串,转换结果中不包含十进制小数点。 ● fcvt():指定位数为转换精度,其余同ecvt()。

除此外,还可以使用sprintf系列函数把数字转换成字符串,其比itoa()系列函数运行速度慢

以下是用itoa()函数将整数转换为字符串的一个例子:

1
2
3
4
5
6
7
8
9
10
# include <stdio.h>
# include <stdlib.h>
void main (void)
{
int num = 100;
char str[25];
itoa(num, str, 10);
printf("The number 'num' is %d and the string 'str' is %s. \n" ,
num, str);
}

itoa()函数有3个参数:第一个参数是要转换的数字,第二个参数是要写入转换结果的目标字符串,第三个参数是转移数字时所用 的基数。在上例中,转换基数为10。10:十进制;2:二进制…

itoa并不是一个标准的C函数,它是Windows特有的,如果要写跨平台的程序,请用sprintf。是Windows平台下扩展的,标准库中有sprintf,功能比这个更强,用法跟printf类似:

1
2
char str[255];
sprintf(str, "%x", 100); //将100转为16进制表示的字符串。

cpp中string转int:

1
2
3
4
void str2int(int &int_temp,const string &string_temp)  
{
int_temp=atoi(string_temp.c_str());
}

cpp中int转string:

1
2
3
4
5
6
void int2str(const int &int_temp,string &string_temp)  
{
char s[12]; //设定12位对于存储32位int值足够
itoa(int_temp,s,10); //itoa函数亦可以实现,但是属于C中函数,在C++中推荐用流的方法
string_temp=s;
}

架构类

Linux C代码实现主函数参数选项解析

1. 手动解析版本

使用argc、argv,逐个字符比较,得到要想的参数名字即进行判断、解析。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int debug;

void show_version(char* name)
{
printf("%s by Late Lee, version: 1.0\n", name);
}

void usage(char* name)
{
show_version(name);

printf(" -h, --help short help\n");
printf(" -v, --version show version\n");
}

int main(int argc, char *argv[])
{
int i = 0;

/* early check for debug and config parameter */
if (argc > 1) {
for (i = 1; i < argc; i++)
{
if ((strcmp(argv[i], "-D")==0) || (strcmp(argv[i], "--debug")==0))
{
debug = 1;
}
}
}

//
/* parse parameters, maybe not the best way but... */
for (i = 1; i < argc; i++)
{
if (debug)
printf("arg %d: \"%s\"\n",i,argv[i]);
// help
if ((strcmp(argv[i],"-h")==0) || (strcmp(argv[i],"--help")==0))
{
usage(argv[0]);
return 0;
}
// version
else if ((strcmp(argv[i],"-v")==0) || (strcmp(argv[i],"--version")==0))
{
show_version(argv[0]);
return 0;
}
// debug
else if ((strcmp(argv[i],"-D")==0) || (strcmp(argv[i],"--debug")==0))
{
debug=1;
}
else if ((strcmp(argv[i],"fpga")==0))
{
printf("test of fpga...\n");
}
// string
else if ((strcmp(argv[i],"-i")==0) || (strcmp(argv[i],"--iface")==0))
{
if (i+1<argc)
{
char interface[32] = {0};
strncpy(interface, argv[i+1], 32);
if (debug)
printf("Used interface: %s\n", interface);
i++;
continue;
} else {
printf("Error: Interface for -i missing.\n");
return 1;
}
}
// number
else if ((strcmp(argv[i],"-ru")==0) || (strcmp(argv[i],"--rateunit"))==0)
{
if (i+1<argc && isdigit(argv[i+1][0])) {
int rateunit = 0;
rateunit = atoi(argv[i+1]);
if (rateunit < 0 || rateunit > 1)
{
printf("Error: Invalid parameter \"%d\" for --rateunit.\n", rateunit);
printf(" Valid parameters:\n");
printf(" 0 - bytes\n");
printf(" 1 - bits\n");
return 1;
}
if (debug)
printf("Rateunit changed: %d\n", rateunit);
i++;
continue;
}
else
{
}
}
// only one
else if (strcmp(argv[i],"--enable")==0)
{
int enable = 0;
enable = 1;
}
else
{
printf("Unknown parameter \"%s\". Use --help for help.\n",argv[i]);
return 1;
}
}
}
2. 利用getopt函数完成
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
解析命令选项示例

#include <unistd.h>
extern char *optarg; //选项的参数指针
extern int optind, //下一次调用getopt的时,从optind存储的位置处重新开始检查选项。
extern int opterr, //当opterr=0时,getopt不向stderr输出错误信息。
extern int optopt; //当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,该选项存储在optopt中,getopt返回'?’、
int getopt(int argc, char * const argv[], const char *optstring);

使用:
$ ./a.out -Wall -o hello.c

*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

int debug_level = 0;

#define _AUTHOR "Late Lee"
#define _VERSION_STR "1.0"
#define _DATE ""

// 默认打印error等级
enum
{
MSG_ERROR = 0,
MSG_WARNING,
MSG_INFO,
MSG_DEBUG,
MSG_MSGDUMP,
MSG_EXCESSIVE,
};

void ll_printf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));

void ll_printf(int level, const char *fmt, ...)
{
va_list ap;

va_start(ap, fmt);
if (debug_level >= level)
{
#ifdef CONFIG_DEBUG_SYSLOG
if (wpa_debug_syslog) {
vsyslog(syslog_priority(level), fmt, ap);
} else {
#endif /* CONFIG_DEBUG_SYSLOG */
//debug_print_timestamp();
#ifdef CONFIG_DEBUG_FILE
if (out_file) {
vfprintf(out_file, fmt, ap);
fprintf(out_file, "\n");
} else {
#endif /* CONFIG_DEBUG_FILE */
vprintf(fmt, ap);
printf("\n");
#ifdef CONFIG_DEBUG_FILE
}
#endif /* CONFIG_DEBUG_FILE */
#ifdef CONFIG_DEBUG_SYSLOG
}
#endif /* CONFIG_DEBUG_SYSLOG */
}
va_end(ap);
}

void show_version(char* name)
{
printf("%s by %s, version: %s\n", name, _AUTHOR, _VERSION_STR);
}

void usage(char* name)
{
show_version(name);

printf(" -h, short help\n");
printf(" -v, show version\n");
printf(" -d, debug level\n");

exit(0);
}

const char* my_opt = "hOo:W:d:";

int main(int argc, char *argv[])
{
int c;
const char* p1 = NULL;
const char* p2 = NULL;
const char* p3 = NULL;
while(1)
{
c = getopt(argc, argv, my_opt);
printf("optind: %d\n", optind);
if (c < 0)
{
break;
}
printf("option char: %x %c\n", c, c);
switch(c)
{
case 'd':
debug_level = atoi(optarg);
printf("debug level: %d\n", debug_level);
break;
case 'O':
printf("optimization flag is open.\n\n");
break;
case 'o':
printf("the obj is: %s\n\n", optarg);
p1 = optarg;
break;
case 'W':
printf("optarg: %s\n\n", optarg);
p2 = optarg;
break;
case ':':
fprintf(stderr, "miss option char in optstring.\n");
break;
case '?':
case 'h':
default:
usage(argv[0]);
break;
//return 0;
}
}
if (optind == 1)
{
usage(argv[0]);
}

ll_printf(MSG_ERROR, "p1: %s p2: %s\n", p1, p2);

return 0;
}

使用 getopt() 进行命令行处理

网络模块

日志模块

读取配置文件模块

内存池模块

缓存库模块

文件系统模块

管理后台模块

数据库模块

技巧类

Linux程序中预定义的几个调试宏

Linux下C语言编程中有几个很实用的调试宏

1
__LINE__ __FILE__  __FUNCTION__ __TIME__ __DATA__

这几个预定义宏是属于ANSI标准的,内置于编译器,全局性的变量,可以方便地实现代码跟踪调试,不是在哪个头文件中包含的,见下例:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
printf("The file is %s.\n",__FILE__);
printf( "The date is %s.\n", __DATE__ );
printf( "The time is %s.\n", __TIME__ );
printf( "This is line %d.\n", __LINE__ );
printf( "This function is %s.\n", __FUNCTION__ );
return 0;
}

运行结果:

1
2
3
4
5
The file is macro.c.
The date is Aug 24 2012.
The time is 23:13:26.
This is line 8.
This function is main.

line 行数 文件名指令可以改变它的值,简单的讲,编译时,它们包含程序的当前行数和文件名。

DATE 宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。 TIME 宏指令包含程序编译的时间。时间用字符串表示,其形式为时:分:秒

__func__代表这条语句所在的函数的函数名

联合体用途

字节序有两种表示方法:大端法(big ending),小端法(little ending)。网络字节序采用的是大端法。主机字节序不同的CPU采用的方法不一样,可以通过代码来查看自己主机的字节序。

  • 大端法:高位字节排放在内存低地址端,低位字节排放在内存的高地址端。
  • 小端法:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 看一个unsigned short 数据,它占2个字节,给它赋值0x1234。
  • 若采用的大端法,则其低地址端应该存放的是0x12;
  • 若采用的小端法,则其低地址端应该存放的是0x34; 可以通过联合体来获得其高低地址的数据。测试主机字节序的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    typedef union{
    unsigned short value;
    unsigned char bytes[2];
    }Test;

    int main(void)
    {
    Test test_value;
    test_value.value = 0x1234;
    if(test_value.bytes[0] == 0x12 && test_value.bytes[1] == 0x34)
    {
    printf("big ending");
    }
    else if(test_value.bytes[0] == 0x34 && test_value.bytes[1] == 0x12)
    {
    printf("little ending");
    }else{
    printf("use test_value error");
    }
    return 0;
    }

工具类

自定义日志的调试打印信息

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
#define TRACE_NONE      0
#define TRACE_FATAL 1
#define TRACE_ERROR 2
#define TRACE_WARNING 3
#define TRACE_INFO 4
#define TRACE_DEBUG 5

#define TRACE_LEN_MAX 64

extern int *TraceLevel;
extern char TraceName[TRACE_LEN_MAX + 1];

#define Log(A, format,args...) \
((TraceLevel == NULL || TraceName == NULL || *TraceLevel < (A)) ? 0 : LogMsg(A, __FILE__, __LINE__, format, ##args))

#define LogFatal(format,args...) \
Log(TRACE_FATAL, format, ##args)
#define LogError(format,args...) \
Log(TRACE_ERROR, format, ##args)
#define LogWarning(format,args...) \
Log(TRACE_WARNING, format, ##args)
#define LogInfo(format,args...) \
Log(TRACE_INFO, format, ##args)
#define LogDebug(format,args...) \
Log(TRACE_DEBUG, format, ##args)
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
int LogMsg(int level, const char *filename,
int line, const char *fmt, ...)
{
va_list ap;
FILE *fp;
char sLogFile[128 + 1];
char sCurrTime[6 + 1];
struct timeb tTimeB;
char sMilliTM[4];

memset(sLogFile, 0, sizeof(sLogFile));
LogFile(sLogFile);
GetTime_HHMMSS(sCurrTime);
memset(&tTimeB, 0, sizeof(tTimeB));
ftime(&tTimeB);
snprintf(sMilliTM, sizeof(sMilliTM), "%03d", tTimeB.millitm);

fp = fopen(sLogFile, "a+");
if (fp != (FILE*)NULL) {
fprintf(fp, "[%08d][%.6s:%.3s][%16s][%04d][%7s]",
getpid(), sCurrTime, sMilliTM, filename, line, g_LevelDsp[level]);
va_start(ap, fmt);
vfprintf(fp, fmt, ap);
va_end(ap);
fprintf(fp, "\n");
fflush(fp);
fclose(fp);
}

return 0;
}

再在后台进程中设置TraceLevel和TraceName即可。

获取当前系统日期、时间

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
/*****************************************************************************
** 函数名称: GetDate
** 功能描述: 取当前系统日期
** 当前版本: 1.0.0.0
** 作 者:
** 修 改:
** 输入参数:
** 输出参数: char * psDate -- 系统日期, 格式为yyyymmdd
** 返回结果:int
0 ---> 成功
****************************************************************************/
int GetDate(char * psDate)
{
time_t nSeconds;
struct tm * pTM;

time(&nSeconds);
pTM = localtime(&nSeconds);

/* 系统日期, 格式:YYYYMMDD */
sprintf( psDate,"%04d%02d%02d",
pTM->tm_year + 1900, pTM->tm_mon + 1,pTM->tm_mday );

return 0;
}
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
/*****************************************************************************
** 函数名称: GetTime
** 功能描述: 取当前系统时间
** 当前版本: 1.0.0.0
** 作 者:
** 修 改:
** 输入参数:
** 输出参数: char * psTime -- 系统时间, 格式为HHMMSS
** 返回结果:int
0 ---> 成功
****************************************************************************/
int GetTime(char * psTime)
{
time_t nSeconds;
struct tm * pTM;

time(&nSeconds);
pTM = localtime(&nSeconds);

/* 系统时间, 格式:HHMMSS */
sprintf( psTime,"%02d%02d%02d",
pTM->tm_hour,pTM->tm_min, pTM->tm_sec);

return 0;
}
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
/*****************************************************************************
** 函数名称: GetDateTime
** 功能描述: 取当前系统日期和时间
** 当前版本: 1.0.0.0
** 作 者:
** 修 改:
** 输入参数:
** 输出参数: char * psDateTime -- 系统日期时间, 格式为yyyymmddHHMMSS
** 返回结果:int
0 ---> 成功
****************************************************************************/
int GetDateTime(char * psDateTime)
{
time_t nSeconds;
struct tm * pTM;

time(&nSeconds);
pTM = localtime(&nSeconds);

/* 系统日期和时间, 格式:yyyymmddHHMMSS */
sprintf( psDateTime,"%04d%02d%02d%02d%02d%02d",
pTM->tm_year + 1900, pTM->tm_mon + 1,pTM->tm_mday,
pTM->tm_hour,pTM->tm_min, pTM->tm_sec );

return 0;
}

调用的时候定义一个char数组,大小为日期的长度大小加1,然后直接调用上面的函数,参数为数组名即可。   当然,还有其他许多关于日期、时间操作的函数,比如不同日期、时间格式间的转换等。

FQA

发表于 2017-07-25
ssh连接服务器出错
1
2
3
4
5
6
7
8
9
10
11
12
13
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
da:f7:3e:ba:f7:00:e6:44:76:f2:58:6e:48:**.
Please contact your system administrator.
Add correct host key in /用户home目录/.ssh/known_hosts to get rid of this message.
Offending RSA key in /用户home目录/.ssh/known_hosts:1
RSA host key for ip地址 has changed and you have requested strict checking.
Host key verification failed.

出现这个问题的原因是,第一次使用SSH连接时,会生成一个认证,储存在客户端的known_hosts中.

可使用以下指令查看:

1
ssh-keygen -l -f ~/.ssh/known_hosts

由于服务器重新安装系统了,所以会出现以上错误。 解决办法:

1
ssh-keygen -R 服务器端的ip地址

或者直接在known_hosts中删除对应ip的行

1…141516…19
轻口味

轻口味

190 日志
27 分类
63 标签
RSS
GitHub 微博 豆瓣 知乎
友情链接
  • SRS
© 2015 - 2019 轻口味
京ICP备17018543号
本站访客数 人次 本站总访问量 次