lvpengwei’s Blog

学习历程,生活点滴。

iOS&Android 播放透明视频

| Comments

在 iOS & Android 上播放带 alpha 的 mp4 视频,用来代替序列帧动画(png/webp),素材小,播放也流畅。

用滤镜就可以完成,Demo 地址:
iOS
Android

如何使用 AE 制作带 alpha 的 mp4

原理:因为视频只有 rgb,没有 a,所以制作两个视频,第一个是原视频,第二个是纯黑白的边界视频,然后把两个视频合成一个,目的是保证帧对应。第一个视频的 rgb 是 0~1 的 float 值,第二个视频的 rgb 要么全是 1,要么全是 0。在客户端进行渲染的时候,写一个滤镜,目标像素 rgba 在取值的时候,rgb 使用第一个视频的 rgb,a 使用第二个视频的 r,就可以完成播放透明视频。

1
gl_FragColor = vec4(color1.rgb, color2.r);

导出迪斯尼乐拍通里的照片

| Comments

迪斯尼乐拍通App里面的照片导出收费,而且费用很高,我通过抓包拿到App里面的图片链接然后使用脚本下载解析得到高清的图片(非原图) 代码:Disneyphotopass-export

过程

  1. 抓包:使用Charles拿到某张图片的链接,下载下来之后发现是另外一个图片,如下图,这时候就蛋疼了,那我需要的那张图去哪了,而且这张图片的分辨率不高,按理说不会这么大,那么猜测我需要的那张图就藏在这返回的数据里面
    enter image description here
  2. 隐写术:之前不知道这个概念,所以我就进行了各种尝试去找
    • 是否是gif
    • 使用iOS ImageIO的API去看是不是在图片的exif里面
    • 然后问了伟哥,是否有方法把一张图片放在另外一张图片里,伟哥说“有好多图片隐写的方法你可以搜一下”
      算是找到了这种方案的一个术语-隐写,然后发现其中一种隐写方法的解析工具binwork,拿出之前下载的图片试一下,果然里面放了两张图片,如下图 enter image description here
  3. 解析:使用命令可以解出想要的图片dd if=image-download/1 bs=1 skip=10103 of=image/1.jpg enter image description here

脚本

然后把这个过程写成一个脚本,输入是一行一个链接的文本,输出是想要的图片,脚本来做下载和解析。

tokenId获取

脚本可以自动收集链接,需要抓包获取tokenId

  1. 使用浏览器(Chrome):登录网页版乐拍通,然后检查(Inspect)网络(Network)里找
  2. 乐拍通App抓包

链接的收集(Deprecated)

给手机设上代理,打开乐拍通,进入图片详情,然后一张一张滑过去,然后过滤一下,全选,右击,Copy URLs,然后粘在一个disneyphotopass文档里,执行脚本,就可以了。 enter image description here

iOS APP的简单Web Interface应用

| Comments

  最近接了一个内部工具开发的需求:运营人员需要生成商品尺码图片,之前他们是用PS做的,现在想让我们做个程序工具化这个过程。

刚拿到这个需求,分析之后,想了两个方案:

  1. APP生成图片,导出到电脑
  2. h5生成图片save到电脑
  3. 脚本生成,因为UI比较复杂,需要获取数据,所以排除

由于对APP开发比较熟悉,所以尝试着用客户端去做,生成图片不难,关键是如何导出到电脑上,当时的思路是

  1. iOS的话保存到相册用AirDrop去传
  2. Android保存到SD卡上,用PC上的软件导出
  3. 打成zip包,客户端起一个server,把zip download下来

本着操作简洁的原则,开始探索第三种方案。

一个程序,对用户来说只要有输入和输出就行了,中间不需要什么过多的介入,这才是优化流程的意义所在。

在这个例子中,输入是商品id数组,输出是一个zip包。

设计流程是:接收一个商品id数组,开始进行处理,最终输出一张图片save到Documents的一个文件夹下,然后进行zip打包,最后进行下载。

遇到的问题:

1.iOS的AutoLayout,图片的模板是用AutoLayout实现,变高(基于AutoLayoutviewContentSize概念)。问题就是截图的时候会报警告,然后出的图是空的,debug之后,发现是view赋值之后没有进行layout,所以size是CGSizeZero的,截图时得到的context是0x0,之前在项目中,没有出现此问题,是因为view一般都会有superview,而superview在layout的时候subview也会layout。这里的view没有superview,所以没人触发它的layout过程,加了两行代码,完成。

1
2
[self setNeedsUpdateConstraints];
[self layoutIfNeeded];

2.在提交商品id数组之后,如果处理过程是异步的,那么如何通知用户是个问题,本来打算是手机local notification通知,但是我们这个APP直接部署在局域网的Mac Pro的模拟器里面,不需要用户安装,而web页面做push操作比较麻烦,所以采用同步处理,也就是说,提交过商品id数组之后页面一直loading,直到处理完毕,给出下一个页面。在APP端,商品信息的加载和商品主图的加载也是异步的,那么怎么才能在一个请求中做同步呢,也就是收到web请求的时候,提交到一个manager中去处理这部分商品,但是这里sleep掉,等那边处理完毕之后,再唤醒接着返回response,于是就想到了iOS的Runloop这个机制。代码如下:

MyHTTPConnection.m

1
2
3
4
5
6
7
8
9
10
11
12
NSPort *port = [NSMachPort port];
[[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
[ExportImageManager sharedInstance].thread = [NSThread currentThread];
[[ExportImageManager sharedInstance] createImageWithGoodsIds:goodsIdArr];
while (![ExportImageManager sharedInstance].completed) {
    NSLog(@"runloop start......");
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    NSLog(@"runloop end......");
}
[[NSRunLoop currentRunLoop] removePort:port forMode:NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());
[ExportImageManager sharedInstance].thread = nil;

注解: 这块代码执行的环境是在HTTPServer的队列里面,所以在哪个线程中是未知的,线程中的runloop默认是不开启的,所以在这里,做了几件事

  1. 声明一个port放在runloop中,防止runloop中没有任何输入源会直接退出
  2. 把当前所在的thread赋给manager,当处理完成的时候,在这个thread上performSelector,唤醒runloop
  3. 在while循环中启动runloop
  4. 把之前的port remove掉
  5. 把runloop停掉
  6. 之前引用的thread清空

最终实现,3个操作步骤

  1. localhost:8081/create?id,id,id,id 图片生成请求,response返回的时候处理结束,会在Documents/export/的文件夹下生成以商品id命名的图片
  2. localhost:8081/zipDocuments/export打包成Documents/export.zip 并删除Documents/export
  3. localhost:8081/export.zip 下载zip

其实可以把打包操作合并在第一步中。缩减为两步

思路来源是,QQ阅读传pdf的时候是在电脑上打开一个网页,然后把文件拖进去就可以同步到手机。实现之后发现,这个功能和CharlesWeb Interface功能一样。。

项目地址:https://github.com/lvpengwei/ExportGoodsImage

Simulator Screen Shot Jun 13, 2016, 17.00.33.png

Android坐标系学习案例(与iOS进行对比)

| Comments

问题: 判断parent view中的手势是否出现在旋转之后的subview的范围中的解决方法(iOS&Android)

解决思路: 取出点击位置的point, 然后translate到subview的坐标系中, 判断是否在subview的矩形中即可.

iOS:

现象:

7DBEE162-CB37-44E7-8560-4E50556532DA.png

代码:

0FDD8A27-5C7F-48EB-A0DD-853FDA4B846C.png

结果:

78067F60-E64B-4FFB-AA67-68783D824B32.png

部分解释: iOS中的view有frame/bounds/center/transform等坐标属性,

四者的区别(具体可查看文档, 非常详细)

  • frame: 相对于parent view的坐标系
  • bounds: 相对于自己的坐标系
  • center: view的中心点(跟layer的anchorPoint有关, anchorPoint默认值是(0.5, 0.5), 所以是中心点)
  • transform: 相对于center的缩放/旋转等二维操作. layer中有个transform是三维的.

三者之间的关系是-bounds/center/transform会影响frame的值.

大部分情况, 我们使用的frame(x, y, width, height)只影响到了boundscenter, 此时transform是初始值CGAffineTransformIdentity; 而当我们要设置transform的时候, frame的值就会变得很奇怪. 其实frame还是那个矩形, 不过它代表的含义是能包含这个view的最小矩形而已. 因为transform涉及到scale/rotation等操作, 所以frame看起来和我们真正想要的值不太一样, 而这时候就是需要分开使用center/bounds/transform的时候, 不能单独设置frame.

layer的相关属性不介绍, 有兴趣可以直接查看API文档.

Android:

类似的, 我开始在Android中寻找相应的解决方案. 首先需要明白的几个点

  • Android的每一个view也有自己的坐标系, 左上角是原点(0, 0)
  • Android的view的基础坐标属性是left/top/right/bottom. width和height是由前四个值推算得来.
  • Android的view的matrix作用对应于iOS的view的transform.

解决思路还是上面所述, 但是取出点击的point容易, translate到subview的坐标系中遇到了一些问题, iOS中此api存在于UIView中, 而Android的View却没有. 搜索一圈, 还是在Android的事件分发的源码中获取答案. 现象:

A5809894-B554-433E-B603-172DC01AEB93.png

代码:

B7B8A792-4D22-4EDF-B672-A96C1A5FCCA4.png

布局代码:

007A1AED-36E2-4A51-B5CA-7970D56DC40C.png

结果:

2C8C9B9F-FD20-4986-BE8B-BAAA346943FD.png

部分解释: 代码部分借鉴了Android源码.

069605CE-BF37-4BDA-B054-0514E4090EA4.png

  1. 先deep copy出一份新的event.
  2. 设置event的offsetLocation.
  3. 给event应用subview的inverse matrix.
  4. 还有很多类似对应的属性(后边继续学习, 本次没有用到)

然后event就被成功的转换到旋转后的view的坐标系中(斜着的), 原点是左上角.

遇到的问题:

  • ev.offsetLocation: 因为我是在activity中override onTouchEvent, 所以在计算deltaX和deltaY的时候先减去了relativeLayout距离window的x和y, 再减去了demoTextView的left和top. 1.为什么不直接减去demoTextView距离window的x和y? 因为我发现, demoTextView被旋转之后, 它的left/top/right/bottom并没有改变, 从现象的截图里我们也可以看出. 而demoTextView.getLocationInWindow获取的point却是旋转之后的值, 从log中可以看出. 2.为什么不减去relativeLayout的left和top, 而是减去距离window的x和y? 因为relativeLayout只是activity的contentView, 外层还有多少ViewGroup是未知, 所以直接减去距离window的x和y.
  • ev.transform: 源码里面的写法是transformedEvent.transform(child.getInverseMatrix());, 而我自己去调用demoTextView.getInverseMatrix()的时候, 一直报错, 而且API文档中并没有这个方法, 所以就直接去Matrix的类里面去搜关键词inverse, 发现有这样的方法可以使用.

Charles+Surge解决抓包和翻墙的冲突问题

| Comments

平常做iOS开发的时候经常使用ShadowsocksX来翻墙查资料, 使用Charles来抓包debug. 但是两个软件不能同时开, 一直想不到什么好的解决办法.

Surge特点是: 支持翻墙, 但是抓包的request和response不够详细. Mac版的Surge还要自己配置Web Proxy(HTTP)Secure Web Proxy(HTTPS).

然后我就查了一下Charles的菜单, 发现了External Proxy Setting, 然后发现刚好支持Web Proxy(HTTP)Secure Web Proxy(HTTPS), 配好调试, 果然成功. 然后手机上再设置成CharlesWeb Proxy(HTTP), 也是可以抓包和翻墙的.

Charles特点是: 抓包的request和response很详细, 但是External Proxy Setting支持的protocol比较少.

CharlesSurge结合起来就很完美的解决了我这个问题.

再简化一步, 就直接用手机上的Surge生成一个Web Proxy(HTTP)的config, 这样就不用每次去系统设置里手动设置了(每次敲好麻烦), 这样每次进入公司, 电脑上开着Charles和Mac版的Surge, 手机上起着Surge, 两个设备都可以被抓包和翻墙, 妈妈再也不用担心我在ShadowsocksXCharles这两个软件之间来回切换了.

附几张比较重要的图: CharlesExternal Proxy Setting 51F279D2-6924-4BB8-A230-48C693F2CE96.png

手机上的Surge的Web Proxy(HTTP)的config IMG_1261.png

Learn Git

| Comments

学习git之后的总结(心得)

之前骊住项目管理是用svn,后来服务器中项目丢失,导致svn无法再进行管理项目,又通过github发现git的强大。于是用了2天时间自学git基础,之后断断续续用了一周时间去实践搭建git服务器。


git心得(注意事项)

1.添加删除文件:一个团队中,若某个队员要添加或删除一些文件,操作如下

NOTE:

  • 在添加之前,在分支中commit所做的修改,然后切换到master中pull下最新的项目(project.pbxproj有改变的话会导致冲突,可把本地项目中的这个文件先拉到桌面,然后再poll)
  • 开始添加或删除文件
  • push到服务器(包括工程文件project.pbxproj和添加或删除的文件)
    然后其他队员也要进行添加或删除操作时,进行同样的步骤

2.尽量多建分支(branch)
3.把一次功能修改的文件分多次提交(主要是为了更加详细,清楚都进行了哪些操作) 这个是在代码中注释的另一种体现

2014-05-15 更新

xcode工程文件冲突解决办法之一:对项目中的文件进行排序。

1.添加排序脚本sort-Xcode-project-file(排序命令:perl -w sort-Xcode-project-file)

2.添加脚本到build phases(可选)

后记

git 标准gitignore文件(for iOS project) git 常见冲突 解决方案
git 参考教程 :
Git教程 - 廖雪峰的官方网站
Git - 架设服务器