Https相关-HostnameVerifier

[强制]在实现的HostnameVerifier子类中,需要使用verify函数效验服务器主机名的合法性,否则会导致恶意程序利用中间人攻击绕过主机名效验。

说明:
在握手期间,如果URL的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口实现程序来确定是否应该允许此连接,如果回调内实现不恰当,默认接受所有域名,则有安全风险。

反例:
HostnameVerifier hnv=new HosernameVerifier(){
  @Override
  public boolean verify(String hostname,SSLSession session){
      return true;
  }
}
正例:
HostnameVerifier hnv=new HosernameVerifier(){
@Override
public boolean verify(String hostname,SSLSession session){
    if("youhostname".equals(hostname)){
        return true;
    }else{
          HostnameVerifier        hv=HttpsURLConnection.getDefaultHostnameVerifier();
         return hv.verify(hostname,session);
          }
  }
}

经验

./gradlew clean -i assembleDebug

发现 连接dl.google.com超时

则使用http://ping.chinaz.com/网站ping下,找到dl.google.com对应的ip

在本地hosts中添加一条记录

203.208.43.66 dl.google.com

再编译就好了

bei

recyclerView.setNestedScrollingEnabled(true);

https://www.jianshu.com/p/635970ac603a

https://hfutfei.iteye.com/blog/988374

https://www.cnblogs.com/frankliiu-java/articles/1759460.html

https://www.cnblogs.com/zhaoyan001/p/6365064.html

https://blog.csdn.net/android_gogogo/article/details/53376178

https://my.oschina.net/djone/blog/145057

https://blog.csdn.net/lv_fq/article/details/77836700

https://github.com/lvfaqiang/AndroidTestCode

https://github.com/osmandapp/Osmand

http://www.justlive.vip/blog/article/details/4199

https://github.com/jiaowenzheng/CustomTextView

http://www.cnblogs.com/TerryBlog/archive/2013/04/02/2994815.html

https://www.cnblogs.com/zhujiabin/p/5808232.html

https://github.com/Luction/AndroidRichText

https://blog.csdn.net/u014620028/article/details/54092723

https://www.cnblogs.com/zhujiabin/p/5808232.html

http://www.cnblogs.com/luction/p/3645210.html

https://www.jianshu.com/p/b87dddf02e04

https://juejin.im/post/5b13a5b8f265da6e3128d501

https://tonnyl.io/Spantastic-Text-Styling-With-Spans/

http://melonteam.com/posts/gei_ni_de_spannablestring_she_zhi_dian_ji_tai/

https://blog.51cto.com/kinbos/1348407

https://blog.csdn.net/c16882599/article/details/52913799

https://segmentfault.com/a/1190000006163046

https://blog.csdn.net/natloc/article/details/50849700

http://blog.hacktons.cn/2015/02/03/porterduff/

https://www.jianshu.com/p/d54e24efbd7b

https://github.com/MrAllRight/BezierView/

https://www.jianshu.com/p/a5c2f0359d31

https://www.jianshu.com/p/016534448bfe

https://www.jianshu.com/p/92627f72c707

https://blog.csdn.net/ccpat/article/details/46717573

Androidbug-java.util.zip.ZipException: duplicate entry(重复依赖多版本的类库)

以上大概意思:重复依赖,重复了条目。

1、清除一下缓存:File->Invalidate Caches/Restart..

注意:
除开Gradle依赖类库之外,还可以在项目中的libs下静态方式添加类库。
若是使用静态方式添加jar类库的,请删除重复的jar

2、编译前clean下

https://blog.csdn.net/hexingen/article/details/74065796

Androidbug-android-sdk-macosx/cmake/3.6.4111459/bin/cmake

Build command failed.
Error while executing process /Users/xx/Documents/software/android-sdk-macosx/cmake/3.6.4111459/bin/cmake with arguments {-H/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app -B/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app/.externalNativeBuild/cmake/debug/armeabi -GAndroid Gradle - Ninja -DANDROID_ABI=armeabi -DANDROID_NDK=/Users/xx/Documents/software/android-sdk-macosx/ndk-bundle -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app/build/intermediates/cmake/debug/obj/armeabi -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=/Users/xx/Documents/software/android-sdk-macosx/cmake/3.6.4111459/bin/ninja -DCMAKE_TOOLCHAIN_FILE=/Users/xx/Documents/software/android-sdk-macosx/ndk-bundle/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=android-16 -DCMAKE_CXX_FLAGS=}
(include) CMakeLists.txt
Open File
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
Build command failed.
Error while executing process /Users/xx/Documents/software/android-sdk-macosx/cmake/3.6.4111459/bin/cmake with arguments {-H/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app -B/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app/.externalNativeBuild/cmake/release/armeabi -GAndroid Gradle - Ninja -DANDROID_ABI=armeabi -DANDROID_NDK=/Users/xx/Documents/software/android-sdk-macosx/ndk-bundle -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/xx/Documents/qtt/RTC_Android_v1.2.1/qtt_test/app/build/intermediates/cmake/release/obj/armeabi -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=/Users/xx/Documents/software/android-sdk-macosx/cmake/3.6.4111459/bin/ninja -DCMAKE_TOOLCHAIN_FILE=/Users/xx/Documents/software/android-sdk-macosx/ndk-bundle/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=android-16 -DCMAKE_CXX_FLAGS=}
(include) CMakeLists.txt
Open File
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!

却换NDK版本,我用了android-ndk-r15b

Glide

Transformation<Bitmap> circleCrop = new CircleCrop();
if (data.imgUrl.endsWith("gif")) {
Glide.with(context)
.asGif()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.load(data.imgUrl)
.into(imageView);
} else if (data.imgUrl.endsWith("webp")) {
Glide.with(context)
.load(data.imgUrl)
.optionalTransform(circleCrop)
.optionalTransform(WebpDrawable.class, new WebpDrawableTransformation(circleCrop))
.into(imageView);
} else {
Glide.with(context)
.load(data.imgUrl)
.into(imageView);
}

Android播放器-顶点坐标和材质坐标

顶点坐标信息

                     ^
|
4 | 3
(1, -1, 0) <----------------- (1, 1, 0)
| | |
| | |
---------------------|------------------------->
| | |
| | |
V | V
(-1, -1, 0) <-----——----------- (1, -1, 0)
2 | 1
|

上三角形:3->4->2->3
下三角形:1->2->3->1
共向量:2->3

float *vertexData = new float[12]{
1f, -1f, 0f,
-1f, -1f, 0f,
1f, 1f, 0f,
-1f, 1f, 0f
}

材质坐标信息

         ^
|
4 | 3
(0, 1) ----------- (1, 1)
| |
| |
| |
-------(0, 0) ----------- (1, 0)------>
2 | 1
|

glTexParameteri

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

GL_TEXTURE_2D: 操作2D纹理
GL_TEXTURE_MIN_FILTER: 缩小过滤
GL_TEXTURE_MAG_FILTER: 放大过滤

GL_LINEAR: 线性过滤,使用距离当前渲染像素中心最近的4个纹理加权平均值

Android播放器-着色器语言GLSL

着色器语言又叫Shader,有两个模块一个是顶点着色器,一个是片元着色器

  • 顶点着色器是针对每个顶点执行一次,用于确定顶点的位置;
  • 片元着色器是针对每个片元(可以理解为每个像素)执行一次,用于确定每个片元(像素)的颜色;
  • GLSL的基本语法与C基本相同
  • 它完美的支持向量和矩阵操作
  • GLSL提供了大量的内置函数来提供丰富的扩展功能
  • 它是通过限定符操作来管理输入输出类型的

ffmpeg -i 720.mp4 -pix_fmt yuv420p -s 424×240 out.yuv

Android播放器-EGL

EGL是OpenGL与窗口系统对应的适配层

https://www.khronos.org/registry/EGL/sdk/docs/man/

---RenderAPI(OpenGL ES)---
-----------EGL------------
---Native Window System---
Display与原生窗口链接
EGLDisplay eglGetDisplay
EGLBoolean eglInitialize

Surface配置和创建surface(窗口和屏幕上的渲染区域)
EGLBoolean eglChooseConfig
EGLSurface eglCreateWindowSurface

Context创建渲染环境(Context上下文)
渲染环境指OpengGL ES的所有项目运行需要的数据结构。如顶点、片段着色器、顶点数据矩阵
eglCreateContext
eglMakeCurrent

Android播放器-OpenSLES使用流程

  1. 创建并设置SL引擎
  2. 创建并设置混音器
  3. 创建并设置播放器(有数据队列),设置回调并写入缓存队列

enqueue()->Buffer Queue->slBufferQueueCallback

初始化引擎

SLObjectltf engineObject = NULL; // 相当于引擎上下文
SLEngineltf engineEngine = NULL; // 引擎对象接口

slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);

(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // 阻塞方法,也可以用其他的回调方法
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);

具体函数参数

// 创建对象
SL_API SLresult SLAPIENTRY slCreateEngine(
SLObjectltf *pEngine,
SLuint32 numOptions,
const SLEngineOption *pEngineOptions, // 选择项目 默认参数
SLuint32 numInterfaces,
const SLInterfaceID *pInterfaceIds, // 支持的接口
const SLboolean *pInterceRequired // 接口标识数值,是否支持
)

// 实例化
SLresult (*Realize)(
SLObjectltf self,
SLboolean async
)
对象已实现状态(false阻塞)

// 获取接口
SLresult (*GetInterface)(
SLObjectltf self,
const SLInterfaceID id,
void *pInterface
)

创建输出设备

SLEngineltf engineEngine = NULL;
SLObjectltf outputMixObject = NULL;

(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
// 实例化
(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); // 阻塞

// 输出对象
SLDataLocator_OutputMix outputMix = {
SL_DATALOCATOR_OUTPUT_MIX,
outputMixObject
};

SLDataSink audioSnk = {&outputMix, NULL}

配置PCM格式信息

// 缓存队列配置信息
SLDataLocator_AndroidSimpleBufferQueue android_queue = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2
};
// 播放的音频的格式信息
SLDataFormat_PCM pcm = {
SL_DATAFORMAT_PCM, // 播放pcm格式的数据
2, // 2个声道
SL_SAMPLINGRATE_44_1, // 44100hz的频率
SL_PCMSAMPLEFORMAT_FIXED_16, // 位数 16位
SL_PCMSAMPLEFORMAT_FIXED_16, // 和位数一致就行
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_PRONT_RIGHT, // 立体声(前左前右)
SL_BYTEORDER_LITTLEENDIAN // 字节序 小端模式
}

SLDataSource slDataSource = {
&android_queue,
&pcm
};

初始化播放器

const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};

// 创建
(*engineEngine)->CreateAudioPlayer(
engineEngine,
&pcmPlayerObject,
&slDataSource,
&audioSnk,
1,
ids,
req);

// 初始化
(*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);

// 得到接口后调用 获取Player接口
(*pcmPlayerObject)->GetInterface(
pcmPlayerObject,
SL_IID_PLAY,
&pcmPlayerPlay);

播放和缓存队列

注册回调缓冲区

// 获取缓冲区队列接口
(*pcmPlayerObject)->GetInterface(
pcmPlayerObject,
SL_IID_BUFFERQUEUE,
&pcmBufferQueue);

// 在缓冲区读完之后,回调函数就会调用
(*pcmBufferQueue)->RegisterCallback(
pcmBufferQueue,
pcmBufferCallBack,
NULL);
// 回调函数被调用后,需要立刻往缓冲队列里加数据,否则会出现断音

(*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);

// 发一个空数据""={\0} (0是不会发声的),激活回调
(*pcmBufferQueue)->Enqueue(pcmBufferQueue, "", 1);


Android播放器-对解码后数据处理

视频像素和尺寸转换

用ffmpeg处理的话,大概要20ms,性能消耗很大,但其代码处理简单

// 多线程中最好用这个
sws_getContext

// 单线程可以使用这个
// 如果传入的context的格式和新的格式一样,它就会直接返回当前context
struct SwsContext *sws_getCachedContext(
struct SwsContext *context,
int srcW,
int srcH,
enum AVPixelFormat srcFormat,
int dstW, // 尺寸的转换
int dstH,
enum AVPixelFormat dstFormat, // 像素格式的转换
int flags,
SwsFilter *srcFilter, // 过滤器
SwsFilter *dstFilter,
const double *params); // 缩放算法,相关参数

flags,提供了一系列的算法,差值的算法,矩阵的算法,使得缩放图片
#define SWS_FAST_BILNEAR 1
#define SWS_BILINEAR 2
#define SWS_BICUBIC 4
#define SWS_X 8
#define SWS_POINT 0x10
#define SWS_AREA 0x20
#define SWS_BICUBLIN 0x40

int sws_scale(
struct SwsContext *c,
const uint8_t *const srcSlice[], // 指针数组,二维数组 YUV(数组大小3) RGBA(数组大小1)
const int srcStride[], // 一行的大小
int srcSliceY, // 深度
int srcSliceH,
uint8_t *const dst[],
const int dstStride[]);

void sws_freeContext(struct SwsContext *swsContext);

Android播放器-解码

  • 解封装
  • 软硬解码
  • 像素格式转换
  • 重采样
  • pts/dts
  • 同步策略

avcodec_find_decoder

avcodec_register_all(); // 注册所有的解码器
AVCodec *avcodec_find_decoder(enum AVCodecID id)
AVCodec 只是存放了解码器格式的配置信息
AVCodec *avcodec_find_decoder_by_name(const char *name)
avcodec_find_decoder_by_name("h264_mediacodec") // 用Android里面自带的解码模块,ARM CPU是一个集合,里面自带一个解码模块,其实就是调用了Android里面的java接口

AVCodecContext

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec) // 可以不传解码器,只是创建的解码器上下文里面会没有解码器
void avcodec_free_context(AVCodecContext **avctx)

// 打开
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); // 如果AVCodecContext里面已经有解码器,就不需要传codec,options:参考/libavcodec/options_table.h

int thread_count; // 多线程
time_base // 时间的基数

avcodec_parameters_to_context

将codec的参数拷贝到context
avcodec_parameters_to_context(codec, p);

AVFrame

AVFrame *frame = av_frame_alloc();
void av_frame_free(AVFrame **frame);// 引用计数减一,并释放本身
int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用计数加一
AVFrame *av_frame_clone(const AVFrame *src); // 复制,并引用计数加一
void av_frame_unref(AVFrame *frame); // 直接引用计数减一

成员:
uint8_t *data[AV_NUM_DATA_POINTERS] // 可能是交叉模式或平面模式
int linesize[AV_NUM_DATA_POINTERS] // 视频表示一行数据大小,音频表示一个通道数据大小
int width;
int height;
int nb_samples; // 音频,表示单通道样本数量,一般1024
int64_t pts;
int64_t pkt_dts;
int sample_rate;
uint64_t channel_layout;
int channels;
int format; // AVPixelFormat AVSampleFormat

linesize

平面模式(因为有对齐的原因):

0 Y大小
1 U大小
2 V大小
3 NULL

0 左声道(planner)大小
1 右声道大小
2 NULL

0 RGB RGB RGB大小
1 NULL

avcodec_send_packet

int avcodec_send_packet(
AVCodecContext *avctx,
const AVPacket *avpkt);
// 调用完这个函数后,可以安全的释放avpkt,因为内部已经缓存了一个AVPacket空间

avcodec_receive_frame

int avcodec_receive_frame(
AVCodecContext *avctx,
AVFrame *frame);

可能前面几帧解不出来,获取的这一帧可能是之前的,怎么取出结尾中的几帧,也可能取到多个

Android播放器-解封装

  • 解封装
  • 软硬解码
  • 像素格式转换
  • 重采样
  • pts/dts
  • 同步策略

解封装

av_register_all()  // 注册所有的格式(解封装和加封装格式)
avformat_network_init() // 想支持rtsp等网络协议,初始化网络模块

avformat_open_input(...) // 打开文件并解析
avformat_find_stream_info(...) // 查找文件格式和索引

av_find_best_stream(...) // 查找对应的流

AVFormatContext 解加封装上下文,有AVStream数组
AVStream 音视频流信息
AVPacket 解封装完的数据包(视频的话,h264的间隔符00 00 01去掉了)

av_read_frame(...)

int avformat_open_input函数

确保av_register_all avformat_network_init已调用
AVFormatContext **ps 可以传入指向空指针的指针,也可以传入指向自己申请空间地址的指针
const char *url 可以是本地的文件,也可以是网络地址(http,rtsp)
AVInputFormat *fmt 一般不用,直接传null,指定封装格式(如果是频繁打开,最好指定)
AVDictionary **options 一般可以给null,可参考libavformat\options_table.h

AVFormatContext

AVIOContext *pb;  // io context 如果要自定义一些读写格式就要用到这个
char filename[1024]; // 文件名,断开重连需要用到

unsigned int nb_streams; // 数组大小
AVStream **streams; // 一般0是视频,1是音频,但不能保证

int64_t duration; // 以AV_TIME_BASE为单位,获取的总长度,不一定能获取到
int64_t bit_rate; // 比特率

void avformat_close_input(AVFormatContext **s);

关闭完之后,内部帮我们已经置零了

avformat_find_stream_info

获取视频流的信息
在一些文件中,可能没有文件信息(如flv格式、h264格式的),这时我们前面调用的avformat_open_input打开文件后,就没有参数,获取不到文件信息
我们就可以用avformat_find_stream_info,来探测一下流信息,

815,281 261,869.51 554,018.67

AVStream

AVCodecContext *codec; // 过时了
AVRational time_base; // 时间基数 一般为一百万 其实就是分数,这是了减少精度的损失,表示一秒钟有多少个,就是多少分之一秒
int64_t duration; // 转换成毫秒:
// duration*((double)time_base.num/(double)time_base.den)*1000
AVRational avg_frame_rate: // 帧率
AVCodecParameter *codecpar; // 音视频参数

enum AVMediaType codec_type; // 编码类型,表示是音频还是视频
enum AVCodecID codec_id; // 编码格式
unit32 codec_tag; // 用四个字节表示各种编码器
int format; // 指的是像素格式或音频的采样格式
int width;
int height;
uint64_t channel_layout; // 2.1声道,还是3声道
int channels; // 声道数
int sample_rate;
int frame_size; // 一帧音频的大小

写一段代码就测试一下

av_find_best_stream

获取音视频的索引
int av_find_best_stream(
AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb, // 默认设置-1
int related_stream, // 相关的流信息,一个文件里可能有多个节目
AVCodec **decoder_ret, // 解码器
int flags // 暂未使用的参数
)

av_read_frame

参数:
AVFormatContext *s
AVPacket *pkt // 不能传NULL,输出参数,内部会分配帧空间
返回值:
return 0 if OK
< 0 on error or end of file

AVPacket

AVBufferRef *buf; // 可能多个AVPacket共用 buf
int64_t pts; // pts * (num/den)
int64_t dts; // 根据dts顺序发给解码器
uint8_t *data;
int size;

相关函数:
AVPacket *av_packet_alloc(void) // 创建并初始化
AVPacket *av_packet_clone(const AVPacket *src) // 创建并引用计数
int av_packet_ref(AVPacket *dst, const AVPacket *src); // 增加引用
av_packet_unref(AVPacket *pkt) // 减引用
void av_packet_free(AVPacket **pkt) // 清空对象并减引用计数
void av_init_packet(AVPacket *pkt) // 设置默认值
int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size) // 自己构造AVPacket包
int av_copy_packet(AVPacket *dst, const AVPacket *src); // 不建议使用

av_seek_frame

int av_seek_frame(
AVFormatContext *s,
int stream_index, // 流索引 默认-1,即视频
int64_t timestamp, // 和pts一致,AVStream.time_base
int flags
)
stream_index:
音频不存在IBP帧,而视频有IBP帧,所以最好用视频移动到关键帧位置

flags:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward 往后找,就找过去的时间点
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes 根据文件大小位置来跳,这时时间戳就是文件位置
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes 针对frame来说,就跳到这个frame,可能会花屏
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number 找关键帧

Android播放器-Jni

JNI原始数据类型

JNI 原始数据类型
Java Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

JNI引用类型

jobject  (all java objects)
|--jclass (java.lang.Class.objects)
|--jstring (java.lang.String.objects)
|--jarray (array)
| |--jobjectArray (object arrays)
| |--jbooleanArray (boolean arrays)
| |--jbyteArray (byte arrays)
| |--jcharArray (char arrays)
| |--jshortArray (short arrays)
| |--jintArray (int arrays)