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

活到81岁你会干嘛?选择为 iOS 开发软件的她厉害了!

$
0
0


《活到81岁你会干嘛?选择为 iOS 开发软件的她厉害了!》

无论在哪个国家——哪怕是在计算机产业最为发达的美国,老人熟练使用电脑的现象都很少出现。但是,你千万不要以年纪来判断一个人的电脑技术,因为一个比你年长两倍甚至三倍的人,很有可能是一名高手。比如说来自日本的,她是一名
iOS 开发者,今年已经 81 岁了,她从 60 岁才开始使用电脑。

这里写图片描述

学习使用电脑之前,Masako 在日本的一家银行工作了 43 年的时间,与电脑技术基本上没有交集。不久之前,Masako 推出了她自己的首款 iOS 应用Hinadan,这款应用主要是为了展示日本当地的女孩节,也就是“雏祭”正确摆放传统娃娃的方式。在应用里,玩家会看到四层架子,总共 12个娃娃,正确摆放之后游戏就结束。

这里写图片描述

据了解,Masako 使用的编程技术是苹果的 Swift,这是由一位“年轻人”通过网络通信工具向她传授的。她透露,Hinadan当中的图片是由朋友在微软的 Office套件中完成的。“我之所以会开发这款软件,主要是因为很多智能手机软件是为年轻人设计的,基本上没有老人什么事,我希望可以激励其它老人也开始体验使用电脑的乐趣。”


7天玩转iOS界面开发

讲师:张益珲

一套系统的iOS UI开发课程,基本涵盖iOS开发中所用到的全部系统UI控件,在详细讲解基础运用了基础上,更层次的分析高级用法,对于有编程基础但不是iOS开发者的程序员转iOS开发提供便捷的学习路径。同样,对于没有开发基础,有Objective-C基础的同学,也是一套很好的使用教程。

►开始学习


iOS从零基础到精通就业 UI基础

讲师:栾斌

本课程涵盖UIKit框架大多数基础常用控件的使用,以及页面跳转,页面传值,可视化编程等项目开发中常用技术。
【课程目标】

►开始学习


IOS开发视频教程《保卫萝卜》

讲师:任亮

通过本课程,大家可以学习IOS知识,了解游戏开发过程。《保卫萝卜CarrotFantasy》是一款由开发商“凯罗天下”开发的超萌塔防小。14种防御塔保卫萝卜战怪兽。保卫萝卜是一款制作精美的超萌塔防游戏,游戏含有丰富的关卡和主题包,拥有各自风格特色的多种防御塔,有趣的音效设定和搞怪的怪物造型及名字大大地增加了游戏的趣味性。

►开始学习


最完善的Swift3从入门到精通视频课程(从语法到实战)

讲师:张益珲

Swift作为一门新兴的编程语言,其集合了许多现代编程语言的优势,由于其开源的特性,其应用领域也将越来越广泛。本课程从零开始,介绍语言的运行环境,基本语法,高级用法并以iOS实战应用的方式带领读者对Swift语言从入门到精通。

►开始学习


iOS开发-全面解析iOS蓝牙BLE4.0开发

讲师:许英俊

只要你会OC基础,会写HelloWorld,你就可以实现iOS的蓝牙通信功能,实现蓝牙小项目也不在话下,本课程会带领大家详细分析BLE4.0原理,通过分析xcode使用的蓝牙API,让学者能够得心应手的实现蓝牙BLE4.0的开发

►开始学习


[更多相关文章]

作者:blogdevteam 发表于2017/4/11 17:16:00 原文链接
阅读:557 评论:0 查看评论

2016年度中国手游报告:梦幻西游手游是最大赢家

$
0
0


《2016年度中国手游报告:梦幻西游手游是最大赢家》

移动游戏企业家联盟发布的《2016全球移动游戏产业白皮书》报告显示,2016年全球移动游戏市场达到369亿美元,占全体游戏市场37%市场份额,其中,中国、美国、德国、俄罗斯以及巴西作为全球各地区游戏大国,一直是全球游戏市场的中坚力量,那么在过去的2016年,在中国,哪些游戏在称王称霸,整个市场发展趋势又如何?

►阅读全文


Cocos3D与Shader从入门到精通

讲师:卞安

本课程是由知名Cocos2d-x专家“火云红孩儿”发布的《Cocos2d-x开发3D游戏》与《Shader从入门到精通》视频教程。

►开始学习


cocos2d-x高级渲染技术

讲师:传智

本课程讲述cocos2d-x中的渲染技巧,包括混合、裁减等,还不快来为自己get新技能。

►开始学习


游戏引擎初级篇

讲师:张立铜

本套餐课程使用标准C++语言 + OpenGLES2.0规范,进行跨平台开发,支持多系统。 从无到有介绍游戏引擎开发中用到的常用技术,从三维理论开始,到高级的算法,场景提出算法,摄像机控制,路径动画,关键帧动画,骨骼动画,地形的编辑,地形的刷绘,GUI界面原型系统等方面进行详细的介绍。

►开始学习


移动端游戏技术分享

讲师:姜雪伟

该套餐主要是针对移动端游戏开发技术讲解,移动端海水实现,实时阴影,3D残影实现。

►开始学习


Cocos2d-x 实战演练基础篇

讲师:王磊

本课程结合一个实例连连看游戏,讲解cocos2d-x最新3.6版引擎的基础知识和实际用法。

►开始学习


[更多相关文章]

作者:blogdevteam 发表于2017/4/11 17:20:50 原文链接
阅读:329 评论:0 查看评论

Android安全防护之旅---Android应用"反调试"操作的几种方案解析

$
0
0

一、前言

在之前介绍了很多破解相关的文章,在这个过程中我们难免会遇到一些反调试策略,当时只是简单的介绍了如何去解决反调试,其实在去年我已经介绍了一篇关于Android中的安全逆向防护之战的文章:Android安全逆向防护解析;那么这篇文章就来详细总结一下,现阶段比较流行的几种反调试解决方案。


二、反调试策略方案

第一种:先占坑,自己附加

代码非常简单,在so中加上这行代码即可:ptrace(PTRACE_TRACEME, 0, 0, 0);

其中PTRACE_TRACEME代表:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程,一般一个进程只能被附加一次,我们在破解调试的时候都会附加需要调试应用的进程,如果我们先占坑,父进程附加自己,那么后面在附加调试就会失败。加上这段代码我们运行之后看一下效果:


我们在进行破解动态调试的时候都知道附加进程的status文件中的TracerPid字段就是被调试的进程pid,这里我们运行程序之后,查看进程对应的status文件,发现TracerPid值就是进程的父进程pid值。那么后面如果有进程在想附加调试就是会失败的。这种方式启动一定的调试作用,但是不是绝对安全的。关于解决这种反调试方案后面再说。


第二种:签名校验

其实签名校验,准备来说不算是反调试方案,但是也是一种安全防护策略,就在这里提一下了,而签名校验一般现在有很多用途,用意在于防止二次打包,一般方案有两种:

第一:直接在本地做防护,如果发现签名不一致直接退出应用

第二:将签名信息携带请求参数中参与加密,服务端进行签名校验,失败就返回错误数据即可

而这两种方式也都不是最安全的防护,因为只要有签名校验的逻辑,在本地都可以进行过滤掉。而在之前的好几篇文章中都介绍了如何过滤这种签名校验的方法,不了解的同学可以去查看:Android中破解某应用的签名校验;而对于服务器签名校验以及将签名校验放到so中的文章后面会单独在介绍一篇。


第三种:调试状态检查

这种方式是纯属借助Android中的api进行检验,有两种方法:

第一:检查应用是否属于debug模式

直接调用Android中的flag属性:ApplicationInfo.FLAG_DEBUGGABLE,判断是否属于debug模式:


这个其实就是为了防止现在破解者为了调试应用将应用反编译在AndroidManifest.xml中添加:android:debuggable属性值,将其设置true。然后就可以进行调试。

添加这个属性之后,我们可以用 dumpsys package [packagename] 命令查看debug状态:


所以我们可以检查应用的AppliationInfo的flag字段是否为debuggable即可。不过这种方式也不是万能的,后面会介绍如何解决这种反调试问题。


第二:检查应用是否处于调试状态

这个也是借助系统的一个api来进行判断:android.os.Debug.isDebuggerConnected();这个就是判断当前应用有没有被调试,我们加上这段代码之后,按照之前的那篇文章:脱掉360加固保护壳,其中有一个步骤进行jdb连接操作:

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700,当连接成功之后,这个方法就会返回true,那么我们可以利用这个api来进行判断当前应用是否处于调试状态来进行反调试操作。但是这种方式不是万能的,后面会介绍如何解决这种反调试问题。


第四种:循环检查端口

我们在之前破解逆向的时候,需要借助一个强大利器,那就是IDA,在使用IDA的时候,我们知道需要在设备中启动android_server作为通信,那么这个启动就会默认占用端口23946:


我们可以查看设备的tcp端口使用情况 cat /proc/net/tcp


其中5D8A转化成十进制就是23946,而看到uid是0,因为我们运行android_server是root身份的,uid肯定是0了。所以我们可以利用端口检查方式来进行反调试策略,当然这种方式不是万能的,后面会详细介绍如何解决这样的反调试方法。


第五种:循环检查TracerPid值

在第一种方式中,我们简单的介绍了如果应用被调试了,那么他的TracerPid值就是调试进程的pid值,而在使用IDA进行调试的时候,需要在设备端启动android_server进行通信,那么被调试的进程就会被附加,这就是android_server进程的pid值了:


查看一下android_server的pid值:


所以我们可以在自己的应用中的native层加上一个循环检查自己status中的TracerPid字段值,如果非0或者是非自己进程pid(如果采用了第一种方案的话,这里也是需要做一次过滤的);那么就认为被附加调试了。当然这里还有一种方案,就是可以检查进程列表中有没有android_server进程,不过这种方式都不是万能的,后面会详细介绍如何解决这种反调试方案。


三、方案策略总结

下面简单几句话总结这几种方案:

第一、自己附加进程,先占坑,ptrace(PTRACE_TRACEME, 0, 0, 0)!

第二、签名校验不可或缺的一个选择,本地校验和服务端校验双管齐下!

第三、借助系统api判断应用调试状态和调试属性,最基础的防护!

第四、轮训检查android_server调试端口信息和进程信息,防护IDA的一种有效方式!

第五、轮训检查自身status中的TracerPid字段值,防止被其他进程附加调试的一种有效方式!


上面就简单的介绍了现在流行的几种应用反调试策略方案,这几种方案可以全部使用,也可以采用几种使用,但是要记住一点,就是如果要做到更安全点,记得把反调试方案放到native层中,时机最早,一般在JNI_OnUnload函数里面,为了更安全点,native中的函数可以自己手动注册,函数名自己混淆一下也是可以的。具体可参见这篇文章:Android安全逆向防护解析。现在一些加固平台为了更有效的防护,启动的多进程之间的防护监听,多进程一起参与反调试方案,这种方式对于破解难度就会增大,但是也不是绝对安全的。文章中对于每种方式最后都说到了,都不是万能安全的,都有方法解决,而这内容放到下一篇来详细介绍了。


反调试方案策略代码下载:https://github.com/fourbrother/android_anti_debug


四、总结

本文主要介绍了Android中应用在进行反调试反破解的几种方案,对于每种方案进行了详细原理分析,代码也给出了下载地址,可以自行运行看效果,而对于这几种反调试方案并非是绝对安全的,后面会再详细介绍如何解决这些反调试功能,但是为了应用安全,这几种方案也不可以不用,有总比没有好!最后读完文章,记得多多点赞分享扩散,要是有打赏就最好啦啦!


更多内容:点击这里

关注微信公众号,最新技术干货实时推送


编码美丽技术圈
微信扫一扫进入我的"技术圈"世界


扫一扫加小编微信
添加时请注明:“编码美丽”非常感谢!

作者:jiangwei0910410003 发表于2017/4/11 18:44:21 原文链接
阅读:1929 评论:1 查看评论

Centos7 安装mysql

$
0
0

NET Core 第一部分 : 搭建你的服器器


NET Core 第二部分 : 搭建你的服器器
- NET Core helloworld

复制下载mysql 官方下载地址

1.png

2.png

安装mysql

  • 输入命令 下载mysql的repo源
 wget https://repo.mysql.com//mysql57-community-release-el7-10.noarch.rpm
  • 输入命令 安装mysql-community-release-el7-5.noarch.rpm包
 sudo rpm -ivh mysql57-community-release-el7-10.noarch.rpm
  • 输入命令 安装mysql
 $ sudo yum install mysql-server
  • 输入命令 查看MySQL的启动状态
 systemctl status mysqld
  • 输入命令 启动MySQL服务
sudo systemctl start mysqld
  • 输入命令 查看MySQL的启动状态
 systemctl status mysqld

修改密码

  • 输入命令 查看初始密码
  grep 'temporary password' /var/log/mysqld.log

1.png

  • 输入命令 登录myql账号
  mysql -uroot -p
  • 输入命令 设置新的密码
   set password for 'root'@'localhost'=password('MyNewPass4!'); 
  • 输入命令 添加远程登录用户
 GRANT ALL PRIVILEGES ON *.* TO 'youusr'@'%' IDENTIFIED BY 'Youusr0917!' WITH GRANT OPTION;

set password for ‘root’@’localhost’=password(‘MyNewPass4!’);

- 输入命令   **退出mysql**

quit;


## 配置默认编码为utf8
- 输入命令   **修改/etc/my.cnf配置文件**

sudo vi /etc/my.cnf

修改如下:
[mysqld]
character_set_server=utf8
init_connect='SET NAMES utf8'

##  SQLyog客户端远程连接mysqsl
 如果你不能远程连接mysql,可以尝试一下命令

- 输入命令   **登录myql账号**

mysql -uroot -p

- 输入命令   **开放3306端口**

sudo vi /etc/sysconfig/iptables
“`

作者:yucihai 发表于2017/4/12 0:21:03 原文链接
阅读:294 评论:0 查看评论

FFmpeg总结(五)AV系列结构体之AVCodec、AVCodecParameters、AVCodecParser、AVCodecParserContext、AVCodecDescriptor

$
0
0


这里写图片描述

AVCodec: 编解码器结构体
位于libavcodec/avcodec.h中

typedef struct AVCodec {

    const char *name; // codec的名字,保持全局唯一,标识名

    const char *long_name; // codec的名字,全名
    enum AVMediaType type; // Media类型,是视频,音频,还是字幕
    enum AVCodecID id;

    int capabilities; //  codec的容量,参考 AV_CODEC_CAP_*
    const AVRational *supported_framerates; //支持的帧率,如果是null,返回是{0,0}
    const enum AVPixelFormat *pix_fmts;  //支持的像素格式,如果是null或unknown,返回-1
    const int *supported_samplerates;     //支持的采样率,如果是null或unknown,返回0  
    const enum AVSampleFormat *sample_fmts;  //支持的采样率,如果是null,返回-1 
    const uint64_t *channel_layouts;     //支持的声道数, 如果是null,返回0 
    uint8_t max_lowres;                  //解码器支持的最大lowres  
    const AVClass *priv_class;    //定义AVClass 成员变量   
    const AVProfile *profiles;    //定义AVProfile 成员变量          

    /*****************************************************************
     * No fields below this line are part of the public API. They
     * may not be used outside of libavcodec and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */
    int priv_data_size;
    struct AVCodec *next;
    /**
     * @name Frame-level threading support functions
     * @{
     */
    /**
     * If defined, called on thread contexts when they are created.
     * If the codec allocates writable tables in init(), re-allocate them here.
     * priv_data will be set to a copy of the original.
     */
    int (*init_thread_copy)(AVCodecContext *);
    /**
     * Copy necessary context variables from a previous thread context to the current one.
     * If not defined, the next thread will start automatically; otherwise, the codec
     * must call ff_thread_finish_setup().
     *
     * dst and src will (rarely) point to the same context, in which case memcpy should be skipped.
     */
    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
    /** @} */

    /**
     * Private codec-specific defaults.
     */
    const AVCodecDefault *defaults;

    /**
     * Initialize codec static data, called from avcodec_register().
     */
    void (*init_static_data)(struct AVCodec *codec);

    int (*init)(AVCodecContext *);
    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
                      const struct AVSubtitle *sub);
    /**
     * Encode data to an AVPacket.
     *
     * @param      avctx          codec context
     * @param      avpkt          output AVPacket (may contain a user-provided buffer)
     * @param[in]  frame          AVFrame containing the raw data to be encoded
     * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a
     *                            non-empty packet was returned in avpkt.
     * @return 0 on success, negative error code on failure
     */
    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
                   int *got_packet_ptr);
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
    int (*close)(AVCodecContext *);
    /**
     * Decode/encode API with decoupled packet/frame dataflow. The API is the
     * same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except
     * that:
     * - never called if the codec is closed or the wrong type,
     * - AVPacket parameter change side data is applied right before calling
     *   AVCodec->send_packet,
     * - if AV_CODEC_CAP_DELAY is not set, drain packets or frames are never sent,
     * - only one drain packet is ever passed down (until the next flush()),
     * - a drain AVPacket is always NULL (no need to check for avpkt->size).
     */
    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
    int (*send_packet)(AVCodecContext *avctx, const AVPacket *avpkt);
    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
    /**
     * Flush buffers.
     * Will be called when seeking
     */
    void (*flush)(AVCodecContext *);
    /**
     * Internal codec capabilities.
     * See FF_CODEC_CAP_* in internal.h
     */
    int caps_internal;
} AVCodec;

AVCodecParameters:描述一个解码后的流的属性
sizeof(AVCodecParameters),并不是public的api, 这个结构必须通过avcodec_parameters_alloc()分配空间,通过avcodec_parameters_free()释放空间。

typedef struct AVCodecParameters {
    /**
     * General type of the encoded data.
     */
    enum AVMediaType codec_type;
    /**
     * Specific type of the encoded data (the codec used).
     */
    enum AVCodecID   codec_id;
    /**
     * Additional information about the codec (corresponds to the AVI FOURCC).
     */
    uint32_t         codec_tag;

    /**
     * Extra binary data needed for initializing the decoder, codec-dependent.
     *
     * Must be allocated with av_malloc() and will be freed by
     * avcodec_parameters_free(). The allocated size of extradata must be at
     * least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding
     * bytes zeroed.
     */
    uint8_t *extradata;
    /**
     * Size of the extradata content in bytes.
     */
    int      extradata_size;

    /**
     * - video: the pixel format, the value corresponds to enum AVPixelFormat.
     * - audio: the sample format, the value corresponds to enum AVSampleFormat.
     */
    int format;

    /**
     * The average bitrate of the encoded data (in bits per second).
     */
    int64_t bit_rate;

    /**
     * The number of bits per sample in the codedwords.
     *
     * This is basically the bitrate per sample. It is mandatory for a bunch of
     * formats to actually decode them. It's the number of bits for one sample in
     * the actual coded bitstream.
     *
     * This could be for example 4 for ADPCM
     * For PCM formats this matches bits_per_raw_sample
     * Can be 0
     */
    int bits_per_coded_sample;

    /**
     * This is the number of valid bits in each output sample. If the
     * sample format has more bits, the least significant bits are additional
     * padding bits, which are always 0. Use right shifts to reduce the sample
     * to its actual size. For example, audio formats with 24 bit samples will
     * have bits_per_raw_sample set to 24, and format set to AV_SAMPLE_FMT_S32.
     * To get the original sample use "(int32_t)sample >> 8"."
     *
     * For ADPCM this might be 12 or 16 or similar
     * Can be 0
     */
    int bits_per_raw_sample;

    /**
     * Codec-specific bitstream restrictions that the stream conforms to.
     */
    int profile;
    int level;

    /**
     * Video only. The dimensions of the video frame in pixels.
     */
    int width;
    int height;

    /**
     * Video only. The aspect ratio (width / height) which a single pixel
     * should have when displayed.
     *
     * When the aspect ratio is unknown / undefined, the numerator should be
     * set to 0 (the denominator may have any value).
     */
    AVRational sample_aspect_ratio;

    /**
     * Video only. The order of the fields in interlaced video.
     */
    enum AVFieldOrder                  field_order;

    /**
     * Video only. Additional colorspace characteristics.
     */
    enum AVColorRange                  color_range;
    enum AVColorPrimaries              color_primaries;
    enum AVColorTransferCharacteristic color_trc;
    enum AVColorSpace                  color_space;
    enum AVChromaLocation              chroma_location;

    /**
     * Video only. Number of delayed frames.
     */
    int video_delay;

    /**
     * Audio only. The channel layout bitmask. May be 0 if the channel layout is
     * unknown or unspecified, otherwise the number of bits set must be equal to
     * the channels field.
     */
    uint64_t channel_layout;
    /**
     * Audio only. The number of audio channels.
     */
    int      channels;
    /**
     * Audio only. The number of audio samples per second.
     */
    int      sample_rate;
    /**
     * Audio only. The number of bytes per coded audio frame, required by some
     * formats.
     *
     * Corresponds to nBlockAlign in WAVEFORMATEX.
     */
    int      block_align;
    /**
     * Audio only. Audio frame size, if known. Required by some formats to be static.
     */
    int      frame_size;

    /**
     * Audio only. The amount of padding (in samples) inserted by the encoder at
     * the beginning of the audio. I.e. this number of leading decoded samples
     * must be discarded by the caller to get the original audio without leading
     * padding.
     */
    int initial_padding;
    /**
     * Audio only. The amount of padding (in samples) appended by the encoder to
     * the end of the audio. I.e. this number of decoded samples must be
     * discarded by the caller from the end of the stream to get the original
     * audio without any trailing padding.
     */
    int trailing_padding;
    /**
     * Audio only. Number of samples to skip after a discontinuity.
     */
    int seek_preroll;
} AVCodecParameters;

AVCodecParser

typedef struct AVCodecParser {
    int codec_ids[5]; /* several codec IDs are permitted */
    int priv_data_size;
    int (*parser_init)(AVCodecParserContext *s);
    /* This callback never returns an error, a negative value means that
     * the frame start was in a previous packet. */
    int (*parser_parse)(AVCodecParserContext *s,
                        AVCodecContext *avctx,
                        const uint8_t **poutbuf, int *poutbuf_size,
                        const uint8_t *buf, int buf_size);
    void (*parser_close)(AVCodecParserContext *s);
    int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size);
    struct AVCodecParser *next;
} AVCodecParser;

AVCodecParserContext :Frame parsing

typedef struct AVCodecParserContext {
    void *priv_data;
    struct AVCodecParser *parser;
    int64_t frame_offset; /* offset of the current frame */
    int64_t cur_offset; /* current offset
                           (incremented by each av_parser_parse()) */
    int64_t next_frame_offset; /* offset of the next frame */
    /* video info */
    int pict_type; /* XXX: Put it back in AVCodecContext. */
    /**
     * This field is used for proper frame duration computation in lavf.
     * It signals, how much longer the frame duration of the current frame
     * is compared to normal frame duration.
     *
     * frame_duration = (1 + repeat_pict) * time_base
     *
     * It is used by codecs like H.264 to display telecined material.
     */
    int repeat_pict; /* XXX: Put it back in AVCodecContext. */
    int64_t pts;     /* pts of the current frame */
    int64_t dts;     /* dts of the current frame */

    /* private data */
    int64_t last_pts;
    int64_t last_dts;
    int fetch_timestamp;

#define AV_PARSER_PTS_NB 4
    int cur_frame_start_index;
    int64_t cur_frame_offset[AV_PARSER_PTS_NB];
    int64_t cur_frame_pts[AV_PARSER_PTS_NB];
    int64_t cur_frame_dts[AV_PARSER_PTS_NB];

    int flags;
#define PARSER_FLAG_COMPLETE_FRAMES           0x0001
#define PARSER_FLAG_ONCE                      0x0002
/// Set if the parser has a valid file offset
#define PARSER_FLAG_FETCHED_OFFSET            0x0004
#define PARSER_FLAG_USE_CODEC_TS              0x1000

    int64_t offset;      ///< byte offset from starting packet start
    int64_t cur_frame_end[AV_PARSER_PTS_NB];

    /**
     * Set by parser to 1 for key frames and 0 for non-key frames.
     * It is initialized to -1, so if the parser doesn't set this flag,
     * old-style fallback using AV_PICTURE_TYPE_I picture type as key frames
     * will be used.
     */
    int key_frame;

#if FF_API_CONVERGENCE_DURATION
    /**
     * @deprecated unused
     */
    attribute_deprecated
    int64_t convergence_duration;
#endif

    // Timestamp generation support:
    /**
     * Synchronization point for start of timestamp generation.
     *
     * Set to >0 for sync point, 0 for no sync point and <0 for undefined
     * (default).
     *
     * For example, this corresponds to presence of H.264 buffering period
     * SEI message.
     */
    int dts_sync_point;

    /**
     * Offset of the current timestamp against last timestamp sync point in
     * units of AVCodecContext.time_base.
     *
     * Set to INT_MIN when dts_sync_point unused. Otherwise, it must
     * contain a valid timestamp offset.
     *
     * Note that the timestamp of sync point has usually a nonzero
     * dts_ref_dts_delta, which refers to the previous sync point. Offset of
     * the next frame after timestamp sync point will be usually 1.
     *
     * For example, this corresponds to H.264 cpb_removal_delay.
     */
    int dts_ref_dts_delta;

    /**
     * Presentation delay of current frame in units of AVCodecContext.time_base.
     *
     * Set to INT_MIN when dts_sync_point unused. Otherwise, it must
     * contain valid non-negative timestamp delta (presentation time of a frame
     * must not lie in the past).
     *
     * This delay represents the difference between decoding and presentation
     * time of the frame.
     *
     * For example, this corresponds to H.264 dpb_output_delay.
     */
    int pts_dts_delta;

    /**
     * Position of the packet in file.
     *
     * Analogous to cur_frame_pts/dts
     */
    int64_t cur_frame_pos[AV_PARSER_PTS_NB];

    /**
     * Byte position of currently parsed frame in stream.
     */
    int64_t pos;

    /**
     * Previous frame byte position.
     */
    int64_t last_pos;

    /**
     * Duration of the current frame.
     * For audio, this is in units of 1 / AVCodecContext.sample_rate.
     * For all other types, this is in units of AVCodecContext.time_base.
     */
    int duration;

    enum AVFieldOrder field_order;

    /**
     * Indicate whether a picture is coded as a frame, top field or bottom field.
     *
     * For example, H.264 field_pic_flag equal to 0 corresponds to
     * AV_PICTURE_STRUCTURE_FRAME. An H.264 picture with field_pic_flag
     * equal to 1 and bottom_field_flag equal to 0 corresponds to
     * AV_PICTURE_STRUCTURE_TOP_FIELD.
     */
    enum AVPictureStructure picture_structure;

    /**
     * Picture number incremented in presentation or output order.
     * This field may be reinitialized at the first picture of a new sequence.
     *
     * For example, this corresponds to H.264 PicOrderCnt.
     */
    int output_picture_number;

    /**
     * Dimensions of the decoded video intended for presentation.
     */
    int width;
    int height;

    /**
     * Dimensions of the coded video.
     */
    int coded_width;
    int coded_height;

    /**
     * The format of the coded data, corresponds to enum AVPixelFormat for video
     * and for enum AVSampleFormat for audio.
     *
     * Note that a decoder can have considerable freedom in how exactly it
     * decodes the data, so the format reported here might be different from the
     * one returned by a decoder.
     */
    int format;
} AVCodecParserContext;

AVCodecDescriptor:描述单个解码器的属性,单个解码器被描述是通过AVCodecID

typedef struct AVCodecDescriptor {
    enum AVCodecID     id;
    enum AVMediaType type;
    const char      *name;
    const char *long_name;
    /**
     * Codec properties, a combination of AV_CODEC_PROP_* flags.
     */
    int             props;
    /**
     * MIME type(s) associated with the codec.
     * May be NULL; if not, a NULL-terminated array of MIME types.
     * The first item is always non-NULL and is the preferred MIME type.
     */
    const char *const *mime_types;
    /**
     * If non-NULL, an array of profiles recognized for this codec.
     * Terminated with FF_PROFILE_UNKNOWN.
     */
    const struct AVProfile *profiles;
} AVCodecDescriptor;

AVCodecID:枚举出FFmpeg所有解码器id

enum AVCodecID {
    AV_CODEC_ID_NONE,

    /* video codecs */
    AV_CODEC_ID_MPEG1VIDEO,
    AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
#if FF_API_XVMC
    AV_CODEC_ID_MPEG2VIDEO_XVMC,
#endif /* FF_API_XVMC */
    AV_CODEC_ID_H261,
    AV_CODEC_ID_H263,
    AV_CODEC_ID_RV10,
    AV_CODEC_ID_RV20,
    AV_CODEC_ID_MJPEG,
    AV_CODEC_ID_MJPEGB,
    AV_CODEC_ID_LJPEG,
    AV_CODEC_ID_SP5X,
    AV_CODEC_ID_JPEGLS,
    AV_CODEC_ID_MPEG4,
    AV_CODEC_ID_RAWVIDEO,
    AV_CODEC_ID_MSMPEG4V1,
    AV_CODEC_ID_MSMPEG4V2,
    AV_CODEC_ID_MSMPEG4V3,
    AV_CODEC_ID_WMV1,
    AV_CODEC_ID_WMV2,
    AV_CODEC_ID_H263P,
    AV_CODEC_ID_H263I,
    AV_CODEC_ID_FLV1,
    AV_CODEC_ID_SVQ1,
    AV_CODEC_ID_SVQ3,
    AV_CODEC_ID_DVVIDEO,
    AV_CODEC_ID_HUFFYUV,
    AV_CODEC_ID_CYUV,
    AV_CODEC_ID_H264,
    AV_CODEC_ID_INDEO3,
    AV_CODEC_ID_VP3,
    AV_CODEC_ID_THEORA,
    AV_CODEC_ID_ASV1,
    AV_CODEC_ID_ASV2,
    AV_CODEC_ID_FFV1,
    AV_CODEC_ID_4XM,
    AV_CODEC_ID_VCR1,
    AV_CODEC_ID_CLJR,
    AV_CODEC_ID_MDEC,
    AV_CODEC_ID_ROQ,
    AV_CODEC_ID_INTERPLAY_VIDEO,
    AV_CODEC_ID_XAN_WC3,
    AV_CODEC_ID_XAN_WC4,
    AV_CODEC_ID_RPZA,
    AV_CODEC_ID_CINEPAK,
    AV_CODEC_ID_WS_VQA,
    AV_CODEC_ID_MSRLE,
    AV_CODEC_ID_MSVIDEO1,
    AV_CODEC_ID_IDCIN,
    AV_CODEC_ID_8BPS,
    AV_CODEC_ID_SMC,
    AV_CODEC_ID_FLIC,
    AV_CODEC_ID_TRUEMOTION1,
    AV_CODEC_ID_VMDVIDEO,
    AV_CODEC_ID_MSZH,
    AV_CODEC_ID_ZLIB,
    AV_CODEC_ID_QTRLE,
    AV_CODEC_ID_TSCC,
    AV_CODEC_ID_ULTI,
    AV_CODEC_ID_QDRAW,
    AV_CODEC_ID_VIXL,
    AV_CODEC_ID_QPEG,
    AV_CODEC_ID_PNG,
    AV_CODEC_ID_PPM,
    AV_CODEC_ID_PBM,
    AV_CODEC_ID_PGM,
    AV_CODEC_ID_PGMYUV,
    AV_CODEC_ID_PAM,
    AV_CODEC_ID_FFVHUFF,
    AV_CODEC_ID_RV30,
    AV_CODEC_ID_RV40,
    AV_CODEC_ID_VC1,
    AV_CODEC_ID_WMV3,
    AV_CODEC_ID_LOCO,
    AV_CODEC_ID_WNV1,
    AV_CODEC_ID_AASC,
    AV_CODEC_ID_INDEO2,
    AV_CODEC_ID_FRAPS,
    AV_CODEC_ID_TRUEMOTION2,
    AV_CODEC_ID_BMP,
    AV_CODEC_ID_CSCD,
    AV_CODEC_ID_MMVIDEO,
    AV_CODEC_ID_ZMBV,
    AV_CODEC_ID_AVS,
    AV_CODEC_ID_SMACKVIDEO,
    AV_CODEC_ID_NUV,
    AV_CODEC_ID_KMVC,
    AV_CODEC_ID_FLASHSV,
    AV_CODEC_ID_CAVS,
    AV_CODEC_ID_JPEG2000,
    AV_CODEC_ID_VMNC,
    AV_CODEC_ID_VP5,
    AV_CODEC_ID_VP6,
    AV_CODEC_ID_VP6F,
    AV_CODEC_ID_TARGA,
    AV_CODEC_ID_DSICINVIDEO,
    AV_CODEC_ID_TIERTEXSEQVIDEO,
    AV_CODEC_ID_TIFF,
    AV_CODEC_ID_GIF,
    AV_CODEC_ID_DXA,
    AV_CODEC_ID_DNXHD,
    AV_CODEC_ID_THP,
    AV_CODEC_ID_SGI,
    AV_CODEC_ID_C93,
    AV_CODEC_ID_BETHSOFTVID,
    AV_CODEC_ID_PTX,
    AV_CODEC_ID_TXD,
    AV_CODEC_ID_VP6A,
    AV_CODEC_ID_AMV,
    AV_CODEC_ID_VB,
    AV_CODEC_ID_PCX,
    AV_CODEC_ID_SUNRAST,
    AV_CODEC_ID_INDEO4,
    AV_CODEC_ID_INDEO5,
    AV_CODEC_ID_MIMIC,
    AV_CODEC_ID_RL2,
    AV_CODEC_ID_ESCAPE124,
    AV_CODEC_ID_DIRAC,
    AV_CODEC_ID_BFI,
    AV_CODEC_ID_CMV,
    AV_CODEC_ID_MOTIONPIXELS,
    AV_CODEC_ID_TGV,
    AV_CODEC_ID_TGQ,
    AV_CODEC_ID_TQI,
    AV_CODEC_ID_AURA,
    AV_CODEC_ID_AURA2,
    AV_CODEC_ID_V210X,
    AV_CODEC_ID_TMV,
    AV_CODEC_ID_V210,
    AV_CODEC_ID_DPX,
    AV_CODEC_ID_MAD,
    AV_CODEC_ID_FRWU,
    AV_CODEC_ID_FLASHSV2,
    AV_CODEC_ID_CDGRAPHICS,
    AV_CODEC_ID_R210,
    AV_CODEC_ID_ANM,
    AV_CODEC_ID_BINKVIDEO,
    AV_CODEC_ID_IFF_ILBM,
#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM
    AV_CODEC_ID_KGV1,
    AV_CODEC_ID_YOP,
    AV_CODEC_ID_VP8,
    AV_CODEC_ID_PICTOR,
    AV_CODEC_ID_ANSI,
    AV_CODEC_ID_A64_MULTI,
    AV_CODEC_ID_A64_MULTI5,
    AV_CODEC_ID_R10K,
    AV_CODEC_ID_MXPEG,
    AV_CODEC_ID_LAGARITH,
    AV_CODEC_ID_PRORES,
    AV_CODEC_ID_JV,
    AV_CODEC_ID_DFA,
    AV_CODEC_ID_WMV3IMAGE,
    AV_CODEC_ID_VC1IMAGE,
    AV_CODEC_ID_UTVIDEO,
    AV_CODEC_ID_BMV_VIDEO,
    AV_CODEC_ID_VBLE,
    AV_CODEC_ID_DXTORY,
    AV_CODEC_ID_V410,
    AV_CODEC_ID_XWD,
    AV_CODEC_ID_CDXL,
    AV_CODEC_ID_XBM,
    AV_CODEC_ID_ZEROCODEC,
    AV_CODEC_ID_MSS1,
    AV_CODEC_ID_MSA1,
    AV_CODEC_ID_TSCC2,
    AV_CODEC_ID_MTS2,
    AV_CODEC_ID_CLLC,
    AV_CODEC_ID_MSS2,
    AV_CODEC_ID_VP9,
    AV_CODEC_ID_AIC,
    AV_CODEC_ID_ESCAPE130,
    AV_CODEC_ID_G2M,
    AV_CODEC_ID_WEBP,
    AV_CODEC_ID_HNM4_VIDEO,
    AV_CODEC_ID_HEVC,
#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC
    AV_CODEC_ID_FIC,
    AV_CODEC_ID_ALIAS_PIX,
    AV_CODEC_ID_BRENDER_PIX,
    AV_CODEC_ID_PAF_VIDEO,
    AV_CODEC_ID_EXR,
    AV_CODEC_ID_VP7,
    AV_CODEC_ID_SANM,
    AV_CODEC_ID_SGIRLE,
    AV_CODEC_ID_MVC1,
    AV_CODEC_ID_MVC2,
    AV_CODEC_ID_HQX,
    AV_CODEC_ID_TDSC,
    AV_CODEC_ID_HQ_HQA,
    AV_CODEC_ID_HAP,
    AV_CODEC_ID_DDS,
    AV_CODEC_ID_DXV,
    AV_CODEC_ID_SCREENPRESSO,
    AV_CODEC_ID_RSCC,

    AV_CODEC_ID_Y41P = 0x8000,
    AV_CODEC_ID_AVRP,
    AV_CODEC_ID_012V,
    AV_CODEC_ID_AVUI,
    AV_CODEC_ID_AYUV,
    AV_CODEC_ID_TARGA_Y216,
    AV_CODEC_ID_V308,
    AV_CODEC_ID_V408,
    AV_CODEC_ID_YUV4,
    AV_CODEC_ID_AVRN,
    AV_CODEC_ID_CPIA,
    AV_CODEC_ID_XFACE,
    AV_CODEC_ID_SNOW,
    AV_CODEC_ID_SMVJPEG,
    AV_CODEC_ID_APNG,
    AV_CODEC_ID_DAALA,
    AV_CODEC_ID_CFHD,
    AV_CODEC_ID_TRUEMOTION2RT,
    AV_CODEC_ID_M101,
    AV_CODEC_ID_MAGICYUV,
    AV_CODEC_ID_SHEERVIDEO,
    AV_CODEC_ID_YLC,

    /* various PCM "codecs" */
    AV_CODEC_ID_FIRST_AUDIO = 0x10000,     ///< A dummy id pointing at the start of audio codecs
    AV_CODEC_ID_PCM_S16LE = 0x10000,
    AV_CODEC_ID_PCM_S16BE,
    AV_CODEC_ID_PCM_U16LE,
    AV_CODEC_ID_PCM_U16BE,
    AV_CODEC_ID_PCM_S8,
    AV_CODEC_ID_PCM_U8,
    AV_CODEC_ID_PCM_MULAW,
    AV_CODEC_ID_PCM_ALAW,
    AV_CODEC_ID_PCM_S32LE,
    AV_CODEC_ID_PCM_S32BE,
    AV_CODEC_ID_PCM_U32LE,
    AV_CODEC_ID_PCM_U32BE,
    AV_CODEC_ID_PCM_S24LE,
    AV_CODEC_ID_PCM_S24BE,
    AV_CODEC_ID_PCM_U24LE,
    AV_CODEC_ID_PCM_U24BE,
    AV_CODEC_ID_PCM_S24DAUD,
    AV_CODEC_ID_PCM_ZORK,
    AV_CODEC_ID_PCM_S16LE_PLANAR,
    AV_CODEC_ID_PCM_DVD,
    AV_CODEC_ID_PCM_F32BE,
    AV_CODEC_ID_PCM_F32LE,
    AV_CODEC_ID_PCM_F64BE,
    AV_CODEC_ID_PCM_F64LE,
    AV_CODEC_ID_PCM_BLURAY,
    AV_CODEC_ID_PCM_LXF,
    AV_CODEC_ID_S302M,
    AV_CODEC_ID_PCM_S8_PLANAR,
    AV_CODEC_ID_PCM_S24LE_PLANAR,
    AV_CODEC_ID_PCM_S32LE_PLANAR,
    AV_CODEC_ID_PCM_S16BE_PLANAR,
    /* new PCM "codecs" should be added right below this line starting with
     * an explicit value of for example 0x10800
     */

    /* various ADPCM codecs */
    AV_CODEC_ID_ADPCM_IMA_QT = 0x11000,
    AV_CODEC_ID_ADPCM_IMA_WAV,
    AV_CODEC_ID_ADPCM_IMA_DK3,
    AV_CODEC_ID_ADPCM_IMA_DK4,
    AV_CODEC_ID_ADPCM_IMA_WS,
    AV_CODEC_ID_ADPCM_IMA_SMJPEG,
    AV_CODEC_ID_ADPCM_MS,
    AV_CODEC_ID_ADPCM_4XM,
    AV_CODEC_ID_ADPCM_XA,
    AV_CODEC_ID_ADPCM_ADX,
    AV_CODEC_ID_ADPCM_EA,
    AV_CODEC_ID_ADPCM_G726,
    AV_CODEC_ID_ADPCM_CT,
    AV_CODEC_ID_ADPCM_SWF,
    AV_CODEC_ID_ADPCM_YAMAHA,
    AV_CODEC_ID_ADPCM_SBPRO_4,
    AV_CODEC_ID_ADPCM_SBPRO_3,
    AV_CODEC_ID_ADPCM_SBPRO_2,
    AV_CODEC_ID_ADPCM_THP,
    AV_CODEC_ID_ADPCM_IMA_AMV,
    AV_CODEC_ID_ADPCM_EA_R1,
    AV_CODEC_ID_ADPCM_EA_R3,
    AV_CODEC_ID_ADPCM_EA_R2,
    AV_CODEC_ID_ADPCM_IMA_EA_SEAD,
    AV_CODEC_ID_ADPCM_IMA_EA_EACS,
    AV_CODEC_ID_ADPCM_EA_XAS,
    AV_CODEC_ID_ADPCM_EA_MAXIS_XA,
    AV_CODEC_ID_ADPCM_IMA_ISS,
    AV_CODEC_ID_ADPCM_G722,
    AV_CODEC_ID_ADPCM_IMA_APC,
    AV_CODEC_ID_ADPCM_VIMA,
#if FF_API_VIMA_DECODER
    AV_CODEC_ID_VIMA = AV_CODEC_ID_ADPCM_VIMA,
#endif

    AV_CODEC_ID_ADPCM_AFC = 0x11800,
    AV_CODEC_ID_ADPCM_IMA_OKI,
    AV_CODEC_ID_ADPCM_DTK,
    AV_CODEC_ID_ADPCM_IMA_RAD,
    AV_CODEC_ID_ADPCM_G726LE,
    AV_CODEC_ID_ADPCM_THP_LE,
    AV_CODEC_ID_ADPCM_PSX,
    AV_CODEC_ID_ADPCM_AICA,
    AV_CODEC_ID_ADPCM_IMA_DAT4,
    AV_CODEC_ID_ADPCM_MTAF,

    /* AMR */
    AV_CODEC_ID_AMR_NB = 0x12000,
    AV_CODEC_ID_AMR_WB,

    /* RealAudio codecs*/
    AV_CODEC_ID_RA_144 = 0x13000,
    AV_CODEC_ID_RA_288,

    /* various DPCM codecs */
    AV_CODEC_ID_ROQ_DPCM = 0x14000,
    AV_CODEC_ID_INTERPLAY_DPCM,
    AV_CODEC_ID_XAN_DPCM,
    AV_CODEC_ID_SOL_DPCM,

    AV_CODEC_ID_SDX2_DPCM = 0x14800,

    /* audio codecs */
    AV_CODEC_ID_MP2 = 0x15000,
    AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
    AV_CODEC_ID_AAC,
    AV_CODEC_ID_AC3,
    AV_CODEC_ID_DTS,
    AV_CODEC_ID_VORBIS,
    AV_CODEC_ID_DVAUDIO,
    AV_CODEC_ID_WMAV1,
    AV_CODEC_ID_WMAV2,
    AV_CODEC_ID_MACE3,
    AV_CODEC_ID_MACE6,
    AV_CODEC_ID_VMDAUDIO,
    AV_CODEC_ID_FLAC,
    AV_CODEC_ID_MP3ADU,
    AV_CODEC_ID_MP3ON4,
    AV_CODEC_ID_SHORTEN,
    AV_CODEC_ID_ALAC,
    AV_CODEC_ID_WESTWOOD_SND1,
    AV_CODEC_ID_GSM, ///< as in Berlin toast format
    AV_CODEC_ID_QDM2,
    AV_CODEC_ID_COOK,
    AV_CODEC_ID_TRUESPEECH,
    AV_CODEC_ID_TTA,
    AV_CODEC_ID_SMACKAUDIO,
    AV_CODEC_ID_QCELP,
    AV_CODEC_ID_WAVPACK,
    AV_CODEC_ID_DSICINAUDIO,
    AV_CODEC_ID_IMC,
    AV_CODEC_ID_MUSEPACK7,
    AV_CODEC_ID_MLP,
    AV_CODEC_ID_GSM_MS, /* as found in WAV */
    AV_CODEC_ID_ATRAC3,
#if FF_API_VOXWARE
    AV_CODEC_ID_VOXWARE,
#endif
    AV_CODEC_ID_APE,
    AV_CODEC_ID_NELLYMOSER,
    AV_CODEC_ID_MUSEPACK8,
    AV_CODEC_ID_SPEEX,
    AV_CODEC_ID_WMAVOICE,
    AV_CODEC_ID_WMAPRO,
    AV_CODEC_ID_WMALOSSLESS,
    AV_CODEC_ID_ATRAC3P,
    AV_CODEC_ID_EAC3,
    AV_CODEC_ID_SIPR,
    AV_CODEC_ID_MP1,
    AV_CODEC_ID_TWINVQ,
    AV_CODEC_ID_TRUEHD,
    AV_CODEC_ID_MP4ALS,
    AV_CODEC_ID_ATRAC1,
    AV_CODEC_ID_BINKAUDIO_RDFT,
    AV_CODEC_ID_BINKAUDIO_DCT,
    AV_CODEC_ID_AAC_LATM,
    AV_CODEC_ID_QDMC,
    AV_CODEC_ID_CELT,
    AV_CODEC_ID_G723_1,
    AV_CODEC_ID_G729,
    AV_CODEC_ID_8SVX_EXP,
    AV_CODEC_ID_8SVX_FIB,
    AV_CODEC_ID_BMV_AUDIO,
    AV_CODEC_ID_RALF,
    AV_CODEC_ID_IAC,
    AV_CODEC_ID_ILBC,
    AV_CODEC_ID_OPUS,
    AV_CODEC_ID_COMFORT_NOISE,
    AV_CODEC_ID_TAK,
    AV_CODEC_ID_METASOUND,
    AV_CODEC_ID_PAF_AUDIO,
    AV_CODEC_ID_ON2AVC,
    AV_CODEC_ID_DSS_SP,

    AV_CODEC_ID_FFWAVESYNTH = 0x15800,
    AV_CODEC_ID_SONIC,
    AV_CODEC_ID_SONIC_LS,
    AV_CODEC_ID_EVRC,
    AV_CODEC_ID_SMV,
    AV_CODEC_ID_DSD_LSBF,
    AV_CODEC_ID_DSD_MSBF,
    AV_CODEC_ID_DSD_LSBF_PLANAR,
    AV_CODEC_ID_DSD_MSBF_PLANAR,
    AV_CODEC_ID_4GV,
    AV_CODEC_ID_INTERPLAY_ACM,
    AV_CODEC_ID_XMA1,
    AV_CODEC_ID_XMA2,
    AV_CODEC_ID_DST,

    /* subtitle codecs */
    AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
    AV_CODEC_ID_DVD_SUBTITLE = 0x17000,
    AV_CODEC_ID_DVB_SUBTITLE,
    AV_CODEC_ID_TEXT,  ///< raw UTF-8 text
    AV_CODEC_ID_XSUB,
    AV_CODEC_ID_SSA,
    AV_CODEC_ID_MOV_TEXT,
    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
    AV_CODEC_ID_DVB_TELETEXT,
    AV_CODEC_ID_SRT,

    AV_CODEC_ID_MICRODVD   = 0x17800,
    AV_CODEC_ID_EIA_608,
    AV_CODEC_ID_JACOSUB,
    AV_CODEC_ID_SAMI,
    AV_CODEC_ID_REALTEXT,
    AV_CODEC_ID_STL,
    AV_CODEC_ID_SUBVIEWER1,
    AV_CODEC_ID_SUBVIEWER,
    AV_CODEC_ID_SUBRIP,
    AV_CODEC_ID_WEBVTT,
    AV_CODEC_ID_MPL2,
    AV_CODEC_ID_VPLAYER,
    AV_CODEC_ID_PJS,
    AV_CODEC_ID_ASS,
    AV_CODEC_ID_HDMV_TEXT_SUBTITLE,

    /* other specific kind of codecs (generally used for attachments) */
    AV_CODEC_ID_FIRST_UNKNOWN = 0x18000,           ///< A dummy ID pointing at the start of various fake codecs.
    AV_CODEC_ID_TTF = 0x18000,

    AV_CODEC_ID_BINTEXT    = 0x18800,
    AV_CODEC_ID_XBIN,
    AV_CODEC_ID_IDF,
    AV_CODEC_ID_OTF,
    AV_CODEC_ID_SMPTE_KLV,
    AV_CODEC_ID_DVD_NAV,
    AV_CODEC_ID_TIMED_ID3,
    AV_CODEC_ID_BIN_DATA,


    AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it

    AV_CODEC_ID_MPEG2TS = 0x20000, /**< _FAKE_ codec to indicate a raw MPEG-2 TS
                                * stream (only used by libavformat) */
    AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, /**< _FAKE_ codec to indicate a MPEG-4 Systems
                                * stream (only used by libavformat) */
    AV_CODEC_ID_FFMETADATA = 0x21000,   ///< Dummy codec for streams containing only metadata information.
    AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, ///< Passthrough codec, AVFrames wrapped in AVPacket
};

如我们找一个标准的h.264+aac的解码,h.264解码器在h264.c中的声明如下,注意id(AV_CODEC_ID_H264)对应我们上面AVCodcID中的AV_CODEC_ID_H264。

AVCodec ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = ff_h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE,
    .flush                 = flush_dpb,
    .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .priv_class            = &h264_class,
};

aac中解码器声明如下,位于aacdec.c文件中,注意id(AV_CODEC_ID_AAC)与上面上面的AVCodcID中的AV_CODEC_ID_AAC。

AVCodec ff_aac_decoder = {
    .name            = "aac",
    .long_name       = NULL_IF_CONFIG_SMALL("AAC (Advanced Audio Coding)"),
    .type            = AVMEDIA_TYPE_AUDIO,
    .id              = AV_CODEC_ID_AAC,
    .priv_data_size  = sizeof(AACContext),
    .init            = aac_decode_init,
    .close           = aac_decode_close,
    .decode          = aac_decode_frame,
    .sample_fmts     = (const enum AVSampleFormat[]) {
        AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE
    },
    .capabilities    = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
    .caps_internal   = FF_CODEC_CAP_INIT_THREADSAFE,
    .channel_layouts = aac_channel_layout,
    .flush = flush,
    .priv_class      = &aac_decoder_class,
    .profiles        = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
};

每一个编解码器对应一个结构体AVCodec

作者:hejjunlin 发表于2017/4/12 10:53:32 原文链接
阅读:167 评论:0 查看评论

Android ConstraintLayout使用指南

$
0
0

升级Android Studio 2.3之后,IDE默认生成的Activity布局都是以ConstraintLayout做为根布局,体验了一把这个Google去年就开始力推的ConstraintLayout后,觉得非常不错,本文用于记录ConstraintLayout各个方面的使用知识。

平台支持

  • ConstraintLayout最低兼容Android 2.3;
  • 目前Android Studio 2.3默认使用ConstraintLayout作为布局文件的根布局;
  • 想要使用ConstraintLayout,需在项目的build.gradle添加com.android.support.constraint:constraint-layout:XXX版本号依赖;

基础使用

ConstraintLayout翻译成中文也称为约束布局,在整个使用体验过程中真的是贯穿约束二字,这一节先来介绍一些基础使用,后面你就会慢慢感受到约束布局的魅力。创建完工程后打开布局文件,底部切换Design的Tab上,可以看到整个操作界面

左上角的面板是放置了系统内置各种各样的控件,想要布局直接拖到到布局文件中即可(所见即所得),右边的面板是选中布局文件中的控件时期各种各样的空间属性,ConstraintLayout最大的好处在于让我们通过拖控件的形式进行布局,并且不用担心适配问题。所以,先来拖个控件试试看,将一个Button拖动到屏幕正中央,然后运行显示看看效果。

模拟器运行后的效果

而实际运行后却发现,这个Button还是位于屏幕左上角,说好的居中效果呢?这里就要开始引入ConstraintLayout的约束概念,我们切换回去看xml的布局代码,发现了两个问题。第一,布局预览时能够看到显示居中的Button,是因为控件属性设置中引用了两个tools命名空间下的属性。

我们都知道,这两个属性只在布局编辑器的预览中有效,实际运行效果是不生效的。第二,Button标签下有红色波浪线警告,我们把鼠标移到对应位置会发现警告内容,告诉我们Button没有任何约束设置,当前效果只支持预览,实际运行后会返回到左上角去,同时提示我们应该给控件添加约束。

如何增加约束?回到Design页面,对着控件上下左右四个原点拖动添加对应的约束即可

成功添加约束后,即可得到正常的运行效果了。

实际操作不一定要在Tab,也可以直接在Text页面拖动控件添加约束

成功实现添加约束后,可以看到Button多了下面几个属性设置

app:layout_constraintBottom_toBottomOf=”parent” 意思是Button底部的布局约束是位于parent的底部,parent是指包裹着它的ConstraintLayout,也可以设置指定某个控件的id,其他类似的属性就不再赘述,以上四个约束联合起来便实现了Button的居中,ConstraintLayout总共有下面这些同类的属性

你会发现ConstraintLayout非常灵活的把RelativeLayout的活给干了,关于left、right、top、bottom、start、end、baseline的基准可以参照下图

relative-positioning-constraints

如果我想加多一个Button2并且将其放置到原先居中Button的右方并且与其底部对齐,只需如下操作即可

并且你也可以发现,Button2依赖与Button后会随着Button的移动而跟着发生相对移动,目的是了保证我设置的依赖,时刻保持Button2就在Button的右边,并且底部对齐。你也可以看到布局文件中也为Button2添加了如下两个属性

如果你已经理解上面提到的属性含义,这里应该不会有疑惑。

介绍完上下左右的依赖设置后,下面介绍一些Margin属性,除了Android常见的各种android:layout_marginXXX外,ConstraintLayout自行添加了如下属性

这些设置生效于当依赖的约束对象被设置visibility为gone时,非常简单,读者自行设置实践对比即可,这里就不展示效果了。

聊完Margin后,再来介绍一下Bias,ConstraintLayout新增了如下两个属性用于控制控件在水平和垂直方向在屏幕上的偏移比例

当为目标控件设置好横纵向的约束时(app:layout_constraintLeft_toLeftOf=”parent”、app:layout_constraintRight_toRightOf=”parent”或者app:layout_constraintTop_toTopOf=”parent”、app:layout_constraintBottom_toBottomOf=”parent”),这个两个属性才会生效。实际操作过程中,你会发现对着设置好横纵向约束的Button进行拖动,布局中的layout_constraintHorizontal_bias和layout_constraintVertical_bias会一直发生相应的变化,如果你需要Button居中,那么直接将这两个属性的参数值设置为0.5即可。

关于ConstraintLayout的基本使用,这一节就介绍到这里。

进阶使用

看完基本使用,再来学习一些进阶技巧,这里先补充一个关于ConstraintLayout的知识点,与其他Layout不同之处在于,它的layout_width和layout_height不支持设置match_parent,其属性取值只有以下三种情况:

  • wrap_content;
  • 指定具体dp值;
  • 0dp(match_constraint),代表填充约束之意,注意不要以为和match_parent是一样的;

想想如果没有ConstraintLayout,我们要让一个控件的宽高按某个比例进行布局应该怎么做?有了ConstraintLayout后,我们可以使用layout_constraintDimentionRatio属性设置宽高比例,前提是目标控件的layout_width和layout_height至少有一个设置为0dp,如下让一个ImageView宽高按照2:1的比例显示

layout_constraintDimentionRatio默认参数比例是指宽:高,变成高:宽可以设app:layout_constraintDimensionRatio=”H,2:1”。

ConstraintLayout的链条(Chains)特性非常强大,在没有ConstraintLayout之前,线性布局我们主要都依靠LinearLayout来完成,有了ConstraintLayout之后,它把LinearLayout的活也干了,例如要把按钮水平排成一行,可以这样操作

这样ButtonA、B、C就在水平方向形成了一条Chain,并且底部对齐。回去看xml文件,会见到ButtonA新增app:layout_constraintHorizontal_chainStyle的属性设置,这个属性在一条Chain中只会出现在第一个控件中,这个控件是整条Chain的Head。

除了水平方向的layout_constraintHorizontal_chainStyle外还有垂直方向的layout_constraintVertical_chainStyle,两者均有spread,spread_inside,packed这三种取值,如果将控件的layout_width和layout_height设置成为0dp,还可以配合layout_constraintHorizontal_weight、layout_constraintVertical_weight两个属性实现和LinearLayout中设置layout_weight相同的效果,具体的操作这里就不再展示了,下面一张图告诉你Chain的强大之处。

关于Chain的就介绍到此,进阶的最后一部分再介绍一下Guideline功能,如果我们需要对着屏幕的中轴线进行布局,就可以使用到Guideline进行操作,例如下面两个Button分别分布在中轴线的左右两侧

从操作上我们可以看到Guideline也分为垂直和水平两种,并且支持设置在屏幕中所处的位置,可以使用layout_constraintGuide_begin和layout_constraintGuide_end设置具体dp值,也可以使用layout_constraintGuide_percent来设置比例。实际上它也只是一个辅助我们布局的View而已,其源码内部实现也非常简单,并且默认设置了visibility为gone,关于ConstraintLayout的进阶使用便介绍到这里。

使用优势

关于ConstraintLayout的使用优势,我个人体验总结如下:

  • 高效布局,Android这么多年以来真正意义上的实现了所见即所得的拖曳方式布局,极大的提高开发效率;
  • 轻松灵活的实现复杂的布局;
  • 解决多重布局嵌套问题,通过前面介绍你会发现ConstraintLayout真的是非常灵活,可以很大程度的避免Layout之间的嵌套;
  • 满足屏幕适配的需求,想想没有ConstraintLayout之前的拖曳式布局,你就知道有多恶心;

最佳实践

ConstraintLayout最开始出来就有很多开发者盼着完全依赖于拖曳方式实现布局,而在实际操作过程中,完全通过拖曳其实效率反倒是会打折扣,在此建议是拖曳方式和xml编码相结合才是最佳实践的方式。

本文为技术视界原创作品,转载请注明原文出处:http://blog.coderclock.com/2017/04/09/android/android-constraintlayout ,欢迎关注我的微信公众号:技术视界

作者:D_clock 发表于2017/4/12 13:15:41 原文链接
阅读:746 评论:1 查看评论

MPAndroidChart项目实战(三)——饼状图实现和文字重合问题解决

$
0
0

本文出自:http://blog.csdn.net/dt235201314/article/details/70142117

源码下载(UpDating 欢迎Star):

https://github.com/JinBoy23520/MPAndroidChartDemoByJin

一丶概述

上一篇代码补了这么久,不好意思,今天再说说MPAndroidChart实现饼状图以及文字冲突问题解决。

二丶演示效果



三丶实现功能

1.饼状图实现

2.解决当占比过小,文字重合问题


四丶看代码

与上一篇,提高代码复用率

PieChartEntity.Java 设置基本属性(这里不做详细说明,百度有文章可查看属性)

/**
 * 饼状图
 * Created by jin
 */
public class PieChartEntity  {
    private PieChart mChart;
    private List<PieEntry> mEntries;
    private String[] labels;
    private int[] mPieColors;
    private int mValueColor;
    private float mTextSize;
    private PieDataSet.ValuePosition mValuePosition;

    public PieChartEntity(PieChart chart, List<PieEntry> entries, String[] labels,
                          int []chartColor,  float textSize, int valueColor, PieDataSet.ValuePosition valuePosition) {
        this.mChart = chart;
        this.mEntries = entries;
        this.labels= labels;
        this.mPieColors = chartColor;
        this.mTextSize= textSize;
        this.mValueColor = valueColor;
        this.mValuePosition = valuePosition;
        initPieChart();
    }

    public PieChartEntity(PieChart chart, List<PieEntry> entries, String[] labels,
                          int []chartColor,  float textSize, int valueColor) {
        this(chart, entries, labels, chartColor, textSize, valueColor, PieDataSet.ValuePosition.INSIDE_SLICE);

    }

    private void initPieChart() {
        mChart.setExtraOffsets(5, 10, 5, 5);

        mChart.setDragDecelerationFrictionCoef(0.95f);
        mChart.setDrawCenterText(false);
        mChart.getDescription().setEnabled(false);
        mChart.setRotationAngle(0);
        // enable rotation of the chart by touch
        mChart.setRotationEnabled(true);
        mChart.setHighlightPerTapEnabled(true);
        mChart.setDrawEntryLabels(true);
        setChartData();
        mChart.animateY(1000, Easing.EasingOption.EaseInOutQuad);

        Legend l = mChart.getLegend();
        l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP);
        l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT);
        l.setOrientation(Legend.LegendOrientation.VERTICAL);
        l.setDrawInside(false);
        l.setXEntrySpace(7f);
        l.setYEntrySpace(1f);
        l.setYOffset(0f);
        // entry label styling
        mChart.setEntryLabelColor(mValueColor);
        mChart.setEntryLabelTextSize(mTextSize);
        mChart.setExtraOffsets(10, 10, 10, 10);
    }

    public void setHoleDisenabled () {
        mChart.setDrawHoleEnabled(false);
    }

    /**
     * 中心圆是否可见
     * @param holeColor 中心圆颜色
     * @param holeRadius 半径
     * @param transColor 透明圆颜色
     * @param transRadius 透明圆半径
     */
    public void setHoleEnabled (int holeColor, float holeRadius, int transColor, float transRadius) {
        mChart.setDrawHoleEnabled(true);
        mChart.setHoleColor(holeColor);
        mChart.setTransparentCircleColor(transColor);
        mChart.setTransparentCircleAlpha(110);
        mChart.setHoleRadius(holeRadius);
        mChart.setTransparentCircleRadius(transRadius);
    }

    private void setChartData() {
        PieDataSet dataSet = new PieDataSet(mEntries, "");
        dataSet.setSliceSpace(0f);
        dataSet.setSelectionShift(5f);
//        dataSet.setEntryLabelsColor(mValueColor);
        dataSet.setColors(mPieColors);
        //dataSet.setSelectionShift(0f);
        dataSet.setYValuePosition(mValuePosition);
        dataSet.setXValuePosition(mValuePosition);
        dataSet.setValueLineColor(mValueColor);
        dataSet.setSelectionShift(15f);
        dataSet.setValueLinePart1Length(0.6f);
        dataSet.setValueLineColor(mValueColor);
        PieData data = new PieData(dataSet);
        data.setValueFormatter(new PercentFormatter());
        data.setValueTextSize(mTextSize);
        data.setValueTextColor(mValueColor);
        data.setValueTextColor(mValueColor);
        mChart.setData(data);
        // undo all highlights
        mChart.highlightValues(null);
        mChart.invalidate();
    }

    /**
     * <p>说明文字是否可见</p>
     * @param enabled true 可见,默认可见
     */
    public void setLegendEnabled(boolean enabled) {
        mChart.getLegend().setEnabled(enabled);
        mChart.invalidate();
    }
    public void setPercentValues (boolean showPercent) {
        mChart.setUsePercentValues(showPercent);
    }
}

这样字可以直接运用了

/**
 * 添加数据均匀饼装图
 */
public void updatePieChart() {
    int[] colors = {Color.parseColor("#faa74c"), Color.parseColor("#58D4C5"), Color.parseColor("#36a3eb"), Color.parseColor("#cc435f"), Color.parseColor("#f1ea56"),
            Color.parseColor("#f49468"), Color.parseColor("#d5932c"), Color.parseColor("#34b5cc"), Color.parseColor("#8169c6"), Color.parseColor("#ca4561"),Color.parseColor("#fee335")};
    ArrayList<PieEntry> entries = new ArrayList<PieEntry>();
    for(int i = 0 ;i <= 5; i++){
        PieEntry pieEntry = new PieEntry(60,"项目" + i + "占比");
        entries.add(pieEntry);
    }

    for(int i = 6 ;i <= 7; i++){
        PieEntry pieEntry = new PieEntry(100,"项目" + i + "占比");
        entries.add(pieEntry);
    }

    PieEntry pieEntry = new PieEntry(100,"项目8占比");
    entries.add(pieEntry);

    if (entries.size() != 0) {
        PieChart new_pie_chart = (PieChart) mView.findViewById(R.id.new_pie_chart);
        PieChartEntity pieChartEntity = new PieChartEntity(new_pie_chart, entries, new String[]{"", "", ""}, colors, 12f, Color.GRAY, PieDataSet.ValuePosition.OUTSIDE_SLICE);
        pieChartEntity.setHoleEnabled(Color.TRANSPARENT, 40f, Color.TRANSPARENT, 40f);
        pieChartEntity.setLegendEnabled(false);
        pieChartEntity.setPercentValues(true);
    }
}

运行方法就能实现动态图中数据正常的饼状图

但当数据过小,并且连在一起是就有文字重合的问题


这个时候问题就来了。

解决方案,产品决定占比小于5%或者10%不显示,或另外注明显示。

这下初级程序员就GG了,找不到满足需求的控件Demo参考啊,自定义又写不出来,这时大神微微一笑:加个参数判断一下不就OK啦


思路:三方库的默认PieEntry(float value, String lable),我加个构造方法,加个参数PieEntry(float value, String lable, boolean display)用来判断传的value是否满足要求,然后通过boolean display,同时控制绘图部分,当display为false,我就不花向外线和文字。


先看改造后的PieEntry.Java(修改位置有注释)

public class PieEntry extends Entry {

    private String label;

    /**
     * 用来标记是否显示描述文字
     */
    private boolean display = true;

    public PieEntry(float value) {
        super(0f, value);
    }

    public PieEntry(float value, Object data) {
        super(0f, value, data);
    }

    public PieEntry(float value, String label) {
        super(0f, value);
        this.label = label;
    }

    public PieEntry(float value, String label, Object data) {
        super(0f, value, data);
        this.label = label;
    }

    /**
     * 当传数据过小,调用此方法不显示文字
     * @param value
     * @param label
     * @param display
     */
    public PieEntry(float value ,String label,boolean display){
        super(0f,value);
        this.label = label;
        this.display = display;
    }
    /**
     * This is the same as getY(). Returns the value of the PieEntry.
     *
     * @return
     */
    public float getValue() {
        return getY();
    }

    public String getLabel() {
        return label;
    }

    /**
     * 文字绘制时用到做判断
     * @return
     */
    public boolean isDisplay() {
        return display;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * 设置display值,达到控制是否显示文字
     * @param display
     */
    public void setDisplay(boolean display) {
        this.display = display;
    }

    @Deprecated
    @Override
    public void setX(float x) {
        super.setX(x);
        Log.i("DEPRECATED", "Pie entries do not have x values");
    }

    @Deprecated
    @Override
    public float getX() {
        Log.i("DEPRECATED", "Pie entries do not have x values");
        return super.getX();
    }

    public PieEntry copy() {
        PieEntry e = new PieEntry(getY(), label, getData());
        return e;
    }
}

再看关于绘图的类PieChartRenderer.Java

这个类太长,主要修改方法是drawValues (修改位置有注释)

@Override
public void drawValues(Canvas c) {

    MPPointF center = mChart.getCenterCircleBox();

    // get whole the radius
    float radius = mChart.getRadius();
    float rotationAngle = mChart.getRotationAngle();
    float[] drawAngles = mChart.getDrawAngles();
    float[] absoluteAngles = mChart.getAbsoluteAngles();

    float phaseX = mAnimator.getPhaseX();
    float phaseY = mAnimator.getPhaseY();

    final float holeRadiusPercent = mChart.getHoleRadius() / 100.f;
    float labelRadiusOffset = radius / 10f * 3.6f;

    if (mChart.isDrawHoleEnabled()) {
        labelRadiusOffset = (radius - (radius * holeRadiusPercent)) / 2f;
    }

    final float labelRadius = radius - labelRadiusOffset;

    PieData data = mChart.getData();
    List<IPieDataSet> dataSets = data.getDataSets();

    float yValueSum = data.getYValueSum();

    boolean drawEntryLabels = mChart.isDrawEntryLabelsEnabled();

    float angle;
    int xIndex = 0;

    c.save();

    float offset = Utils.convertDpToPixel(5.f);

    for (int i = 0; i < dataSets.size(); i++) {

        IPieDataSet dataSet = dataSets.get(i);

        final boolean drawValues = dataSet.isDrawValuesEnabled();

        if (!drawValues && !drawEntryLabels)
            continue;

        final PieDataSet.ValuePosition xValuePosition = dataSet.getXValuePosition();
        final PieDataSet.ValuePosition yValuePosition = dataSet.getYValuePosition();

        // apply the text-styling defined by the DataSet
        applyValueTextStyle(dataSet);

        float lineHeight = Utils.calcTextHeight(mValuePaint, "Q")
                + Utils.convertDpToPixel(4f);

        IValueFormatter formatter = dataSet.getValueFormatter();

        int entryCount = dataSet.getEntryCount();

        mValueLinePaint.setColor(dataSet.getValueLineColor());
        mValueLinePaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getValueLineWidth()));

        final float sliceSpace = getSliceSpace(dataSet);

        for (int j = 0; j < entryCount; j++) {

            PieEntry entry = dataSet.getEntryForIndex(j);

            if (xIndex == 0)
                angle = 0.f;
            else
                angle = absoluteAngles[xIndex - 1] * phaseX;

            final float sliceAngle = drawAngles[xIndex];
            final float sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius);

            // offset needed to center the drawn text in the slice
            final float angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.f) / 2.f;

            angle = angle + angleOffset;

            final float transformedAngle = rotationAngle + angle * phaseY;

            float value = mChart.isUsePercentValuesEnabled() ? entry.getY()
                    / yValueSum * 100f : entry.getY();

            final float sliceXBase = (float) Math.cos(transformedAngle * Utils.FDEG2RAD);
            final float sliceYBase = (float) Math.sin(transformedAngle * Utils.FDEG2RAD);

            final boolean drawXOutside = drawEntryLabels &&
                    xValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
            final boolean drawYOutside = drawValues &&
                    yValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
            final boolean drawXInside = drawEntryLabels &&
                    xValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;
            final boolean drawYInside = drawValues &&
                    yValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;

            if (drawXOutside || drawYOutside) {

                final float valueLineLength1 = dataSet.getValueLinePart1Length();
                final float valueLineLength2 = dataSet.getValueLinePart2Length();
                final float valueLinePart1OffsetPercentage = dataSet.getValueLinePart1OffsetPercentage() / 100.f;

                float pt2x, pt2y;
                float labelPtx, labelPty;

                float line1Radius;

                if (mChart.isDrawHoleEnabled())
                    line1Radius = (radius - (radius * holeRadiusPercent))
                            * valueLinePart1OffsetPercentage
                            + (radius * holeRadiusPercent);
                else
                    line1Radius = radius * valueLinePart1OffsetPercentage;

                final float polyline2Width = dataSet.isValueLineVariableLength()
                        ? labelRadius * valueLineLength2 * (float) Math.abs(Math.sin(
                        transformedAngle * Utils.FDEG2RAD))
                        : labelRadius * valueLineLength2;

                final float pt0x = line1Radius * sliceXBase + center.x;
                final float pt0y = line1Radius * sliceYBase + center.y;

                final float pt1x = labelRadius * (1 + valueLineLength1) * sliceXBase + center.x;
                final float pt1y = labelRadius * (1 + valueLineLength1) * sliceYBase + center.y;

                if (transformedAngle % 360.0 >= 90.0 && transformedAngle % 360.0 <= 270.0) {
                    pt2x = pt1x - polyline2Width;
                    pt2y = pt1y;

                    mValuePaint.setTextAlign(Align.RIGHT);

                    if(drawXOutside)
                        mEntryLabelsPaint.setTextAlign(Align.RIGHT);

                    labelPtx = pt2x - offset;
                    labelPty = pt2y;
                } else {
                    pt2x = pt1x + polyline2Width;
                    pt2y = pt1y;
                    mValuePaint.setTextAlign(Align.LEFT);

                    if(drawXOutside)
                        mEntryLabelsPaint.setTextAlign(Align.LEFT);

                    labelPtx = pt2x + offset;
                    labelPty = pt2y;
                }

                /**
                 * 这里是绘制圈外线所以须添加
                 */
                if (entry.isDisplay()) {
                    if (dataSet.getValueLineColor() != ColorTemplate.COLOR_NONE) {
                        c.drawLine(pt0x, pt0y, pt1x, pt1y, mValueLinePaint);
                        c.drawLine(pt1x, pt1y, pt2x, pt2y, mValueLinePaint);
                    }
                }

                /**
                 * 这里也相关
                 */
                // draw everything, depending on settings
                if (drawXOutside && drawYOutside&&entry.isDisplay()) {

                    drawValue(c,
                            formatter,
                            value,
                            entry,
                            0,
                            labelPtx,
                            labelPty,
                            dataSet.getValueTextColor(j));

                    if (j < data.getEntryCount() && entry.getLabel() != null) {
                        drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight);
                    }

                } else if (drawXOutside) {
                    /**
                     * 一样相关
                     */
                    if (j < data.getEntryCount() && entry.getLabel() != null&&entry.isDisplay()) {
                        drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight / 2.f);
                    }
                } else if (drawYOutside) {

                    drawValue(c, formatter, value, entry, 0, labelPtx, labelPty + lineHeight / 2.f, dataSet
                            .getValueTextColor(j));
                }
            }

            if (drawXInside || drawYInside) {
                // calculate the text position
                float x = labelRadius * sliceXBase + center.x;
                float y = labelRadius * sliceYBase + center.y;

                mValuePaint.setTextAlign(Align.CENTER);

                // draw everything, depending on settings
                if (drawXInside && drawYInside&&entry.isDisplay()) {

                    drawValue(c, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j));

                    if (j < data.getEntryCount() && entry.getLabel() != null) {
                        drawEntryLabel(c, entry.getLabel(), x, y + lineHeight);
                    }

                } else if (drawXInside) {
                    if (j < data.getEntryCount() && entry.getLabel() != null) {
                        drawEntryLabel(c, entry.getLabel(), x, y + lineHeight / 2f);
                    }
                } else if (drawYInside) {

                    drawValue(c, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j));
                }
            }

            xIndex++;
        }
    }
    MPPointF.recycleInstance(center);
    c.restore();
}

好了,就写到这里,如果给你带来帮助,记得关注一下博主哦!


作者:DT235201314 发表于2017/4/12 15:45:04 原文链接
阅读:176 评论:0 查看评论

iOS打造属于自己的用户行为统计系统

$
0
0

  打造一款符合自己公司需求的用户行为统计系统,相信是很多运营人员的梦想,也是开发人员对技术的的执着追求。下面我为大家分一享下自己为公司打造的用户行为统计系统。
  用户行为统计(User Behavior Statistics, UBS)一直是移动互联网产品中必不可少的环节,也俗称埋点。对于产品经理,运营人员来说,埋点当然是越多,覆盖范围越广越好。废话废话就不多少了,这里我主要利用了AOP面向切片编程的思想来解决这个问题的。参考博客:参考博客地址首先声明,我这里并没有完全照搬别人博客,这里主要是顺着别人博客思路去走,走进死胡同,然后返璞归真,用自己的思路去实现的。之所以把别人的思路写下来讨论,就是为了说明思考的过程有时也很重要。

用户行为统计统计什么?

  我们常常说用户行为统计,那么用户行为统计主要统什计么呢,在我看来主要分为两类:1,页面统计:PV2,事件统计:Event

页面统计:PV

  页面统计就是就在用户进入某个页面的时候,进记行录保存;在用户离开某个页面的时候进行保存记录。在当适的时候将保存的数据发送给后台服务器。实现代码如下:

[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_ENTER];
        } error:nil];

        [UIViewController aspect_hookSelector:@selector(viewDidDisappear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_LEAVE];
        } error:nil];

很多博客贴出这样的代码以为就解决了问题,其实忽略了很大的一个问题,这样简粗单暴的去处理,会发现项中目所的有UIViewCnotroller的这两个方法viewDidAppear:viewDidDisappear:都被会hook,造了成额外的性能开销,非常的不好。所以我边这进行了处理只针对要统的计页面进行hook操作。具现体实如下:

+ (void)configPV{
    for (NSString *vcName in [JKUBS shareInstance].configureData[JKUBSPVKey]) {

        Class target = NSClassFromString(vcName);
        [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_ENTER];
        } error:nil];

        [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_LEAVE];
        } error:nil];
    }


}

事件统计:Event

  事件统计主要是在用户触发事件时进行记录保存,然后在合适的时候将记的录数据发送给后台服务器进行处理。按照文章开头参考博客所说,简单将件事分成了UIButotn,UIControl,UIGestureRecognizer以及点击UITableView单元格cell触发的事件,点击UICollectionView单元格cell触发的事件。
  按照这个思路我首先对UIButton,UIControl触发的事件进行处理:

+ (void)configUIControlEvent{

    [UIControl aspect_hookSelector:@selector(sendAction:to:forEvent:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> data){
        [self JKHandleEvent:data];
    } error:nil];

}

这个实现起来相对容易些,相信大家都有实现过。

  对UIGestureRecognizer触发的事件进行处理,比较麻烦 首先UIGestureRecognizer是一个类簇,我们触发事件时的tap,LongPress,swipe,pan等手势发送事件是并不是发送事件的真正的类,我这边通过打断点的形式找到了发送事件的真正的类是:UIGestureRecognizerTarget 发送事件的私有方法是:_sendActionWithGestureRecognizer: 然后我就通过hook操作对手势触发的事件进行了处理:

+ (void)configGestureRecognizerEvent{
    Class UIGestureRecognizerTarget =NSClassFromString(@"UIGestureRecognizerTarget");
    [UIGestureRecognizerTarget aspect_hookSelector:@selector(_sendActionWithGestureRecognizer:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> data){
        [self JKHandleEvent:data];
    } error:nil];


}

对手势触发的事件进行统计虽然困难,但还是实现了。
  对于点击UITableView单元格cell触发的事件,点击UICollectionView单元格cell触发的事件。我这边以点击UITableView单元格cell触发的事件为例进行说明。假设JKBViewController实现了UITableView 的代理方法tableView:didSelectRowAtIndexPath: 那么我的实现如下:

+ (void)configureDelegateEvent{

    [JKBViewController aspect_hookSelector:@selector(tableView:didSelectRowAtIndexPath:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> data){
        [self JKHandleEvent:data];
    } error:nil];

}

通过这个实现我们能够做到对点击UITableView单元格cell触发的事件进行统计,但是顺着参博考客作者的思路这一步一步做下来,做到这里我内心有种不的妙感觉。

走进死胡同

以下是参考的博客作者在开发的过程中遇到的问题

1,并不是所有的事件都是有继承自UIControl的空间来发出的,比如:手势,点击Cell。
2,并不是所有的按钮点击了之后就立马需要埋点上传?可能在按钮的响应方法中经过了层层的if(){ } else{ }最后才需要埋点。
4,对于代理方法该怎样处理?
5,如果很多个按钮对应着一个事件该怎样处理?

其针实对第1点,我边这虽然梳理了很多类型的事件,但是仍然有很多没有被统计上,比如摇一摇触发的事件,计步器触发的事件,tabBar点击触发的事件等,还很有多我可能没到想的事件,我现发如果按照作者的意图,按照事件触发的类型去一个一个的进行hook操作的话,工作两蛮大,而且还是会有遗漏的。尤是其涉及到有方些法苹果没有开放给开发者,我们进行处理的话比较麻烦。开员发人估被计要累死啊。
针对第2点,按作照者的意图,会现发点击之后里面还有层层的判断,如何绕过层层的判断呢?这个我会在接下来详细阐述。
针对第4点,我在上面已经实现过了。
针对第5点,在现实的情况中确实存在者不同的页面中,甚至相同的页面中不同的按钮对应着同一个事件这样的问题。如果按照参考博客作者的思路确实处理起来很是麻烦。

返璞归真

  针对上面出现的困境,我在想有没有更好的办法去解决呢。首先想到我们统计用户操的作事件,并是不为了统计用户点击了某个按钮,或者进行了某个手势操作,调了用某个代理方法。而为是了统计用户进行这个操作的目的是什么,是为了购物,还是为了分享等。所以我就打破参考博客作者的思路,不再对按钮,手势,单元格选中等事件进行hook,而是对用户的目的事件触发的方法进行hook,事件就是事件,没有来源之分。也就是hook就提示的事件,中间层层的逻辑判断,我不需要考虑,我只考虑hook的目的事件。举例个子,用户要行进分享- (void)goShare;,我不关心用是户否点击了按钮,或者tap手势触发了方法,或者单元格被中选,我只关心分享的方法- (void)goShare;有没有被调用,被调用的时候我是否可以进记行录操作。另外唯一确定一个方法,除了selector,还要有相关的target(方法的实现者,或者消息接受者)。针上面第5点,不同按钮对应同一个事件,一般情况下事件相同target不同,我们是能够区别的出来的。当了然也存在同一个页面上的不同按钮触发的同一个事件,这种情况下不是太常见,函数外面包一层,改个别的名字区分一下就好了,不过EnvetID还是要一样的。
  为了更好的方便大家,我这边按自照己的思路写了一个pod库,下面先说一下自己的plist文件文件:
这里写图片描述
大家可以看到PV字段下,每一个页面都以可设置页面的名字,还一有些其他的信息。
Event字段下有EventID,同时呢也允许同一个EventID下有不同的触发事件。
事件1这一级字段写上具体的事件内容,主要是方便开发人读员阅查找。
JKVC1点击,JKVC2点击,tap单击,选中tableView单元格这些都是为了标件来明事源,方便开发人员阅读。另外如果事件还需要配置额外的参数,那么可以在EventID同级字段下添加新的内容。
下看看面来代码吧:
JKUBS.h

#import <Foundation/Foundation.h>
#import "Aspects.h"


extern NSString const *JKUBSPVKey;
extern NSString const *JKUBSEventKey;
extern NSString const *JKUBSEventIDKey;
extern NSString const *JKUBSEventConfigKey;
extern NSString const *JKUBSSelectorStrKey;
extern NSString const *JKUBSTargetKey;


typedef NS_ENUM(NSInteger, JKUBSPVSTATUS){
    JKUBSPV_ENTER = 0,         //进入页面
    JKUBSPV_LEAVE              //离开页面
};

@interface JKUBS : NSObject

@property (nonatomic,strong,readonly) NSDictionary *configureData;




/**
 生成单例的方法

 @return 单例对象
 */
+ (instancetype)shareInstance;


/**
 通过json配置文件导入配置信息
json配置文件或plist配置文件只导入一个就好了
 @param jsonFilePath json文件沙盒路径
 */
+ (void)configureDataWithJSONFile:(NSString *)jsonFilePath;


/**
 通过plist配置文件导入配置信息
json配置文件或plist配置文件只导入一个就好了
 @param plistFileName plist文件名字(不带后缀名)
 */
+ (void)configureDataWithPlistFile:(NSString *)plistFileName;


/**
 处理PV
这个方法需要开发者重载进行具体的操作
 @param data 页面信息
 @param status 进入或离开页面的状态
 */
+ (void)JKhandlePV:(id<AspectInfo>)data status:(JKUBSPVSTATUS)status;


/**
 处理事件
这个方法需要开发者重载进行具体的操作
 @param data 事件信息
 @param eventId 事件ID
 */
+ (void)JKHandleEvent:(id<AspectInfo>)data EventID:(NSInteger)eventId;


@end

JKUBS.m

#import "JKUBS.h"

NSString const *JKUBSPVKey = @"PV";
NSString const *JKUBSEventKey = @"Event";
NSString const *JKUBSEventIDKey = @"EventID";
NSString const *JKUBSEventConfigKey = @"EventConfig";
NSString const *JKUBSSelectorStrKey = @"selectorStr";
NSString const *JKUBSTargetKey = @"target";


@interface JKUBS()

@property (nonatomic,strong,readwrite) NSDictionary *configureData;

@end

@implementation JKUBS
static JKUBS *_ubs =nil;
+ (instancetype)shareInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _ubs = [JKUBS new];
    });

    return _ubs;

}

+ (void)configureDataWithJSONFile:(NSString *)jsonFilePath{
    NSData *data = [NSData dataWithContentsOfFile:jsonFilePath];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
    [JKUBS shareInstance].configureData = dic;

    if ([JKUBS shareInstance].configureData) {
        [self setUp];
    }
}


+ (void)configureDataWithPlistFile:(NSString *)plistFileName{
    NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:plistFileName ofType:@"plist"]];
    [JKUBS shareInstance].configureData = dic;

    if ([JKUBS shareInstance].configureData) {
        [self setUp];
    }

}


+ (void)setUp{

    [self configPV];
    [self configEvents];

}

#pragma mark PVConfig - - - -

+ (void)configPV{
    for (NSString *vcName in [JKUBS shareInstance].configureData[JKUBSPVKey]) {

        Class target = NSClassFromString(vcName);
        [target aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_ENTER];
        } error:nil];

        [target aspect_hookSelector:@selector(viewDidDisappear:) withOptions:AspectPositionAfter usingBlock:^(id data){
            [self JKhandlePV:data status:JKUBSPV_LEAVE];
        } error:nil];
    }


}



+ (void)JKhandlePV:(id<AspectInfo>)data status:(JKUBSPVSTATUS)status{


}

#pragma mark EventConfig - - - -

+ (void)configEvents{

    NSDictionary *eventsDic = [JKUBS shareInstance].configureData[JKUBSEventKey];
    NSArray *events =[eventsDic allValues];
    for (NSDictionary *dic in events) {
        NSInteger EventID = [dic[JKUBSEventIDKey] integerValue];
        NSArray *eventConfigs = [dic[JKUBSEventConfigKey] allValues];
        for (NSDictionary *eventConfig in eventConfigs) {
            NSString *selectorStr = eventConfig[JKUBSSelectorStrKey];
            NSString *targetClass = eventConfig[JKUBSTargetKey];
            Class target =NSClassFromString(targetClass);
            SEL selector = NSSelectorFromString(selectorStr);

                [target aspect_hookSelector:selector withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> data){
                    [self JKHandleEvent:data EventID:EventID];
                } error:nil];


        }
    }


}

+ (void)JKHandleEvent:(id<AspectInfo>)data EventID:(NSInteger)eventId{

}

其中有两个方法要重点说一下。

+ (void)JKhandlePV:(id<AspectInfo>)data status:(JKUBSPVSTATUS)status;
+ (void)JKHandleEvent:(id<AspectInfo>)data EventID:(NSInteger)eventId;

这两个方法都需要在JKUBS的category进行重载,来做具体的实现。例如页面活动的记录,事件的记录。打造用户行为统计系统,我这边已经完成了AOP思想下的事件采集,具体如何记录,保存,发给送后台,这里就不详细说明了。

代码下载地址
使用pod如下:

pod "JKUBS"
作者:HHL110120 发表于2017/4/12 17:45:10 原文链接
阅读:220 评论:0 查看评论

iOS 动画解析 XLBallLoading

$
0
0

一、显示效果

显示效果

二、原理分析

1、拆解动画

从效果图来看,动画可拆解成两部分:放大动画位移动画
放大动画 比较简单,这里主要来分析一下位移动画

1、先去掉放大/缩小效果:

屏蔽放大效果

2、去掉其中的一个圆球

这里写图片描述

现在基本可以看出主要原理就是让其中一个圆球绕另一个球做圆弧运动,下面咱们重点说一下这个圆弧运动的原理。

2、圆弧运动

为了方便观察我们先放慢一下这个动画,然后添加辅助线:
放慢后的效果图

从图中可以看出,蓝色球主要经过了三段轨迹

第一段:从左边缘逆时针运动180°到灰色球的右侧
第二段:从灰色球右侧贴着灰色球逆时针运动180°到其左侧
第三段:从灰色球左侧返回起始位置

既然分析出了运动轨迹,下面实现起来就方便了

第一段:
这里写图片描述

第二段:
第二段运动轨迹示意

三、实现代码

第一段运动的的实现

    //动画容器的宽度
    CGFloat width = _ballContainer.bounds.size.width;
    //小圆半径
    CGFloat r = (_ball1.bounds.size.width)*ballScale/2.0f;
    //大圆半径
    CGFloat R = (width/2 + r)/2.0;
    UIBezierPath *path1 = [UIBezierPath bezierPath];
    //设置起始位置
    [path1 moveToPoint:_ball1.center];
    //画大圆(第一段的运动轨迹)
    [path1 addArcWithCenter:CGPointMake(R + r, width/2) radius:R startAngle:M_PI endAngle:M_PI*2 clockwise:NO];

第二段运动:

//画小圆
    UIBezierPath *path1_1 = [UIBezierPath bezierPath];
    //圆心为灰色小球的中心 半径为灰色小球的半径
    [path1_1 addArcWithCenter:CGPointMake(width/2, width/2) radius:r*2 startAngle:M_PI*2 endAngle:M_PI clockwise:NO];
    [path1 appendPath:path1_1];

第三段运动:

//回到原处
    [path1 addLineToPoint:_ball1.center];

利用关键帧动画实现小球沿设置好的贝塞尔曲线移动:

    //执行动画
    CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation1.path = path1.CGPath;
    animation1.removedOnCompletion = YES;
    animation1.duration = [self animationDuration];
    animation1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [_ball1.layer addAnimation:animation1 forKey:@"animation1"];

在每次位移动画开始时执行缩放动画:

//放大缩小动画
-(void)animationDidStart:(CAAnimation *)anim{

    CGFloat delay = 0.3f;
    CGFloat duration = [self animationDuration]/2 - delay;

    [UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveEaseOut| UIViewAnimationOptionBeginFromCurrentState animations:^{
        _ball1.transform = CGAffineTransformMakeScale(ballScale, ballScale);
        _ball2.transform = CGAffineTransformMakeScale(ballScale, ballScale);
        _ball3.transform = CGAffineTransformMakeScale(ballScale, ballScale);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveEaseInOut| UIViewAnimationOptionBeginFromCurrentState animations:^{
            _ball1.transform = CGAffineTransformIdentity;
            _ball2.transform = CGAffineTransformIdentity;
            _ball3.transform = CGAffineTransformIdentity;
        } completion:nil];
    }];
}

在每次动画结束时从新执行动画:

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    if (_stopAnimationByUser) {return;}
    [self startPathAnimate];
}

Github地址

作者:u013282507 发表于2017/4/12 19:43:28 原文链接
阅读:9 评论:0 查看评论

Android_IPC机制

$
0
0

本文主要讲解Android中的IPC机制

IPC简介

IPC是inter-Process-Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间交换数据的过程.说起进程间的通信,首先我们的理解什么是进程和线程,面试题必答题哈哈…当然了这两个肯定不是一回事.

线程: 按照操作系统中的描述.线程是CPU调度的最小单元,同事线程是系统的有限系统资源.
进程: 是指一个执行单元.在PC和移动端是指一个程序或者一个应用.

一个进程可以包含多个线程,因此进程和线程是包含于被包含的关系.最简单的情况下,一个进程只有一个线程,即主线程.在Android里边主线程也交错UI线程,在UI线程才能才能操作界面元素.很多时候一个进程中需要执行大量的耗时任务,如果这些任务放在主线程中执行,就会造成界面无法响应,严重影响用户体验,这种情况PC和移动端都会出现.在Android系统我们称之为ANR(Android Not Responding),即应用无响应.解决这个问题就需要用到线程,把一些耗时任务放在线程中即可.


Android中最有特色的进程间通信是Binder,Binder可以轻松实现进程间通信,除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然同一个设备两个进程通过Socket通信也是可以的.甚至我们使用系统提供的ContentProvider去查询数据的时候,也是一种进程间的通讯,只不过通信细节被系统屏蔽了.

IPC概念介绍

介绍一下IPC中的一些基础概念,主要包含三方面的内容:Serializable接口,Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好理解进程间通信的各种方式.
Serializable接口和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent或Binder传输数据时就需要使用Parcelable或者Serializable.

Serializable接口

Serializable是java提供的序列化接口,他是一个空接口,为对象提供标准的序列化和反序列化的操作.
只要我们的自定义bean实现该接口就可以实现序列化操作(如下代码)

package cn.yuan.xiaoyu.testmodule.bean;

import java.io.Serializable;

/**
 * Created by yukuoyuan on 2017/4/10.
 */
public class TestBean implements Serializable {
    public int age;
    public String name;
    public String phone;
    public String email;

    @Override
    public String toString() {
        return "TestBean{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

Parcelable接口

Parcelable 是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并通过Intent 或者Binder传递(如下代码)

package cn.yuan.xiaoyu.testmodule.bean;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by yukuoyuan on 2017/4/10.
 */

public class TestBean implements Parcelable {
    public int age;
    public String name;
    public String phone;
    public String email;

    @Override
    public String toString() {
        return "TestBean{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.age);
        dest.writeString(this.name);
        dest.writeString(this.phone);
        dest.writeString(this.email);
    }

    public TestBean() {
    }

    protected TestBean(Parcel in) {
        this.age = in.readInt();
        this.name = in.readString();
        this.phone = in.readString();
        this.email = in.readString();
    }

    public static final Parcelable.Creator<TestBean> CREATOR = new Parcelable.Creator<TestBean>() {
        @Override
        public TestBean createFromParcel(Parcel source) {
            return new TestBean(source);
        }

        @Override
        public TestBean[] newArray(int size) {
            return new TestBean[size];
        }
    };
}

方法说明
这里写图片描述


系统给我们提供了许多实现Parcelable接口的类他们都是可以直接序列化的.例如Intent,Bundle,Bitmap等,List和Map也可以序列化.前提是他们的元素类型是可以序列化的

Parcelable和Serializable区别

既然Parcelable和Serializable都能实现序列化并且都可用于Intent之间的数据传递,那么二者该如何选取呢?Serializable是Java中的序列化接口,其使用起来很简单但是开销很大.序列化和反序列化需要大量的IO操作.而Parcelable是Android中的序列化方式.因此更适合用在Android平台上.它的缺点是使用起来稍微复杂一点.但是它的效率很高.这是Android推荐的序列化方式.因此我们要首选Parcelable,Parcelable主要用于内存序列化上,通过Parcelable将对象序列化到存储设备或者将对象序列化后通过网络传输也都是可以的.但是这个过程会稍微复杂,因此在这两种情况下建议使用Serializable.以上就是Parcelable和Serializable的区别.

Binder

Binder 是一个很深入的话题

Binder是Android的一个类,它实现了IBinder接口,从IPC角度来说呢,BInder是Android中一种跨进程通信的方式.
在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间的通信,所以较为简单,无法触及Binder的核心,其中Messenger的底层是AIDL,所以我们用AIDL来分析Binder的工作机制.
我们通过一个小demo来讲解

User.java

package cn.yuan.xiaoyu.testmodule.bean;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by yukuoyuan on 2017/4/13.
 */
public class User implements Parcelable {
    private int userId;
    private String userName;
    private int age;
    private int sex;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.userId);
        dest.writeString(this.userName);
        dest.writeInt(this.age);
        dest.writeInt(this.sex);
    }

    public User() {
    }

    protected User(Parcel in) {
        this.userId = in.readInt();
        this.userName = in.readString();
        this.age = in.readInt();
        this.sex = in.readInt();
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

//未完待续….

作者:EaskShark 发表于2017/4/13 10:11:30 原文链接
阅读:251 评论:0 查看评论

FFmpeg总结(六)AV系列结构体之AVPacket

$
0
0

AVPacket位置:libavcodec/avcodec.h下:

这里写图片描述

AVPacket:

  • 通常通过demuxer导出的data packet作为解码器的input data
  • 或是收到来自编码器的data packet,通过muxer进入输出的output data

看如下图,更易理解:
这里写图片描述

对于视频来说,它通常应该包含一个压缩的帧,对于音频,可能包含多个压缩帧,允许编码器输出为空的packet,没有压缩数据,只包含数据(如一些更新参数的编码)

AVPacket 是FFmpeg中为数不多的结构体,它的size是public ABI的一部分,因此它没有libavcodec及libavformat主块中获得分配栈空间及添加新字段。

数据所有权的的语义决于buf字段,如果是组,分组数据是动态分配的,在调用av_packet_unref()把引用计数降到0之前,都是有效定义的。

如果buf字段没有设置av_packet_ref()将拷贝一份代替增加的引用计数

  • 数据分配总是分配通过av_malloc()
  • 数据拷贝总是通过av_packet_ref()
  • 数据释放总是通过av_packet_unref()
typedef struct AVPacket {

    AVBufferRef *buf; //一个引用指向packet数据存储的buffer的引用计数,如果为NULL,packet数据没有引用计数

    int64_t pts; //显示时间戳(AVStream->time_base units)pts主要用于度量解码后的视频帧什么时候被显示出来

    int64_t dts; //解码时间戳(AVStream->time_base units)
    //DTS主要用于视频的解码,在解码阶段使用.PTS主要用于视频的同步和输出.
    //在display的时候使用.在没有B frame的情况下.DTS和PTS的输出顺序是一样的.
    uint8_t *data;
    int   size;
    int   stream_index;

    int   flags; //AV_PKT_FLAG标识

    AVPacketSideData *side_data; //容器中能提供的其他packet数据,packet中包含一些具体信息
    int side_data_elems;

    int64_t duration; // packet的时长(AVStream->time_base units)

    int64_t pos;  //< byte position in stream, -1 if unknown

#if FF_API_CONVERGENCE_DURATION
    /**
     * @deprecated Same as the duration field, but as int64_t. This was required
     * for Matroska subtitles, whose duration values could overflow when the
     * duration field was still an int.
     */
    attribute_deprecated
    int64_t convergence_duration;
#endif
} AVPacket;
作者:hejjunlin 发表于2017/4/13 10:47:22 原文链接
阅读:184 评论:0 查看评论

iOS中block块的存储位置&内存管理

$
0
0

block是iOS开发中一种使用方便的代码块,但是在使用过程中也很容易不小心就造成问题,本文讲解其存储位置所决定的内存修饰以及如何避免循环引用。

iOS内存分区

先讲讲大的,关于iOS在内存中的分区情况。

内存分为五个区:栈区、堆区、全局区、常量区、代码区。这五个区在物理上是分开的,如下图所示:

这五个区存储的内容也各有划分:

  • 栈区(stack):这一块区域系统会自己进行管理,我们不用干预,主要存一些局部变量,以及函数跳转时的现场保护。因此大量的局部变量、深递归、函数循环调用都可能耗尽内存而造成运行崩溃。
  • 堆区(heap):与栈区相对,这一块一般由我们开发人员管理,比如一些alloc、free的操作,存储一些自己创建的对象。
  • 全局区(静态区 static):全局变量和静态变量都存储在这里,已经初始化的和没有初始化的变量会分开存储在相邻的区域,程序结束后系统来释放。
  • 常量区:存储常量字符串和const常量。
  • 代码区:顾名思义,就是存我们写的代码。

block块存储位置

block块根据情况有两种可能的存储位置,一种存在代码区,一种存在堆区。

1、如果block块没有访问处于栈区的变量(比如局部变量),也没有访问堆区的变量(比如我们alloc创建的对象),那就存在代码区,即使访问了全局变量,也依然存在代码区。

2、如果访问了栈区或者堆区的变量,那就会被存在堆区(实际存在栈区,ARC下会自动拷贝到堆区)。

关于存在堆区的情况,有一点需要注意的是,堆区是不断变化的,不断地有变量的创建和销毁,如果block块没有强引用,那也随时可能被销毁,这就导致一旦在销毁时访问block块,程序就会崩溃,所以,在定义block时,内存修饰最好用strong或者copy。而且在使用时也最好先判断一下block是否为空,比如:

if (!block) {
    return;
}
block();

循环引用

既然在修饰block时,使用了strong,那么另一个问题就需要注意了,也就是循环引用。

当使用了strong修饰后,self会强引用block,而如果在block中又需要访问self的一些属性或者方法,从而调用了self,这时self和block就进入循环引用,容易内存溢出。

解决的办法时在block中的需要用到self时,事先将self用__weak修饰,这样互相引用的一方就不再是强引用了。

比如:

__weak ViewController *weakSelf = self;
self.block = ^{
    weakSelf.str = @"123";
};

但是这样还不够,在多线程下,单单使用weakSelf,可能前一刻weakSelf还在,后面需要用时却被释放掉了,毕竟弱引用是不稳定的,这时候就需要又使用一个修饰符__strong来在block中修饰,是不是操碎了心。

因此更好的释放方式如下:

__weak __typeof(self) *weakSelf = self;
self.block = ^{
    __strong __typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        strongSelf.str = @"123";
    }
    // 如果不用了,应置为空
    strongSelf.block = nil;
};

平常使用block的情况很多,很多人往往都是直接拿样例代码改着用了,不知道为什么要这么修饰block,也不知道weakSelf、strongSelf有什么用。这里就从存储位置来解释为什么要这样修饰block,从而又会造成循环引用的问题,最后如何去解决他。希望可以帮助大家更好的理解手中的每一行代码。


版权所有:http://blog.csdn.net/cloudox_

作者:Cloudox_ 发表于2017/4/13 14:24:35 原文链接
阅读:194 评论:0 查看评论

iOS中 仿Tumblr点赞心破碎动画 韩俊强的博客

$
0
0

最近Tumblr轻博客无论是web端还是移动端,都非常受欢迎,简单调研了一下,其中动画是我感兴趣的,特此写了个仿Tumblr点赞心破碎动画;


1.首先看下效果:



2.模仿Tumblr中的效果应用如下:



原理:使用按钮点击Action增加两个事件,通过改变背景hidden和frame,切换图片,增加动画效果等;

setupUI及touch Action:

- (void)setupUI
{
    // 点击的btn
    UIButton *praiseBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    praiseBtn.frame = CGRectMake(100, 200, KKPraiseBtnWH, KKPraiseBtnWH);
    [praiseBtn setImage:[UIImage imageNamed:@"icon_like"] forState:UIControlStateNormal];
    [praiseBtn setImage:[UIImage imageNamed:@"icon_likeon"] forState:UIControlStateSelected];
    [self.view addSubview:praiseBtn];
    [praiseBtn addTarget:self action:@selector(clickTheBtn:) forControlEvents:UIControlEventTouchUpInside];
    _praiseBtn = praiseBtn;
    
    // 放大后的btn
    _coverBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    _coverBtn.frame = praiseBtn.frame;
    _coverBtn.alpha = 0;
    [_coverBtn setImage:[UIImage imageNamed:@"big"] forState:UIControlStateSelected];
    [_coverBtn setImage:[UIImage imageNamed:@"big"] forState:UIControlStateNormal];
    [self.view insertSubview:_coverBtn belowSubview:praiseBtn];
    _cancelPraiseImg = [[UIImageView alloc]initWithFrame:CGRectMake(80, 150, KKPraiseBtnWH*2, KKPraiseBtnWH*2*KKToBrokenHeartWH)];
    _cancelPraiseImg.hidden = YES;
    _cancelPraiseImg.centerX = _praiseBtn.centerX;
    [self.view addSubview:_cancelPraiseImg];
}

-(void)clickTheBtn:(UIButton *)btn
{
    [self playAnimation];
    btn.userInteractionEnabled = NO;
    btn.selected = !btn.selected;
}
-(void)playAnimation{
    if (!_praiseBtn.selected) {
        _coverBtn.alpha = 1;
        [UIView animateWithDuration:1.0f animations:^{
            _coverBtn.frame = CGRectMake(80, 100, KKPraiseBtnWH*2, KKPraiseBtnWH*2);
            
            CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
            NSValue *value1 = [NSNumber numberWithFloat:-M_PI/180*5];
            NSValue *value2 = [NSNumber numberWithFloat:M_PI/180*5];
            NSValue *value3 = [NSNumber numberWithFloat:-M_PI/180*5];
            anima.values = @[value1,value2,value3];
            anima.repeatCount = MAXFLOAT;
            [_coverBtn.layer addAnimation:anima forKey:nil];
            
            _coverBtn.alpha = 0;
            _coverBtn.centerX = _praiseBtn.centerX;
        } completion:^(BOOL finished) {
            _coverBtn.frame = _praiseBtn.frame;
            _praiseBtn.userInteractionEnabled = YES;
        }];
    } else {
        _cancelPraiseImg.hidden = NO;
        NSArray *imgArr = [NSArray arrayWithObjects:[UIImage imageNamed:@"icon_like_broken1"],[UIImage imageNamed:@"icon_like_broken2"],[UIImage imageNamed:@"icon_like_broken3"],[UIImage imageNamed:@"icon_like_broken4"], nil];
        _cancelPraiseImg.animationImages = imgArr;
        _cancelPraiseImg.animationDuration = KKBorkenTime;
        _cancelPraiseImg.animationRepeatCount = 1;
        [_cancelPraiseImg startAnimating];
        
        [UIView animateWithDuration:KKBorkenTime animations:^{
            _cancelPraiseImg.frame = CGRectMake(80, 200, KKPraiseBtnWH*2, KKPraiseBtnWH*2*KKToBrokenHeartWH);
            _cancelPraiseImg.alpha = 0;
        }completion:^(BOOL finished) {
            _cancelPraiseImg.frame = CGRectMake(80, 150, KKPraiseBtnWH*2, KKPraiseBtnWH*2*KKToBrokenHeartWH);
            _cancelPraiseImg.alpha = 1;
            _praiseBtn.userInteractionEnabled = YES;
        }];
    }
}


                                                            详情请看Demo:喜欢❤️star一下吧,你的支持是我最大的动力!

                                                            更多:每周更新关注新浪微博!iOS开发者交流群:446310206


作者:qq_31810357 发表于2017/4/13 14:44:24 原文链接
阅读:318 评论:0 查看评论

UI/View-ComboxBox控件获取选中项

$
0
0

.NET提供的comboxBox控件,获取选中的项,总是容易混淆。在这里备忘一下。
通过属性 selectItem 获取当前选中的某一项,而不是通过 selectText

作者:daigualu 发表于2017/4/13 15:36:50 原文链接
阅读:136 评论:0 查看评论

Android textview和listview实现水平自动滚动的走马灯效果

$
0
0

当我们遇到需要在一个textview里显示较长文字时候,往往有以下几种考虑:

          1.换行,Android里本身也是这样在考虑;

          2.可以进行水平或者垂直滑动;

          3.采用走马灯效果。

          其实这三种要实现都不难,根据实际情况进行选择就是了。不过我在具体实现走马灯时候遇到了一些问题,后来参考了下大神的代码,得以解决,记录一下。

          代码地址是:http://download.csdn.net/download/wds1181977/5997065

          具体情况如下:


          textview里要实现走马灯效果,主要需要三点:

          设置单行显示;设置文本超出textview后的效果;以及最重要的是设置焦点。

          对应代码如下:

<TextView
    android:id="@+id/tv_title"
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:text="基本信息"
    android:textColor="@color/black"
    android:gravity="center_vertical"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:singleLine="true"
    android:ellipsize="marquee"
    android:marqueeRepeatLimit="marquee_forever"  />

         focusable和fucosableInTouchMode是配套用来设置获取焦点的;singleLine是设置单行;ellipsize是设置文本过长的效果,其中marquee是走马灯的效果,marqueeRepeatLimit是自动滚动显示多少次,marquee_forver是一直滚动。

         这样写好了以后,但实际我运行的时候并没有出现走马灯的效果,而是显示了最前面部分,然后就是,,,省略掉后面内容了。

         找了一下原因,就是在这个获取焦点上。

         一般情况我们的布局都比demo复杂,静态的设置focusable=true甚至动态的在代码用textview.setfocusable里设置并不能保证焦点就是在这个textview上。

         所以更好的办法是写一个自定义的textview,将焦点写死。比如:

public class MTextView  extends TextView {

    public MTextView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public MTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean isFocused() {
        // TODO Auto-generated method stub
        return true;
    }

}
        然后在代码里 引用:

<com.diit.apppro.presentation.view.component.common.MTextView
    android:id="@+id/tv_title"
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:text="基本信息"
    android:textColor="@color/black"
    android:gravity="center_vertical"
    android:scrollHorizontally="true"
    android:singleLine="true"
    android:ellipsize="marquee"
    android:marqueeRepeatLimit="marquee_forever"  />
         这样就可以实现了。

         如果是listview的话,只需要在每个item里的textview引用这个自定义view即可实现各自的走马灯效果。


作者:bit_kaki 发表于2017/4/13 16:19:08 原文链接
阅读:26 评论:0 查看评论

android混合开发:cordova的安装使用

$
0
0

1、什么是cordova

cordova是一个开源的移动开发框架,开发者可以使用html、css、js做跨平台开发,并且可以利用其提供的组件调用移动设备的硬件功能,如:传感器、拍照等。也就是说:cordova提供了js与原生的交互通道

cordova的前身是phonegap,是从phonegap剥离出来开源贡献给apache的,所以俩者的使用是完全相同的,只是phonegap提供了一下收费功能,如云发布。

本文以windows进行cordova安装调试,并默认您掌握了安卓开发技能,熟练使用android studio

2、安装cordova

  • 下载安装Node.js,下载对应的pc平台版本,因为cordova需要通过npm安装,所以Node.js必不可少。

  • 在windos上安装Cordova CLI开发工具,打开控制台执行以下命令行:

    C:>npm install -g cordova
    此时会自动下载安装,安装结束后输入cordova,如果出现帮助信息,说明您已成功安装Cordova CLI开发工具。

3、配置环境变量


  • jdk1.8安装及环境变量配置,请注意是jdk1.8
  • android sdk 环境变量配置,不配置将会无法编译
  • 配置完成后命令行输入一下命令验证是否成功:

C:>cordova requirements

4、新建项目

进入您需要存放项目的文件夹,输入命令行:

cordova creat hellowCordova com.yourname HellowCordova

其中hellowCordova是工程文件名名称,com.yourname是项目的包名,当然这个包名可以在配置文件里面修改,这个后期再说,HellowCordova是项目名称。

进入新建的项目文件:

cd:hellowCordova

添加安卓平台:

cordova platform add android –save

添加成功后,项目的platforms文件夹下将会生成android文件,这就是安卓端源码保存的地方。

编译项目:

cordova build

如果我们用的是as自带的sdk下载工具下载的adk,这一步会报找不到gradle wrapper,用sdk下载工具自行下载或者从以前es的sdk中拷贝指定文件过来就行,错误提示已经很明显了。

Error: Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.
Looked here: D:\deleve_tools\sdk_as\tools\templates\gradle\wrapper

至此项目创建成功。

5、工程文件结构

工程文件结构

图为项目成功创建后的文件结构图,各文件功能如下:

  • hooks:存放自定义cordova命令的脚本
  • platforms:存放各个平台编译后的文件,注意每次build都会覆盖修改该目录下文件
  • plugins:存放插件的目录
  • www:存放html前端页面的源码路径
  • config.xml:配置文件

6、导入到android studio

打开as,导入项目platforms路径下的android文件,编译后的项目结构:
这里写图片描述

结构跟eclipse项目差不多,html页面资源保存于assets中(强烈建议不要修改www目录下的文件,每次cordova build后该文件会修改),src目录下的文件可以修改。这样我们就可以利用cordova进行混合开发了,将前端页面放到根文件的www中,编译项目,通过as开发native部分。

项目运行图:
这里写图片描述

作者:q649381130 发表于2017/4/13 16:59:31 原文链接
阅读:75 评论:0 查看评论

Android--百度地图报错:at com.baidu.location.LocationClient.onStart(Unknown Source)

$
0
0

在使用百度地图定位的时候,报一个异常

Exception Ljava/lang/UnsatisfiedLinkError;   
threadid=1: thread exiting with uncaught exception (group=0x410d19d8)  
  
FATAL EXCEPTION: main  
  
java.lang.ExceptionInInitializerError  
at com.baidu.location.g.a(Unknown Source)  
at com.baidu.location.f.int(Unknown Source)  
at com.baidu.location.f.int(Unknown Source)  
at com.baidu.location.f.if(Unknown Source)  
  
at com.baidu.location.f$b.handleMessage(Unknown Source)  
at android.os.Handler.dispatchMessage(Handler.java:99)  
  
at android.os.Looper.loop(Looper.java:137)  
at android.app.ActivityThread.main(ActivityThread.java:4439)  
at java.lang.reflect.Method.invokeNative(Native Method)  
at java.lang.reflect.Method.invoke(Method.java:511)  
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)  
、  
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)  
  
at dalvik.system.NativeStart.main(Native Method)  
Caused by: java.lang.UnsatisfiedLinkError: Couldn't load locSDK_2.4: findLibrary returned null  
  
at java.lang.Runtime.loadLibrary(Runtime.java:365)  
at java.lang.System.loadLibrary(System.java:535)  
at com.baidu.location.Jni.<clinit>(Unknown Source) 

解决办法:首先检查libs下是否缺少支持的.so文件,(我的已经存在)

继续检查,发现new LocationClient 这个的时候 需要用 getApplicationContext。 
问题解决。(用全局变量context)

作者:chaoyu168 发表于2017/4/13 17:04:34 原文链接
阅读:105 评论:0 查看评论

android开发中遇到的问题汇总【十】

$
0
0

294. java中 volatile static结合使用 static 静态 volatile 不稳定的 JAVA 里static 和volatile的区别

变量放在主存区上,使用该变量的每个线程,都将从主存区拷贝一份到自己的工作区上进行操作。

volatile, 声明这个字段易变(可能被多个线程使用),Java内存模型负责各个线程的工作区与主存区的该字段的值保持同步,即一致性。

static, 声明这个字段是静态的(可能被多个实例共享),在主存区上该类的所有实例的该字段为同一个变量,即唯一性。

volatile, 声明变量值的一致性;static,声明变量的唯一性。

此外,volatile同步机制不同于synchronized, 前者是内存同步,后者不仅包含内存同步(一致性),且保证线程互斥(互斥性)。
static 只是声明变量在主存上的唯一性,不能保证工作区与主存区变量值的一致性;除非变量的值是不可变的,即再加上final的修饰符,否则static声明的变量,不是线程安全的。

1) If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. 

2) A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable。

295. ids的作用和使用场景

作用:通过ids.xml中事先定义好id,在使用时候不用重新生成对应的id,提高性能和可维护性。优化编译效率。统一管理资源Id。 eg:如果没有ids.xml中定义。在layout文件中声明方式如下@+id/xxx。 如果定义过,使用方式如下@id/xxx 即不用加"+"号。 使用场景,对于需要同意管理资源id的场景,比如框架id 参考android项目中values中ids.xml的作用

297. Android, ListView IllegalStateException: “The content of the adapter has changed but ListView did not receive a notification”

http://stackoverflow.com/questions/3132021/android-listview-illegalstateexception-the-content-of-the-adapter-has-changed

299. Error:Could not read cache value from ‘xxx/gradle/daemon/2.10/registry.bin’.

根据路径找到registry.bin,删除,重启androidstudio即可。

300. Android: AlertDialog causes a memory leak

in the leaked activity’s onDestroy(), set the AlertDialog’s ListView’s onItemClickListener() to null, which will release the reference to the listener an make whatever memory allocated within that listener to be eligible for GC. This way you won’t get OOM. It’s just a workaround and the real solution should actually be incorporated in the ListView.

Here’s a sample code for your onDestroy():

@Override 
protected void onDestroy() { 
    super.onDestroy(); 
    if(leakedDialog != null) { 
            ListView lv = leakedDialog.getListView();
            if(lv != null)  lv.setOnItemClickListener(null);
    } 
} 

对于adapter同理 参考 http://stackoverflow.com/questions/7083441/android-alertdialog-causes-a-memory-leak

301 viewpager setcurrentItem之后,调用相关的监听 onpageSelected onPageChangedState onPageScrolled

如果是想在动画执行完成之后,执行某些操作,可以通过如下方式

private class PageChangeListener implements OnPageChangeListener { 

    @Override 
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
   } 

    @Override 
    public void onPageSelected(int position) {
        isPageChanged = true; 
    } 

    @Override 
    public void onPageScrollStateChanged(int state) {
        switch (state) {
        case ViewPager.SCROLL_STATE_IDLE:
            if (isPageChanged) { 
                updateCurrentPage();//this will be called when animation ends 
                isPageChanged = false; 
            } 
            break; 
        case ViewPager.SCROLL_STATE_DRAGGING:
            break; 
        case ViewPager.SCROLL_STATE_SETTLING:
            break; 
        } 
    } 
} 

参考Android viewpager animation

302 Instant Run does not support deploying build variants with multidex enabled, to a target with API level 20 or below. To use Instant Run with a multidex enabled build variant, deploy to a target with API level 21 or higher.”);

http://stackoverflow.com/questions/36516931/instant-run-disabled-for-multidexed-application

303 java.util.ConcurrentModificationException at java.util.LinkedList$LinkIterator.next(LinkedList.java:124)

linkedlist 不是线程安全的,用ConcurrentLinkedQueue 参考 LinkedList多线程不安全的解决办法

304 sqlite 出现 unrecognized token: “xxxx”

使用sql 语句中,如果有字符串,必须加上 ‘ ‘单括号 括起来

You need to escape the filename parameter. The punctuation in the filename is confusing SQLite. You could do it by surrounding the filename in 'single quotes' in the string you pass in to SQLite, but it`s cleaner and safer to pass it as a separate argument, like this:

sqliteDatabase.update(AndroidOpenDbHelper.TABLE_FILE, values, 
        AndroidOpenDbHelper.COLUMN_NAME_FILE_NAME+"=?", new String[] {filename});

android.database.sqlite.SQLiteException: unrecognized token:

305 ScrollView嵌套ListView,listItem.measure(0,0);报空指针异常NullPointerException

当调用listItem.measure(0, 0);报空指针时问题:
检查Adapter适配时Item的根容器为RelativeLayout,
报错原因:

In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. (See MeasureSpec.makeMeasureSpec for more details.) This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIED was placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead.
This behavior has been preserved for apps that set android:targetSdkVersion="17" or older in their manifest`s uses-sdktag for compatibility. Apps targeting SDK version 18 or newer will receive the correct behavior

有三种解决方案:

一、升级版本到4.2.2
二、更改根容器为LinearLayout
三、在适配器里添加convertView.setLayoutParams(new LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); convertView为Item的view

参考ScrollView嵌套ListView,listItem.measure(0,0);报空指针异常NullPointerException

306 Webview访问https 报错 primary error: 5 certificate: Issued to: CN=*

public void onReceivedSslError(final WebView view, final SslErrorHandler handler, SslError error) {
    Log.d("CHECK", "onReceivedSslError");
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    AlertDialog alertDialog = builder.create();
    String message = "Certificate error.";
    switch (error.getPrimaryError()) {
        case SslError.SSL_UNTRUSTED:
            message = "The certificate authority is not trusted.";
            break;
        case SslError.SSL_EXPIRED:
            message = "The certificate has expired.";
            break;
        case SslError.SSL_IDMISMATCH:
            message = "The certificate Hostname mismatch.";
            break;
        case SslError.SSL_NOTYETVALID:
            message = "The certificate is not yet valid.";
            break;
    }
    message += " Do you want to continue anyway?";
    alertDialog.setTitle("SSL Certificate Error");
    alertDialog.setMessage(message);
    alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("CHECK", "Button ok pressed");
            // Ignore SSL certificate errors
            handler.proceed();
        }
    });
    alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("CHECK", "Button cancel pressed");
            handler.cancel();
        }
    });
    alertDialog.show();
    }
});
webView.loadUrl("https://www.google.co.in/");

参考
Android : Workaround for webview not loading https url
Android WebView not loading an HTTPS URL
How can load https url without use of ssl in android webview

[Android安全开发之安全使用HTTPS](https://www.easyaq.com/newsdetail/id/37169867.shtml) 这是一篇很好的文章!!!

目前很多应用都用webview加载H5页面,如果服务端采用的是可信CA颁发的证书,在webView.setWebViewClient(webviewClient)时重载WebViewClient的onReceivedSslError(),如果出现证书错误,直接调用handler.proceed()会忽略错误继续加载证书有问题的页面,如果调用handler.cancel()可以终止加载证书有问题的页面,证书出现问题了,可以提示用户风险,让用户选择加载与否,如果是需要安全级别比较高,可以直接终止页面加载,提示用户网络环境有风险:
![](https://cdn.easyaq.com/@/image/download/y2361mq04769o3.JPG)
不建议直接用handler.proceed(),聚安全的应用安全扫描器会扫出来直接调用handler.proceed()的情况。
如果webview加载https需要强校验服务端证书,可以在onPageStarted()中用HttpsURLConnection强校验证书的方式来校验服务端证书,如果校验不通过停止加载网页。当然这样会拖慢网页的加载速度,需要进一步优化,具体优化的办法不在本次讨论范围,这里也不详细讲解了。

需要在客户端中预埋证书文件,或者将证书硬编码写在代码中

正确使用HTTPS并非完全能够防住客户端的Hook分析修改,要想保证通信安全,也需要依靠其他方法,比如重要信息在交给HTTPS传输之前进行加密,另外实现客户端请求的签名处理,保证客户端与服务端通信请求不被伪造 

更多问题请关注 android开发遇到问题点滴

作者:u011570979 发表于2017/4/13 22:37:18 原文链接
阅读:75 评论:0 查看评论

dwc3验证套件 day4:debug

$
0
0

其实地上本没有路,走的人多了,也便成了路。 –鲁迅

一、工作进展

自从前几天dwc3 day3–platform,转眼一个星期了。上周六(8号)在验证环境下实验,发现一直不能检测到port connect信号。这周一(10号)检查了一天没发现什么问题,周二早上终于发现了,原来是phycfg寄存器的suspend位置1了。周三上午set address和get device descriptor的setup阶段都成功了。但是设备模型一直没有返回数据。真是头疼,今天又找mentor的人和SoC的朋友一起检查波形。

二、调试记录

2.1 代码流程分析

在遇到问题的时候经常需要review一下代码,随着不断的遇到问题,不知不觉review了好几次代码,每次都看出一些不兼容的地方。

这个代码执行流程是这样的:

  1. 控制器初始化,定义hccr,hcor,ctrl等关键数据结构,映射寄存器地址。
  2. 检测配置hub状态,获取hub描述符。
  3. 以roothub为根设备进行usb设备识别。检测到hub的connect信息之后,重新调用usb_new_device进行设备识别。

2.2 修改打印接口

更新串口打印接口并宏化,方面后续屏蔽。更改内存分配接口。

2.3 利用仿真环境查看执行情况

三、后续规划

host识别ramdisk设备之后,测试读写。

作者:abcamus 发表于2017/4/13 22:39:40 原文链接
阅读:83 评论:0 查看评论

SpriteKit改变Node锚点其物理对象位置不正确的解决

$
0
0

在创建Node的物理对象后,默认情况下物理对象和Node的实际边界对应的很好,因为此时Node的默认锚点是其中心位置即(0.5,0.5),不过如果我们改变了Node的锚点,就会发现其物理边界还是保持原来的位置,这可不是我们想要的结果:

let bouncer = SKSpriteNode(imageNamed: "bouncer")
        bouncer.anchorPoint = CGPoint(x: 0.5, y: 0)
        bouncer.position = CGPoint(x: 0, y: -frame.size.height/2.0)

        bouncer.physicsBody = SKPhysicsBody(circleOfRadius: bouncer.size.width/2.0)

        addChild(bouncer)

在打开了物理调试选项之后运行App,我们可以清楚地看到实际发生了神马:

这里写图片描述

之所以Node会诡异悬空,是因为其物理对象的边界已卡位在屏幕底部了.那为什么Node边界和物理边界不对应呢?因为我们在代码中修改了它的锚点位置.

你可能以为物理对象也有诸如锚点之类的属性可以调整,但是并没有 :(

不过你可以在初始化物理对象的时候人肉定位其中心点,just like this:

bouncer.physicsBody = SKPhysicsBody(circleOfRadius: bouncer.size.width/2.0, center: CGPoint(x: 0, y: bouncer.size.height/2.0))

我来解释下一下center的含义:默认(0,0)是Node的中心位置,所以x不需要调整仍是0,不过y如上图所示,需要上移半个高度.

这里写图片描述

再次运行App,问题得以解决!

作者:mydo 发表于2017/4/14 10:27:40 原文链接
阅读:238 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


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