日期: 2021 年 5 月 24 日

iOS提交后申请加急审核(备份)

之前只是听过加急审核这回事,一直也没有用到过。昨天用了下果然很给力。昨天晚上将近8点(北京时间)提交应用,今天早上上班看了下已经通过了审核,而且可以在AppStore里下载到。

话说加急的原因比较悲催,年前一直在做老版本的更新(一直在改别人代码的bug),临过年一周多的时候说不改了,做新版本的。心里终于舒服些,开始自己写新版本。2月7号上班到2月18号一直在做新版本。就在经理天天催,老板经常问进度的时候,实施那边说xxx那边20号要验收,验收不过要打官司了……

然后开始悲催的继续找到以前别人的代码改bug,十几个,虽说难度不是很大,但是很不爽~

18号加班改完,19号给测试人员测试。然后我继续开始新版本。结果测试那一天了没消息,LZ很2的去找到测试去问测试完了吗?然后测试MM笑着和我说,呀,忘了和你说了,没问题了。 括弧当时时间为下午6点左右,LZ当天正常下班时间是6点十几分。然后开始回来今天提交。话说提交速度那叫一个慢!没办法,启用米国*,速度快了很多,看着进度条一点一点往前走,慢慢走,慢慢走……n久后走到头。然后上面文字提示说已经提交完毕,但是进度条又回到了初始位置是怎么回事 ?!愤怒了!去iTunes connect里刷新状态提示等待提交 = =!

好吧,重新提交 –> 然后卡那不动了 ~~ 这米国*也不好用啊还是库克那服务器的原因啊。。。。

唉,取消提交然后用Application Loader提交,打包、选择、上传,速度很慢但是一切还算是顺利。在忽上忽下、“动荡不定”的网速中终于提交成功。iTunes connect里刷新状态为等待审核。OK,终于提交了。

竟然扯了这么多~

下面进入正题。提交完成后进入加急审核页面。

链接:https://developer.apple.com/appstore/contact/appreviewteam/index.html

在i would like to里选择加急审核

%title插图%num%title插图%num

然后填写相关信息。

%title插图%num%title插图%num

App Information里填写应用名称,ID填写9位数字ID 现在(2015-12-8)看了下下ID已经是10位了。感谢网友@qq_33265981 提醒

继续

%title插图%num%title插图%num

选择原因,然后填写理由。

这里要注意的是理由一般是用户安全问题或者崩溃问题成功率会高一些(LZ写的是崩溃,不要说我是赶时间,苹果会不同意,以前版本确实有崩溃问题,只不过不多~ 这里我不多说了,你们懂的)

还有一点要注意的是,如果是崩溃问题,你*好写上操作步骤,让审核员去重现这个问题。

PS. 如果遇到非常时期或者人品不好遇到心情不好的审核员,请自行另行处理~

就到这里了,继续搬砖去了~

信息共享,转载免费,但请注明出处

http://blog.csdn.net/showhilllee

2014.5.10补充:

之前听别人说一年只有两次加急的机会。一直没有尝试过,所以也没法证明是否是真的。

昨天晚上(周五晚)由于销售要在周一的时候开始做大量推广,所以今年的第三次加急审核又开始了……

今天早上起床后还在担心应该是没有通过,但是AppStore查看了下,已经通过了审核。

说明之前一年只有两次加急审核的机会是不正确的。至于一年到底有多少次加急审核的机会,官方没有看到相关的说明,也不能确定次数是多少。

如果有看到相关文档或说明的童鞋,希望能和大家分享下~

2014.5.14补充:

第四次加急,从提交到通过大概过了两天的时间。

2015.6.2补充:

加急审核说明是可以写中文的。这里感谢 @第七只蚂蚁 提供宝贵的信息。

iOS实际网络检测框架RealReachability

背景

网络连接状态检测对于我们的iOS app开发来说是一个非常通用的需求。为了更好的用户体验,我们会在无网络时展现本地或者缓存的内容,并对用户进行合适的提示。但事实上,当前iOS开发者们普遍使用的网络检测框架,实际上都无法帮助我们检测真正的网络连接状态;它们所能检测的只是本地连接状态。 本地连接状态和实际网络连接状态不一致的“伪连接”情况包括但不限于如下场景:

  1. 现在很流行的公用wifi,需要网页鉴权,鉴权之前无法上网,但本地连接已经建立;
  2. 存在了本地网络连接,但信号很差,实际无法连接到服务器;
  3. iOS连接的路由设备本身没有连接外网。

很多国内外的网友关于此问题早有提问和吐槽,比如:

  • 如何判断设备是否真正连上互联网?而不是只有网络连接
  • [Reachability reachabilityWithHostName:]完全没用!
  • 国外网友对Reachability库缺陷在github上的提问

为了着手解决此类问题,笔者希望就此打造一个通用、简单、可靠并且能检测实际网络连接状态的框架,从而帮助提升app在上述场景下的用户体验;这就是RealReachability的由来。

iOS下的网络检测方案

  1. 从苹果示例代码衍生而来的各类变种,是被开发者们使用*多的;即各类reachability命名相关的框架。其中*负盛名的当属tonymillion的Reachability。其基础代码来源于苹果,开发者们对其进行了扩展和语法上的支持(比如block回调等),使其更易于使用。
  2. AFNetworking框架中也提供了AFNetworkReachabilityManager,很多使用AFNetworking的小伙伴们会顺带使用该组件来进行网络检测。其代码基于苹果的SCNetworkReachability API,封装结构清晰,易于使用,且包含在AFNetworking中,也受到大家的喜爱。
  3. 一些app本身包含了例如xmpp之类的功能,此类实现通常会向连接的服务器定时发送心跳包,主要用于长连接的保活和断线处理;如果客户端没有收到server端回传的包,那么我们可以认为实际网络连接已经失效。sip协议也有类似的保活实现,定时间隔都是可配置的。基于server端连接稳定的前提,我们也可以利用这类心跳保活的机制间接达到实际网络状态检测的效果。
  4. 各种网络请求的超时。在上述伪连接的场景下,用户触发的http请求的通常结果就是超时,该时间一般不短于30秒。理论上说我们可以根据网络请求的结果来标识实际的网络状态,但这样做带来了实现上的耦合性(网络检测本应该是独立的功能模块),同时超时时间的等待也不利于打造一个很好的用户体验。

RealReachability简单介绍

RealReachability是笔者1个月之前发布到github的开源库,项目地址如下:https://github.com/dustturtle/RealReachability。
短短1个月时间收获了几百个star,*近还上了github的oc板块趋势排行榜,开发者们对此框架的热情完全出乎了笔者的意料;这也正说明此框架抓住了开发者们的痛点。

此框架开发的初衷来源于项目实际需求,离线模式对网络连接状态的要求比较苛刻,且实际场景经常会遇到“伪连接”的情况,项目中所使用的Reachability面对此场景力不从心。多方研究后引入了ping能力(此方案流量开销*小,也*简单),实现了简单的实际网络连接监测;后面将ping的能力和本地网络检测有机的结合,并且在实时性和开销上达成了一个平衡,不断提炼和优化,于是有了这个框架。

可以告诉大家的是,这个框架在appstore上架应用中已经经受了考验,且经过了长时间的测试,可以放心使用;目前也已经有很多开发者朋友们在其app中使用了我们的框架(让笔者略感自豪的是,他们来自世界各地!)。

RealReachability的实现原理

RealReachability的架构

realReachability架构概要图

RealReachability主要包含3大模块:connection、ping、FSM;

其中Ping模块通过对同样是苹果提供的ping样例代码进行了封装,connection模块实现了基于SCNetworkReachability的本地状态检测,FSM模块是有限状态机。

id=”iframeu2513605_1″ src=”http://pos.baidu.com/acom?rdid=2513605&dc=2&di=u2513605&dri=1&dis=0&dai=2&ps=1666×440&dcb=BAIDU_UNION_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1456712385390&ti=RealReachability%20%7C%20%E6%A0%87%E5%93%A5%E7%9A%84%E6%8A%80%E6%9C%AF%E5%8D%9A%E5%AE%A2&ari=1&dbv=2&drs=1&pcs=1905×930&pss=1905×1714&cfv=0&cpl=5&chi=1&cce=true&cec=UTF-8&tlm=1456712386&ltu=http%3A%2F%2Fwww.henishuo.com%2Fios-realreachability%2F&ecd=1&psr=1920×1080&par=1920×1002&pis=-1x-1&ccd=24&cja=false&cmi=7&col=zh-CN&cdo=-1&tcn=1456712386&qn=b80f64a3b1373c30&tt=1456712385364.660.691.692″ width=”760″ height=”90″ align=”center,center” vspace=”0″ hspace=”0″ marginwidth=”0″ marginheight=”0″ scrolling=”no” frameborder=”0″ allowtransparency=”true” style=”-webkit-tap-highlight-color: rgba(255, 0, 0, 0); border-width: 0px; border-style: initial; font-family: inherit; font-style: inherit; font-weight: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: bottom; max-width: 100%;”>

通过FSM的状态管理控制connection模块和Ping模块协同工作,剔除了重复的状态变化,并通过可配置的定时策略等业务逻辑优化,权衡了实时性和开销,*终得到了我们的实现。
PS:其中connection模块和ping模块也可独立使用,分别提供本地网络检测和ping的能力。

感兴趣的读者也可以尝试(调用方式请参考RealReachability开源代码)。

RealReachability的应用范围和场景

我们致力于打造一个打造一个通用、简单、可靠并且能检测实际网络连接状态的框架,从而帮助app开发者们在各种伪连接的场景下,也能够检测出网络真实的可达性,从而优化app的体验;现代架构的移动app大部分是胖客户端,即使离开了网络,也能提供很大一部分功能。RealReachability的能力也限于此。比如网速检测、个人热点信息检查等功能,不是此框架的设计目标,也超出了其能力范围。

RealReachability的开销

localconnection是基于本地的检测,因此其带来的开销可以忽略;ping模块的单次ICMP探测包大小仅为64字节(包含包头部),探测的频率默认为2分钟1次,对于客户端来说1小时的应用前台运行仅额外消耗2kb的流量,完全忽略不计;对于server端来说,如此低频率、低流量的分布式ICMP包,也不会带来压力:对比正常动辄几十kb甚至更多大小的请求,可以说是九牛一毛。

RealReachability基础使用

RealReachability集成和依赖

  • *简便的集成方法当属pod: pod ‘RealReachability’;当前的pod稳定版本版本号为1.1.1。
  • 手动集成:将RealReachability文件夹加入到工程即可。
  • 依赖:Xcode5.0+,支持ARC, iOS6+.项目需要引入SystemConfiguration.framework.

使用介绍

其接口的设计和调用方法和Reachability非常相似,大家可以无缝上手,非常方便。 开启网络监听:

关闭网络监听:

回调代码示例:

查询当前实际网络连接状态:

RealReachability进阶使用(可选)

手动触发实时网络状态查询,可以在block回调中处理自己的业务(特别是网络请求操作),此接口的block会被异步调用。
代码示例:

手动设置目标host地址(ping探测的目标地址):

注意:需要确保该host地址可以被ping探测到。此操作为可选,默认的目标host地址为www.baidu.com。

手动设置自动探测间隔(autoCheckInterval,单位为分钟):

注意:此处根据应用的实时性要求来设置自动探测的间隔,实时性要求较高时可适当调低此参数,但不建议设置为1.0以下。

iOS-性能优化深入探究

iOS-性能优化深入探究

上图是几种时间复杂度的关系,性能优化一定程度上是为了降低程序执行效率减低时间复杂度。 如下是几种时间复杂度的实例:

O(1)
  1. return array[index] == value;
  2. 复制代码
O(n)
  1. for (int i = 0, i < n, i++) {
  2. if (array[i] == value)
  3. return YES;
  4. }
  5. 复制代码
O(n2)
  1. /// 找数组中重复的值
  2. for (int i = 0, i < n, i++) {
  3. for (int j = 0, j < n, j++) {
  4. if (i != j && array[i] == array[j]) {
  5. return YES;
  6. }
  7. }
  8. }
  9. 复制代码

1. OC 中几种常见集合对象接口方法的时间复杂度

NSArray / NSMutableArray

  • containsObject; indexOfObject; removeObject 均会遍历元素查看是否匹配,复杂度等于或小于 O(n)
  • objectAtIndex;firstObject;lastObject; addObject; removeLastObject 这些只针对栈顶,栈底的操作时间复杂度都是 O(1)
  • indexOfObject:inSortedRange:options:usingComparator: 使用的是二分查找,时间复杂度是O(log n)

NSSet / NSMutableSet / NSCountedSet

集合类型是无序并且没有重复元素的。这样可以使用hash table 进行快速的操作。比如,addObject; removeObject; containsObject 都是按照 O(1) 来的。需要注意的是将数组转成Set 时,会将重复元素合并为一个,并且失去排序。

NSDictionary / NSMutableDictionary

和 Set 一样都可以使用 hash table ,多了键值对应。添加和删除元素都是 O(1)。

containsObject 方法在数组和Set里的不同的实现

containsObject 在数组中的实现
  1. ///GUNSTEP NSArray indexOfObject: 方法的实现
  2. – (BOOL)containsObject:(id)anObject {
  3. return [self indexOfObject:anObject] != NSNotFound;
  4. }
  5. – (NSUInteger) indexOfObject: (id)anObject
  6. {
  7. unsigned c = [self count];
  8. if (c > 0 && anObject != nil)
  9. {
  10. unsigned i;
  11. IMP get = [self methodForSelector: oaiSel];
  12. BOOL (*eq)(id, SEL, id)
  13. = (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];
  14. for (i = 0; i < c; i++)
  15. if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
  16. return i;
  17. }
  18. return NSNotFound;
  19. }
  20. 复制代码
containsObject 在 Set 里的实现:
  1. – (BOOL) containsObject: (id)anObject
  2. {
  3. return (([self member: anObject]) ? YES : NO);
  4. }
  5. //在 GSSet,m 里有对 member 的实现
  6. – (id) member: (id)anObject
  7. {
  8. if (anObject != nil)
  9. {
  10. GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
  11. if (node != 0)
  12. {
  13. return node->key.obj;
  14. }
  15. }
  16. return nil;
  17. }
  18. 复制代码
在数组中会遍历所有元素查找到结果后返回,在Set中查找元素是通过键值的方式从map映射表中取出,因为S儿童里的元素是唯一的,所以可以hash元素对象作为key达到快速查找的目的。

2. 使用GCD进行性能优化

可以通过GCD提供的方法将一些耗时操作放到非主线程进行,使得App 能够运行的更加流畅,响应更快,但是使用GCD 时需要注意避免可能引起的线程爆炸和死锁的情况。在非主线程处理任务也不是万能的,如果一个处理需要消耗大量内存或者大量CPU操作,GCD也不合适,需要将大任务拆分成不同的阶段任务分时间进行处理。

避免线程爆炸的方法:

  • 使用串行队列
  • 控制 NSOperationQueue 的并发数 – NSOperationQueue.maxConcurrentOperationCount

举个会造成线程爆炸和死锁的例子:

  1. for (int i = 0, i < 999; i++) {
  2. dispatch_async(q,^{…});
  3. }
  4. dispatch_barrier_sync(q,^{…});
  5. 复制代码

如何避免上述的的线程爆炸和死锁呢? 首先使用 dispatch_apply

  1. dispatch_apply(999,q,^(size_t i){…});
  2. 复制代码

或者使用 dispatch_semaphore

  1. #define CONCURRENT_TASKS 4
  2. dispatch_queue_t q = dispatch_queue_create(“com.qiuxuewei.gcd”, nil);
  3. dispatch_semaphore_t sema = dispatch_semaphore_create(CONCURRENT_TASKS);
  4. for (int i = 0; i < 999; i++) {
  5. dispatch_async(q, ^{
  6. dispatch_semaphore_signal(sema);
  7. });
  8. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  9. }
  10. 复制代码

3. I/O 性能优化

I/O 操作是性能消耗大户,任何的I/O操作都会使低功耗状态被打破。所以减少 I/O 操作次数是性能优化关键。如下是优化的一些方法:

  • 将零碎的内容作为一个整体进行写入
  • 使用合适的 I/O 操作 API
  • 使用合适的线程
  • 使用 NSCache 做缓存减少 I/O 次数

NSCache

为何使用 NSCache 而不适应 NSMutableDictionary 呢?相交字典 NSCache 有以下优点:

  • 自动清理系统所占内存(在接收到内存警告⚠️时)
  • NSCache 是线程安全的
  • - (void)cache:(NSCache *)cache willEvictObject:(id)obj; 缓存对象在即将被清理时回调。
  • evictsObjectWithDiscardedContent 可以控制是否可被清理。

SDWebImage 在设置图片时就使用 NSCache 进行了性能优化:

  1. – (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
  2. return [self.memCache objectForKey:key];
  3. }
  4. – (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
  5. // 检查 NSCache 里是否有
  6. UIImage *image = [self imageFromMemoryCacheForKey:key];
  7. if (image) {
  8. return image;
  9. }
  10. // 从磁盘里读
  11. UIImage *diskImage = [self diskImageForKey:key];
  12. if (diskImage && self.shouldCacheImagesInMemory) {
  13. NSUInteger cost = SDCacheCostForImage(diskImage);
  14. [self.memCache setObject:diskImage forKey:key cost:cost];
  15. }
  16. return diskImage;
  17. }
  18. 复制代码

利用 NSCache 自动释放内存的特点将图片放到 NSCache 里,这样在内存警告时会自动清理掉不常用的图片,在读取 Cache 里内容时,如果没有被清理直接返回图片数据,清理了会执行 I/O 从磁盘中读取图片,通过这种方式减少磁盘操作,空间也会更加有效的控制释放。

4. 控制 App 的唤醒次数

通知,Voip, 定位,蓝牙 等都会使设备从 Standby 状态唤起。唤起这个过程会有比较大的消耗。应该避免频繁发生。 以 定位 API 举例:

连续的位置更新

[locationManager startUpdatingLocation] 这个方法会使设备一直处于活跃状态。

延时有效定位

[locationManager allowDeferredLocationUpdatesUntilTraveled:<#(CLLocationDistance)#> timeout:<#(NSTimeInterval)#>] 高效节能的定位方式,数据会缓存在位置硬件上。适合跑步应用。

重大位置变化

[locationManager startMonitoringSignificantLocationChanges] 会更节能,对于那些只有在位置有很大变化的时候才需要回调的应用需要采用这种方式,比如天气应用。

区域监测

[locationManager startMonitoringForRegion:<#(nonnull CLRegion *)#>] 也是一种节能的定位方式,比如在博物馆内按照不同区域监测展示不同信息之类的应用。

频繁定位
  1. // start monitoring location
  2. [locationManager startUpdatingLocation]
  3. // Stop monitoring when no longer needed
  4. [locationManager stopUpdatingLocation]
  5. 复制代码

不要轻易使用 startUpdatingLocation() 除非万不得已,尽快的使用 stopUpdatingLocation() 来结束定位还用户一个节能设备。

5. 预防性能问题

坚持几个编码原则:

  • 优化计算的复杂度从而减少CPU的使用
  • 在应用响应交互的时候停止没有必要的任务处理
  • 设置合适的 Qos
  • 将定时器任务合并,让CPU更多时候处于 idle 状态

6. 性能优化技巧篇

1. 复用机制

在 UICollectionView 和 UITableView 会使用到 代码复用的机制,在所展示的item数量超过屏幕所容纳的范围时,只创建少量的条目(通常是屏幕*大容纳量 + 1),通过复用来展示所有数据。这种机制不会为每一条数据都创建 Cell .增强效率和交互流畅性。 在iOS6以后,不仅可以复用cell,也可以复用每个section 的 header 和 footer。 在复用UITableView 会用到的 API:

  1. // 复用 Cell:
  2. – [UITableView dequeueReusableCellWithIdentifier:];
  3. – [UITableView registerNib:forCellReuseIdentifier:];
  4. – [UITableView registerClass:forCellReuseIdentifier:];
  5. – [UITableView dequeueReusableCellWithIdentifier:forIndexPath:];
  6. // 复用 Section 的 Header/Footer:
  7. – [UITableView registerNib:forHeaderFooterViewReuseIdentifier:];
  8. – [UITableView registerClass:forHeaderFooterViewReuseIdentifier:];
  9. – [UITableView dequeueReusableHeaderFooterViewWithIdentifier:];
  10. 复制代码

在使用代码复用需要注意在设置Cell 属性是,条件判断需要覆盖所有可能,避免因为复用导致数据错误的问题。例如在 cellForRowAtIndexPath: 方法内部:

  1. if (indexPath %2 == 0) {
  2. cell.backgroundColor = [UIColor redColor];
  3. }else{
  4. cell.backgroundColor = [UIColor clearColor];
  5. }
  6. 复制代码

2. 设置View为不透明

UIView 又一个 opaque 属性, 在不需要透明效果的时候,应该尽量设置它为 YES, 可以提高绘图效率。 在静态视图作用可能不明显,但在 UITableVeiw 或 UICollectionView 这种滚动 的 Scroll View 或是一个复杂动画中,透明效果对程序性能有较大的影响!

3. 避免使用臃肿的 Xib 文件

当加载一个 Xib 时,它所有的内容都会被加载,如歌这个 Xib 中有的View 你不会马上用到,加载就是浪费资源。而加载 StoryBoard 时,并不会把所有的ViewController 都加载,只会按需加载。

4. 不要阻塞主线程

UIKit 会把它所有的工作放在主线程执行,比如:绘制界面,管理手势,响应输入等。当把所有代码逻辑都放在主线程时,有可能因为耗时太长而卡住主线程造成程序无法响应,流畅性差等问题。所以一些 I/O 操作,网络数据解析都需要异步在非主线程处理。

5. 使用尺寸匹配的UIImage

当从 App bundle 中加载图片到 UIImageView 时,*好确保图片的尺寸和 UIImageView 相对应。否则会使UIImageView 对图片进行拉伸,这样会影响性能。如果图片时从网络加载,需要手动进行 scale。在UIImageView 中使用resize 后的图片

6. 选择合适的容器

在使用 NSArray / NSDictionary / NSSet 时,了解他们的特点便于在合适的时机选择他们。

  • Array:数组。有序的,通过 index 查找很快,通过 value 查找很慢,插入和删除较慢。
  • Dictionary:字典。存储键值对,通过键查找很快。
  • Set:集合。无序的,通过 value 查找很快,插入和删除较快。

7. 启用 GZIP 数据压缩

在网络请求的数据量较大时,可以将数据进行压缩再进行传输。可以降低延迟,缩短网络交互时间。

8. 懒加载视图 / 视图隐藏

展现视图的两种形式一种是懒加载,当用到的时候去创建并展现给用户,另外一种提前分配内存创建出视图,不用的时候将其隐藏,等用到的时候将其透明度变为1,两种方案各有利弊。懒加载更合理的使用内存,视图隐藏让视图的展现更迅速。在选择时需要权衡两者利弊做出*优选择。

9. 缓存

开发需要秉承一个原则,对于一些更新频率低,访问频率高的内容进行缓存,例如:

  • 服务器响应数据
  • 图片
  • 计算值 (UITableView 的 row height)

10. 处理 Memory Warning

处理 Memory Warning 的几种方式:

  • 在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
  • 在 UIViewController 中重载 didReceiveMemoryWarning 方法。
  • 监听 UIApplicationDidReceiveMemoryWarningNotification 通知。

当通过这些方式监听到内存警告时,你需要马上释放掉不需要的内存从而避免程序被系统杀掉。

比如,在一个 UIViewController 中,你可以清除那些当前不显示的 View,同时可以清除这些 View 对应的内存中的数据,而有图片缓存机制的话也可以在这时候释放掉不显示在屏幕上的图片资源。

但是需要注意的是,你这时清除的数据,必须是可以在重新获取到的,否则可能因为必要数据为空,造成程序出错。在开发的时候,可以使用 iOS Simulator 的 Simulate memory warning 的功能来测试你处理内存警告的代码。

11. 复用高开销对象

高开销对象,顾名思义就是初始化很耗性能的对象。比如:NSDateFormatter , NSCalendar .为了避免频繁创建,我们可以使用一个全局单例强引用着这个对象,保证整个App 的生命周期只被初始化一次。

  1. // no property is required anymore. The following code goes inside the implementation (.m)
  2. – (NSDateFormatter *)dateFormatter {
  3. static NSDateFormatter *dateFormatter;
  4. static dispatch_once_t onceToken;
  5. dispatch_once(&onceToken, ^{
  6. dateFormatter = [[NSDateFormatter alloc] init];
  7. [dateFormatter setDateFormat:@”yyyy-MM-dd a HH:mm:ss EEEE”];
  8. });
  9. return dateFormatter;
  10. }
  11. 复制代码

设置 NSDateFormatter 的 date format 跟创建一个新的 NSDateFormatter 对象一样慢,因此当你的程序中要用到多种格式的 date format,而每种又会用到多次的时候,你可以尝试为每种 date format 创建一个可复用的 NSDateFormatter 对象来提供程序的性能。

12. 选择正确的网络返回数据格式

通常用到的有两种: JSON 和 XML。 JSON 优点:

  • 能够更快的被解析
  • 在承载相同数据时,体积比XML更小,传输的数据量更小。

缺点:

  • 需要整个JSON数据全部加载完成后才能开始解析

而XML的优缺点恰好相反。解析数据不需要全部读取完才解析,可以变加载边解析,这样在处理大数据集时可以有效提高性能。 选择哪种格式取决于应用场景。

13. 合理设置背景图片

为一个View 设置背景图,我们想到的方案有两种

  • 为视图加一个 UIImageView 设置 UIImage 作为背景
  • 通过 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>] 将一张图转化为 UIColor, 直接为 View 设置 backgroundColor。

两种方案各有优缺点:若使用一个全尺寸图片作为背景图使用 UIImageView 会节省内存。 当你计划采用一个小块的模板样式图片,就像贴瓷砖那样来重复填充整个背景时,你应该用 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>] 这个方法,因为这时它能够绘制的更快,并且不会用到太多的内存。

14. 减少离屏渲染

离屏渲染:GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。 离屏渲染需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动作。

设置如下属性均会造成离屏渲染:

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等
  • 渐变

例如给一个View设置阴影,通常我们会使用这种方式:

  1. imageView.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
  2. imageView.layer.shadowRadius = 5.0f;
  3. imageView.layer.shadowOpacity = 0.6;
  4. 复制代码

这种方式会触发离屏渲染,造成不必要的内存开销,我们完全可以使用如下方式代替:

  1. imageView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:CGRectMake(imageView.bounds.origin.x+5, imageView.bounds.origin.y+5, imageView.bounds.size.width, imageView.bounds.size.height)] CGPath];
  2. imageView.layer.shadowOpacity = 0.6;
  3. 复制代码

不会造成离屏渲染。

15. 光栅化

CALayer 有一个属性是 shouldRasterize 通过设置这个属性为 YES 可以将图层绘制到一个屏幕外的图像,然后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,如果很很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧来更加高效。但是光栅化原始图像需要时间,而且会消耗额外的内存。

  1. cell.layer.shouldRasterize = YES;
  2. cell.layer.rasterizationScale = [[UIScreen mainScreen] scale];
  3. 复制代码

使用光栅化的一个前提是视图不会频繁变化,若一个频繁变化的视图,例如 排版多变,高度不同的 Cell, 光栅化的意义就不大了,反而造成必要的内存损耗。

16. 优化 UITableView

  • 通过正确的设置 reuseIdentifier 来重用 Cell。
  • 尽量减少不必要的透明 View。
  • 尽量避免渐变效果、图片拉伸和离屏渲染。
  • 当不同的行的高度不一样时,尽量缓存它们的高度值。
  • 如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
  • 使用 shadowPath 来设置阴影效果。
  • 尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
  • 尽量优化 – [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
  • 选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
  • 对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。

17.选择合适数据存储方式

iOS 中数据存储方案有以下几种:

  • NSUserDefaults。只适合用来存小数据。
  • XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
  • 使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
  • 使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
  • 使用 CoreData。 Apple 提供的对于SQLite 的封装,性能不如使用原生 SQLite, 不推荐使用。

18. 减少应用启动时间

在启动时的一些网络配置,数据库配置,数据解析的工作放在异步线程进行。

19. 使用 Autorelease Pool

当需要在代码中创建许多临时对象时,你会发现内存消耗激增直到这些对象被释放,一个问题是这些内存只会到 UIKit 销毁了它对应的 Autorelease Pool 后才会被释放,这就意味着这些内存不必要地会空占一些时间。这时候就是我们显式的使用 Autorelease Pool 的时候了,一个示例如下:

  1. //一个很大数组
  2. NSArray *urls = <# An array of file URLs #>;
  3. for (NSURL *url in urls) {
  4. @autoreleasepool {
  5. NSError *error;
  6. NSString *fileContents = [NSString stringWithContentsOfURL:url
  7. encoding:NSUTF8StringEncoding error:&error];
  8. /* Process the string, creating and autoreleasing more objects. */
  9. }
  10. }
  11. 复制代码

添加 Autorelease Pool 会在每一次循环中释放掉临时对象,提高性能。

20. 合理选择 imageNamed 和 imageWithContentsOfFile

  • imageNamed 会对图片进行缓存,适合多次使用某张图片
  • imageWithContentsOfFile 从bundle中加载图片文件,不会进行缓存,适用于加载一张较大的并且只使用一次的图片,例如引导图等

今年的 WWDC 2018 Apple 向我们推荐了一种性能比较高的大图加载方案:

  1. func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
  2. let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
  3. // 其他场景可以用createwithdata (data并未decode,所占内存没那么大),
  4. let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
  5. let maxDimension = max(pointSize.width, pointSize.height) * scale
  6. let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
  7. kCGImageSourceShouldCacheImmediately : true ,
  8. kCGImageSourceCreateThumbnailWithTransform : true,
  9. kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
  10. let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
  11. return UIImage(cgImage: downsampleImage)
  12. }
  13. 作者:知识小集
  14. 链接:https://juejin.im/post/5b396fece51d4558a3055131
  15. 来源:掘金
  16. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  17. 复制代码

详细关于两者的分析可参照笔者的另外一篇博客:iOS-UIImage imageWithContentsOfFile 和 imageName 对比

21. 合理进行线程分配

GCD 很轻易的可以开辟一个异步线程(不会100%开辟新线程),若不加以控制,会导致开辟的子线程越来越多浪费内存。并且在多线程情况下因为网络时序会造成数据处理错乱,所以可以:

  • UI 操作和 DataSource 操作在主线程
  • DB 操作,日志记录,网络回调在各自固定线程
  • 不同业务,通过使用队列保持数据一致性。

22. 预处理和延时加载

预处理:初次展示需要消耗大量内存的数据需提前在后台线程处理完毕,需要时将处理好的数据进行展现 延时加载:提前加载下级界面的数据内容。举个栗子:类似抖音视频滑动,在播放当前视频的时候就提前将下个视频的数据加载好,等滑到下个视频时直接进行展示!

23. 在合适的时机使用 CALayer 替代 UIView

若视图无需和用户交互,类似绘制线条,单纯展示一张图片,可以将图片对象赋值给 layer 的 content 属性,以提高性能。 但是不能滥用,否则会造成代码难以维护的恶果。

以上。

2021年5月中旬流通领域重要生产资料市场价格变动情况

中国统计信息服务中心 卓创资讯   据对全国流通领域9大类50种重要生产资料市场价格的监测显示,2021年5月中旬与5月上旬相比,33种产品价格上涨,13种下降,4种持平。 2021年5月中旬流通领域重要生产资料市场价格变动情况  产品名称 单位 本期价格(元) 比上期 价格涨跌(元) 涨跌幅 (%) 一、黑色金属         螺纹钢(Φ16-25mm,HRB400E) 吨 5857.3 172.8 3.0 线材(Φ6.5mm,HPB300) 吨 6178.4 249.3 4.2 普通中板(20mm,Q235) 吨 6369.5 248.3 4.1 热轧普通薄板(3mm,Q235) 吨 6457.5 164.4 2.6 无缝钢管(219*6,20#) 吨 6835.6 673.4 10.9 角钢(5#) 吨 6130.9 364.0 6.3 二、有色金属         电解铜(1#) 吨 74953.5 345.8 0.5 铝锭(A00) 吨 19702.1 46.0 0.2 铅锭(1#) 吨 15262.5 -147.5 -1.0 锌锭(0#) 吨 22421.3 118.0 0.5 三、化工产品         硫酸(98%) 吨 585.0 10.0 1.7 烧碱(液碱,32%) 吨 485.3 -5.0 -1.0 甲醇(优等品) 吨 2646.1 152.6 6.1 纯苯(石油苯,工业级) 吨 8131.6 264.9 3.4 苯乙烯(一级品) 吨 10682.2 317.9 3.1 聚乙烯(LLDPE,7042) 吨 8561.8 -62.0 -0.7 聚丙烯(T30S) 吨 9083.7 -79.1 -0.9 聚氯乙烯(SG5) 吨 9374.8 -6.9 -0.1 顺丁胶(BR9000) 吨 12136.3 48.0 0.4 涤纶长丝(FDY150D/96F) 吨 7465.6 -1.1 0.0 四、石油天然气         液化天然气(LNG) 吨 3729.5 400.4 12.0 液化石油气(LPG) 吨 4200.3 -64.7 -1.5 汽油(95#国VI) 吨 8046.5 66.6 0.8 汽油(92#国VI) 吨 7807.7 61.4 0.8 柴油(0#国VI) 吨 6262.8 129.1 2.1 石蜡(58#半) 吨 7266.7 33.4 0.5 五、煤炭         无烟煤(洗中块) 吨 1175.0 175.0 17.5 普通混煤(4500大卡) 吨 733.1 51.4 7.5 山西大混(5000大卡) 吨 821.9 20.2 2.5 山西优混(5500大卡) 吨 913.1 51.4 6.0 大同混煤(5800大卡) 吨 938.1 51.4 5.8 焦煤(主焦煤) 吨 1775.0 106.2 6.4 焦炭(二级冶金焦) 吨 2624.5 247.7 10.4 六、非金属建材         普通硅酸盐水泥(P.O 42.5袋装) 吨 487.9 0.0 0.0 普通硅酸盐水泥(P.O 42.5散装) 吨 450.2 1.3 0.3 浮法平板玻璃(4.8/5mm) 吨 2619.0 189.0 7.8 七、农产品(主要用于加工)         稻米(粳稻米) 吨 4014.5 -6.7 -0.2 小麦(国标三等) 吨 2528.6 -1.9 -0.1 玉米(黄玉米二等) 吨 2826.9 8.5 0.3 棉花(皮棉,白棉三级) 吨 16303.6 4.9 0.0 生猪(外三元) 千克 18.5 -1.6 -8.0 大豆(黄豆) 吨 5308.3 55.0 1.0 豆粕(粗蛋白含量≥43%) 吨 3598.1 -33.6 -0.9 花生(油料花生米) 吨 8779.2 -26.4 -0.3 八、农业生产资料         尿素(小颗料) 吨 2347.3 120.6 5.4 复合肥(硫酸钾复合肥,氮磷钾含量45%) 吨 2512.5 0.0 0.0 农药(草甘膦,95%原药) 吨 40000.0 2166.7 5.7 九、林产品         天然橡胶(标准胶SCRWF) 吨 13112.5 -804.8 -5.8 纸浆(漂白化学浆) 吨 5935.0 -20.0 -0.3 瓦楞纸(高强) 吨 3933.1 103.3 2.7 注:上期为2021年5月上旬。    附注   1.指标解释   流通领域重要生产资料市场价格,是指重要生产资料经营企业的批发和销售价格。与出厂价格不同,生产资料市场价格既包含出厂价格,也包含有经营企业的流通费用、利润和税费等。出厂价格与市场价格互相影响,存在时滞,两者的变动趋势在某一时间段内有可能会出现不完全一致的情况。   2.监测内容   流通领域重要生产资料市场价格监测内容包括9大类50种产品的价格。类别与产品规格说明详见附表。   3.监测范围   监测范围涵盖全国31个省(区、市)300多个交易市场的近2000家批发商、代理商、经销商等经营企业。   4.监测方法   价格监测方法包括信息员现场采价,电话、即时通讯工具和电子邮件询价等。   5.涨跌个数的统计   产品价格上涨、下降、持平个数按照涨跌幅(%)进行统计。   6.发布日期   每月4日、14日、24日发布上一旬数据,节假日顺延。 附表:流通领域重要生产资料市场价格监测产品规格说明表  序号 监测产品 规格型号 说明   一、黑色金属      1   螺纹钢 Φ16-25mm,HRB400E 屈服强度≥400MPa  2 线材 Φ6.5mm,HPB300 屈服强度≥300MPa  3 普通中板 20mm,Q235 屈服强度≥235MPa  4 热轧普通薄板 3mm,Q235 屈服强度≥235MPa  5 无缝钢管 219*6,20# 20#钢材,屈服强度≥245MPa  6 角钢 5# 屈服强度≥235MPa   二、有色金属      7 电解铜 1# 铜与银质量分数≥99.95%  8 铝锭 A00 铝质量分数≥99.7%  9 铅锭 1# 铅质量分数≥99.994% 10 锌锭 0# 锌质量分数≥99.995%   三、化工产品     11  硫酸 98% H2SO4质量分数≥98% 12 烧碱(液碱) 32% NaOH质量分数≥32%的离子膜碱 13 甲醇 优等品 水质量含量≤0.10% 14 纯苯(石油苯) 工业级 苯纯度≥99.8% 15 苯乙烯 一级品 纯度≥99.5% 16 聚乙烯(LLDPE) 7042 熔指:2.0±0.5g/10min 17 聚丙烯 T30S 熔指:3.0±0.9g/10min 18 聚氯乙烯 SG5 K值:66-68 19 顺丁胶 BR9000 块状、乳白色,灰分≤0.20% 20 涤纶长丝 FDY150D/96F 150旦,AA级   四、石油天然气     21 液化天然气 LNG 甲烷含量≥75%,密度≥430kg/m3 22 液化石油气 LPG 饱和蒸汽压1380-1430kPa 23 汽油 95#国VI 国VI标准 24 汽油 92#国VI 国VI标准 25 柴油 0#国VI 国VI标准 26 石蜡 58#半 熔点不低于58℃   五、煤炭     27 无烟煤 洗中块 挥发分≤8% 28 普通混煤 4500大卡 山西粉煤与块煤的混合煤,热值4500大卡 29 山西大混 5000大卡 质量较好的混煤,热值5000大卡 30 山西优混 5500大卡 优质的混煤,热值5500大卡 31 大同混煤 5800大卡 大同产混煤,热值5800大卡 32 焦煤  主焦煤 含硫量<1% 33 焦炭 二级冶金焦 12.01%≤灰分≤13.50%   六、非金属建材     34 普通硅酸盐水泥 P.O 42.5袋装 抗压强度42.5MPa 35 普通硅酸盐水泥 P.O 42.5散装 抗压强度42.5MPa 36 浮法平板玻璃 4.8/5mm 厚度为4.8/5mm的无色透明玻璃   七、农产品(主要用于加工)     37 稻米 粳稻米 杂质≤0.25%,水分≤15.5% 38 小麦 国标三等 杂质≤1.0%,水分≤12.5% 39 玉米 黄玉米二等 杂质≤1.0%,水分≤14.0% 40 棉花(皮棉) 白棉三级 纤维长度≥28mm,白或乳白色 41 生猪 外三元 三种外国猪杂交的肉食猪 42 大豆 黄豆 杂质≤1.0%,水分≤13.0% 43 豆粕 粗蛋白含量≥43% 粗蛋白≥43%,水分≤13.0% 44 花生 油料花生米 杂质≤1.0%,水分≤9.0%   八、农业生产资料     45 尿素 小颗料 总氮≥46%,水分≤1.0% 46 复合肥 硫酸钾复合肥 氮磷钾含量45% 47 农药(草甘膦) 95%原药 草甘膦质量分数≥95%   九、林产品     48 天然橡胶 标准胶SCRWF 杂质含量≤0.05%,灰分≤0.5% 49 纸浆 漂白化学浆 亮度≥80%,黏度≥600cm³/g 50 瓦楞纸 高强 80-160g/m2  

Android裁剪图片为圆形图片

/**
* 转换图片成圆形
*
* @param bitmap
* 传入Bitmap对象
* @return
*/
public Bitmap toRoundBitmap(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float roundPx;
float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom;
if (width <= height) {
roundPx = width / 2;
left = 0;
top = 0;
right = width;
bottom = width;
height = width;
dst_left = 0;
dst_top = 0;
dst_right = width;
dst_bottom = width;
} else {
roundPx = height / 2;
float clip = (width – height) / 2;
left = clip;
right = width – clip;
top = 0;
bottom = height;
width = height;
dst_left = 0;
dst_top = 0;
dst_right = height;
dst_bottom = height;
}

Bitmap output = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(output);

final int color = 0xff424242;
final Paint paint = new Paint();
final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom);
final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom);
final RectF rectF = new RectF(dst);

paint.setAntiAlias(true);// 设置画笔无锯齿

canvas.drawARGB(0, 0, 0, 0); // 填充整个Canvas
paint.setColor(color);

// 以下有两种方法画圆,drawRounRect和drawCircle
// canvas.drawRoundRect(rectF, roundPx, roundPx, paint);// 画圆角矩形,*个参数为图形显示区域,第二个参数和第三个参数分别是水平圆角半径和垂直圆角半径。
canvas.drawCircle(roundPx, roundPx, roundPx, paint);

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));// 设置两张图片相交时的模式,参考http://trylovecatch.iteye.com/blog/1189452
canvas.drawBitmap(bitmap, src, dst, paint); //以Mode.SRC_IN模式合并bitmap和已经draw了的Circle

return output;
}

参考流程图 &  原理图:

%title插图%num

%title插图%num

Android 针对华为手机调用裁剪出现圆形裁剪框的处理

在网上找的教程大都是:

if(android.os.Build.MODEL.contains(“HUAWEI”))
{//华为特殊处理 不然会显示圆
intent.putExtra(“aspectX”, 9998);
intent.putExtra(“aspectY”, 9999);
}
else
{
intent.putExtra(“aspectX”, 1);
intent.putExtra(“aspectY”, 1);
}

但是经过测试有时候并不管用,可以尝试使用:

 

if (Build.MANUFACTURER.equals(“HUAWEI”)) {
intent.putExtra(“aspectX”, 9998);
intent.putExtra(“aspectY”, 9999);
} else {
intent.putExtra(“aspectX”, 1);
intent.putExtra(“aspectY”, 1);
}

两个surfaceView切换的demo

需求:视频通话界面,两个surfaceView一个显示本端的视图,另一个显示对端的视图,由于显示比例的问题总会存在一个覆盖另一个的问题,为保证用户体验,规定小的覆盖大的视图上面,且点击小的视图可切花为大图视图居中,达到两个视图切花的功能。简单写一个demo完成功能的测试需求,为了较少文章的篇幅,视图的内容用回执矩形代替(实际开发中显示的是本地照相采集的数据和对端经过opgl处理的数据)

一,简单的布局

<?xml version=”1.0″ encoding=”utf-8″?>
<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<RelativeLayout
android:id=”@+id/remote_rl”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”

>

<SurfaceView
android:id=”@+id/remote_view”
android:layout_width=”match_parent”
android:layout_height=”match_parent”

</RelativeLayout> android:layout_gravity=”center” />

</RelativeLayout>

<RelativeLayout
android:id=”@+id/local_rl”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”

>

<SurfaceView
android:id=”@+id/local_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</RelativeLayout>

具体的demo实现

public class MainActivity extends Activity implements View.OnClickListener {
public static final String TAG = “sssss”;
//远端的视图
private SurfaceView remote_sv;
// 本地的视图
private SurfaceView local_sv;
private SurfaceHolder remote_holder;
private SurfaceHolder local_holder;
private RelativeLayout remote_rl;
private RelativeLayout local_rl;

private int screenWidth;
private int screenHeight;

private int beforRemoteweith;
private int beforLocalweith;
private int beforRemoteheigth;
private int beforLocalheigth;
private int StateAB = 0;
private int StateBA = 1;
private int mSate;
private int defaultLocalHeight=200;
private int defaultLocalwidth=400;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels – 500;
remote_sv = (SurfaceView) findViewById(R.id.remote_view);
remote_rl = (RelativeLayout) findViewById(R.id.remote_rl);
local_rl = (RelativeLayout) findViewById(R.id.local_rl);
remote_sv.setOnClickListener(this);

LayoutParams params = new LayoutParams(screenWidth, screenHeight);
remote_sv.setLayoutParams(params);
remote_holder = remote_sv.getHolder();
// 对 surfaceView 进行操作
remote_holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = remote_holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.RED);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
remote_holder.unlockCanvasAndPost(c);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {/*
Log.d(TAG,”remote_holder surfaceChanged width”+ width+”height”+height);
Canvas c = remote_holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.RED);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
remote_holder.unlockCanvasAndPost(c);
*/}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
});// 自动运行surfaceCreated以及surfaceChanged

local_sv = (SurfaceView) findViewById(R.id.local_view);
local_sv.setOnClickListener(this);
local_sv.setOnClickListener(this);
// sv.setZOrderOnTop(false);
local_sv.setZOrderOnTop(true);
// 这两个方法差不多,设置了就会浮现到顶部,但是,后面的看不见,要像下面设置为透明
// local_sv.setZOrderOnTop(true);
// local_sv.setZOrderMediaOverlay(true);

local_holder = local_sv.getHolder();

remote_holder.setFormat(PixelFormat.TRANSPARENT);
local_holder.setFormat(PixelFormat.TRANSPARENT);
LayoutParams params1 = new LayoutParams(defaultLocalHeight, defaultLocalwidth);
local_sv.setLayoutParams(params1);
remote_holder = remote_sv.getHolder();
local_holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.YELLOW);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
holder.unlockCanvasAndPost(c);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {/*
Log.d(TAG,”local_holder surfaceChanged width”+ width+”height”+height);
Canvas c = holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.YELLOW);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width()-50,
holder.getSurfaceFrame().height()-50);
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
holder.unlockCanvasAndPost(c);

*/}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
});
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, defaultLocalwidth,
defaultLocalHeight, RelativeLayout.CENTER_IN_PARENT);
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.local_view:
Log.d(TAG, ” onClick local_view” + mSate);
if (mSate == StateAB) {
zoomlocalViewout(beforRemoteweith, beforRemoteheigth, local_sv,
remote_sv);
zoomRemoteViewint(beforLocalweith, beforLocalheigth);
mSate = StateBA;
}

break;
case R.id.remote_view:
Log.d(TAG, ” onClick emote_view” + mSate);
if (mSate == StateBA) {

zoomRemoteout(beforRemoteweith, beforRemoteheigth, local_sv,
remote_sv);
zoomlocalViewint(beforLocalweith, beforLocalheigth);

mSate = StateAB;
}

break;
default:
break;
}

}
//放大远端的视图
private void zoomRemoteout(int weith2, int heigth2, SurfaceView localView,
SurfaceView remoteView) {

beforLocalheigth = localView.getMeasuredHeight();
beforLocalweith = localView.getMeasuredWidth();
beforRemoteheigth = remoteView.getMeasuredHeight();
beforRemoteweith = remoteView.getMeasuredWidth();
Log.d(TAG, “zoomRemoteout beforLocalheigth” + beforLocalheigth
+ “beforLocalweith” + beforLocalweith + “beforRemoteheigth”
+ beforRemoteheigth + “beforRemoteweith” + beforLocalweith);
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, screenWidth,
beforLocalheigth, RelativeLayout.CENTER_IN_PARENT);

}
//具体的视图操作
private void zoomOpera(View sourcView, SurfaceView beforeview,
SurfaceView afterview, View detView, int beforLocalweith,
int beforLocalHeigth, int rule) {

LayoutParams params1 = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);

Log.w(TAG, “beforLocalheigth = ” + beforLocalheigth
+ “; beforLocalweith = ” + beforLocalweith);
params1.addRule(rule, RelativeLayout.TRUE);
afterview.setLayoutParams(params1);
afterview.setBackgroundResource(android.R.color.transparent);
params1 = new LayoutParams(beforLocalweith, beforLocalHeigth);
params1.addRule(rule, RelativeLayout.TRUE);
detView.setLayoutParams(params1);

}
//缩小远端的视图
private void zoomRemoteViewint(int weith2, int heigth2) {
RelativeLayout paretview = (RelativeLayout) local_rl.getParent();
paretview.removeView(remote_rl);
paretview.removeView(local_rl);
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, beforLocalweith,
beforLocalheigth, RelativeLayout.ALIGN_PARENT_TOP);
Log.d(TAG, “paretview” + paretview.getChildCount());
paretview.addView(local_rl);
paretview.addView(remote_rl);
remote_sv.setZOrderOnTop(true);

}
//放大本端的视图
private void zoomlocalViewout(int weith2, int heigth2,
SurfaceView localView, SurfaceView remoteView) {
beforLocalheigth = localView.getMeasuredHeight();
beforLocalweith = localView.getMeasuredWidth();
beforRemoteheigth = remoteView.getMeasuredHeight();
beforRemoteweith = remoteView.getMeasuredWidth();
Log.d(TAG, “zoomlocalViewout beforLocalheigth” + beforLocalheigth
+ “beforLocalweith” + beforLocalweith + “beforRemoteheigth”
+ beforRemoteheigth + “beforRemoteweith” + beforRemoteweith);
zoomOpera(remote_rl, remote_sv, local_sv, local_rl, beforRemoteweith,
beforRemoteheigth, RelativeLayout.CENTER_IN_PARENT);

}
//减小本端的视图
private void zoomlocalViewint(int weith2, int heigth2) {
RelativeLayout paretview = (RelativeLayout) local_rl.getParent();
paretview.removeView(remote_rl);
paretview.removeView(local_rl);
zoomOpera(remote_rl, remote_sv, local_sv, local_rl, beforRemoteweith,
beforRemoteheigth, RelativeLayout.ALIGN_PARENT_TOP);
paretview.addView(remote_rl);
paretview.addView(local_rl);
local_sv.setZOrderOnTop(true);

}
}

仿微信视频通话大小视图切换(SurfaceView实现)

前言

前一段时间做了一个即时通讯的项目,在项目中遇到很多坑,有时间一一做个总结,项目消息发送基于XMPP+Tigase,语言视频通话基于PJSIP+FreeSWITCH,项目UI仿微信。做到视频通话时,遇到本地视图与远程视图切换,网上搜了一篇相关的博客,根据大神思路写了这个Demo,其中用的是第三直播源可能有点不稳定,切换过程可能存在黑屏和无响应的情况,但是用的Pjsip中切换还是很流程的;

布局

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <FrameLayout
  3. xmlns:android=“http://schemas.android.com/apk/res/android”
  4. xmlns:tools=“http://schemas.android.com/tools”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”
  7. tools:context=“com.demo.surfaceviewdemo.MainActivity”>
  8. <RelativeLayout
  9. android:layout_width=“match_parent”
  10. android:layout_height=“match_parent”>
  11. <RelativeLayout
  12. android:id=“@+id/rl_remote”
  13. android:layout_width=“match_parent”
  14. android:layout_height=“wrap_content”>
  15. <SurfaceView
  16. android:id=“@+id/surfaceview_remote”
  17. android:layout_width=“match_parent”
  18. android:layout_height=“match_parent”/>
  19. </RelativeLayout>
  20. <RelativeLayout
  21. android:id=“@+id/rl_local”
  22. android:layout_width=“wrap_content”
  23. android:layout_height=“wrap_content”
  24. android:layout_alignParentRight=“true”>
  25. <SurfaceView
  26. android:id=“@+id/surfaceview_local”
  27. android:layout_width=“wrap_content”
  28. android:layout_height=“wrap_content”/>
  29. </RelativeLayout>
  30. </RelativeLayout>
  31. <!–通话时显示的–>
  32. <LinearLayout
  33. android:id=“@+id/ll_call_container”
  34. android:layout_width=“match_parent”
  35. android:layout_height=“wrap_content”
  36. android:layout_gravity=“bottom”
  37. android:layout_marginBottom=“25dp”
  38. android:gravity=“center_horizontal”
  39. android:orientation=“horizontal”>
  40. <TextView
  41. android:id=“@+id/tv_call_quiet”
  42. android:layout_width=“wrap_content”
  43. android:layout_height=“wrap_content”
  44. android:layout_weight=“1”
  45. android:drawablePadding=“10dp”
  46. android:drawableTop=“@mipmap/chat_video_change_voice_img”
  47. android:gravity=“center_horizontal”
  48. android:text=“切到语音聊天”
  49. android:textColor=“#ffffff”
  50. android:textSize=“12sp”/>
  51. <TextView
  52. android:id=“@+id/tv_handup_call”
  53. android:layout_width=“wrap_content”
  54. android:layout_height=“wrap_content”
  55. android:layout_weight=“1”
  56. android:drawablePadding=“10dp”
  57. android:drawableTop=“@mipmap/chat_video_guaduan_img_normal”
  58. android:gravity=“center_horizontal”
  59. android:text=“挂断”
  60. android:textColor=“#ffffff”
  61. android:textSize=“12sp”/>
  62. <TextView
  63. android:id=“@+id/tv_change_camera”
  64. android:layout_width=“wrap_content”
  65. android:layout_height=“wrap_content”
  66. android:layout_weight=“1”
  67. android:drawablePadding=“10dp”
  68. android:drawableTop=“@mipmap/chat_video_change_camera_img”
  69. android:gravity=“center_horizontal”
  70. android:text=“转换摄像头”
  71. android:textColor=“#ffffff”
  72. android:textSize=“12sp”/>
  73. </LinearLayout>
  74. </FrameLayout>

代码

为了实现跟微信一样的效果,普通屏幕全屏显示,为了不让视频内容挤到刘海屏中,添加一下代码:

  1. //如果判断有刘海屏不让填充到状态栏
  2. if (DisplayUtil.hasNotchScreen(this)) {
  3. getWindow().addFlags(
  4. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  5. | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
  6. } else {
  7. getWindow().addFlags(
  8. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  9. | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
  10. | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  11. }

大小视图切换代码:

  1. /**
  2. * 大小视图切换 (小视图在前面、大视图在后面)
  3. *
  4. * @param sourcView 之前相对布局大小
  5. * @param beforeview 之前surfaceview
  6. * @param detView 之后相对布局大小
  7. * @param afterview 之后surfaceview
  8. */
  9. private void zoomOpera(View sourcView, SurfaceView beforeview,
  10. View detView, SurfaceView afterview) {
  11. RelativeLayout paretview = (RelativeLayout) sourcView.getParent();
  12. paretview.removeView(detView);
  13. paretview.removeView(sourcView);
  14. //设置远程大视图RelativeLayout 的属性
  15. RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
  16. RelativeLayout.LayoutParams.MATCH_PARENT);
  17. params1.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
  18. beforeview.setZOrderMediaOverlay(true);
  19. beforeview.getHolder().setFormat(PixelFormat.TRANSPARENT);
  20. sourcView.setLayoutParams(params1);
  21. //设置本地小视图RelativeLayout 的属性
  22. params1 = new RelativeLayout.LayoutParams(defaultLocalwidth, defaultLocalHeight);
  23. params1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
  24. params1.setMargins(0, defaultLocalMargin, defaultLocalMargin, 0);
  25. //在调用setZOrderOnTop(true)之后调用了setZOrderMediaOverlay(true) 遮挡问题
  26. afterview.setZOrderOnTop(true);
  27. afterview.setZOrderMediaOverlay(true);
  28. afterview.getHolder().setFormat(PixelFormat.TRANSPARENT);
  29. detView.setLayoutParams(params1);
  30. paretview.addView(sourcView);
  31. paretview.addView(detView);
  32. }

%title插图%num

效果图一

%title插图%num

效果图二

 

一步搞定头像选择、裁剪、拍照的Android图片选择框架

前言

几乎每个APP都需要图片选择和裁剪功能,因为涉及到相机和存储,所以该功能还是要考虑很多兼容性的。这也是github上有一大堆图片选择框架的原因,但是你会发现github上找的图片选择框架并不是简单的只有图片选择, 它还包含视频选择、视频录制、图片压缩等等一大堆功能。其实你只需要一个头像选择的功能,那些框架很多功能你根本用不到,而且代码至少也几十个类,后期有问题改起来也是麻烦事。所以我就封装了一个代码及其简洁的图片选择框架,没有任何多余的功能,涉及的主要功能类只有3个,使用也是非常简单。

 

效果图如下:

%title插图%num

效果图.jpg

功能特点

  • 支持通过拍照获取图片
  • 支持通过相册获取图片
  • 支持图片裁剪
  • 支持仿IOS底部弹出选择菜单ActionSheet效果
  • 支持6.0动态授予权限
  • 解决图片有黑边问题
  • 解决7.0调用相机报FileUriExposedException的问题
  • 解决小米miui系统调用系统裁剪图片功能crash问题

使用

Step 1. 添加JitPack仓库

在项目的build.gradle添加JitPack仓库

 

  1. allprojects {
  2. repositories {
  3. maven { url “https://jitpack.io” }
  4. }
  5. }

Step 2. 添加依赖

在需要使用的module中添加依赖(*新版本见 PictureSelector)

 

  1. dependencies {
  2. compile ‘com.github.wildma:PictureSelector:1.1.1’
  3. }

或者引用本地lib

 

compile project(':pictureselector')

Step 3. 拍照或者从相册选择图片

 

  1. /**
  2. * create()方法参数一是上下文,在activity中传activity.this,在fragment中传fragment.this。参数二为请求码,用于结果回调onActivityResult中判断
  3. * selectPicture()方法参数分别为 是否裁剪、裁剪后图片的宽(单位px)、裁剪后图片的高、宽比例、高比例。都不传则默认为裁剪,宽200,高200,宽高比例为1:1。
  4. */
  5. PictureSelector
  6. .create(MainActivity.this, PictureSelector.SELECT_REQUEST_CODE)
  7. .selectPicture(true, 200, 200, 1, 1);

Step 4. 获取裁剪后的图片地址

 

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. super.onActivityResult(requestCode, resultCode, data);
  4. /*结果回调*/
  5. if (requestCode == PictureSelector.SELECT_REQUEST_CODE) {
  6. if (data != null) {
  7. String picturePath = data.getStringExtra(PictureSelector.PICTURE_PATH);
  8. }
  9. }
  10. }

代码

每个类的注释我都写的很清楚了,所以这里只贴出主要的图片工具类,其他可以到我的Github上查看源码(见文末)。

选择图片工具类

 

  1. package com.wildma.pictureselector;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.graphics.Bitmap;
  6. import android.graphics.BitmapFactory;
  7. import android.net.Uri;
  8. import android.os.Build;
  9. import android.provider.MediaStore;
  10. import android.support.v4.content.FileProvider;
  11. import android.widget.Toast;
  12. import java.io.File;
  13. import java.io.FileNotFoundException;
  14. /**
  15. * Author wildma
  16. * Github https://github.com/wildma
  17. * CreateDate 2018/6/10
  18. * Desc ${选择图片工具类}
  19. * 使用方法:
  20. * 1. 调用getByCamera()、getByAlbum()可通过拍照或相册获取图片
  21. * 2. 在onActivityResult中调用本工具类的onActivityResult方法处理通过相册或拍照获取的图片
  22. */
  23. public class PictureSelectUtils {
  24. public static final int GET_BY_ALBUM = 0x11;//相册标记
  25. public static final int GET_BY_CAMERA = 0x12;//拍照标记
  26. public static final int CROP = 0x13;//裁剪标记
  27. private static Uri takePictureUri;//拍照图片uri
  28. public static Uri cropPictureTempUri;//裁剪图片uri
  29. /**
  30. * 通过相册获取图片
  31. * @param activity
  32. */
  33. public static void getByAlbum(Activity activity) {
  34. Intent intent = new Intent(Intent.ACTION_PICK,
  35. MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  36. intent.setType(“image/*”);
  37. activity.startActivityForResult(intent, GET_BY_ALBUM);
  38. }
  39. /**
  40. * 通过拍照获取图片
  41. * @param activity
  42. */
  43. public static void getByCamera(Activity activity) {
  44. takePictureUri = createImagePathUri(activity);
  45. if (takePictureUri != null) {
  46. Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  47. i.putExtra(MediaStore.EXTRA_OUTPUT, takePictureUri);//输出路径(拍照后的保存路径)
  48. activity.startActivityForResult(i, GET_BY_CAMERA);
  49. } else {
  50. Toast.makeText(activity, “无法保存到相册”, Toast.LENGTH_LONG).show();
  51. }
  52. }
  53. /**
  54. * 创建一个图片地址uri,用于保存拍照后的照片
  55. *
  56. * @param activity
  57. * @return 图片的uri
  58. */
  59. public static Uri createImagePathUri(Activity activity) {
  60. try {
  61. FileUtils.createOrExistsDir(Constant.DIR_ROOT);
  62. StringBuffer buffer = new StringBuffer();
  63. String pathName = buffer.append(Constant.DIR_ROOT).append(Constant.APP_NAME).append(“.”).append(System.currentTimeMillis()).append(“.jpg”).toString();
  64. File file = new File(pathName);
  65. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //解决Android 7.0 拍照出现FileUriExposedException的问题
  66. String authority = activity.getPackageName() + “.fileProvider”;
  67. takePictureUri = FileProvider.getUriForFile(activity, authority, file);
  68. } else {
  69. takePictureUri = Uri.fromFile(file);
  70. }
  71. } catch (Exception e) {
  72. e.printStackTrace();
  73. Toast.makeText(activity, “无法保存到相册”, Toast.LENGTH_LONG).show();
  74. }
  75. return takePictureUri;
  76. }
  77. /**
  78. * 处理拍照或相册获取的图片,默认大小480*480,比例1:1
  79. * @param activity 上下文
  80. * @param requestCode 请求码
  81. * @param resultCode 结果码
  82. * @param data Intent
  83. * @return
  84. */
  85. public static Bitmap onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
  86. return onActivityResult(activity, requestCode, resultCode, data, 0, 0, 0, 0);
  87. }
  88. /**
  89. * 处理拍照或相册获取的图片
  90. * @param activity 上下文
  91. * @param requestCode 请求码
  92. * @param resultCode 结果码
  93. * @param data Intent
  94. * @param w 输出宽
  95. * @param h 输出高
  96. * @param aspectX 宽比例
  97. * @param aspectY 高比例
  98. * @return
  99. */
  100. public static Bitmap onActivityResult(Activity activity, int requestCode, int resultCode, Intent data,
  101. int w, int h, int aspectX, int aspectY) {
  102. Bitmap bm = null;
  103. if (resultCode == Activity.RESULT_OK) {
  104. Uri uri = null;
  105. switch (requestCode) {
  106. case GET_BY_ALBUM:
  107. uri = data.getData();
  108. activity.startActivityForResult(crop(uri, w, h, aspectX, aspectY), CROP);
  109. break;
  110. case GET_BY_CAMERA:
  111. uri = takePictureUri;
  112. activity.startActivityForResult(crop(uri, w, h, aspectX, aspectY), CROP);
  113. break;
  114. case CROP:
  115. bm = dealCrop(activity);
  116. break;
  117. }
  118. }
  119. return bm;
  120. }
  121. /**
  122. * 裁剪,默认裁剪输出480*480,比例1:1
  123. * @param uri 图片的uri
  124. * @return
  125. */
  126. public static Intent crop(Uri uri) {
  127. return crop(uri, 480, 480, 1, 1);
  128. }
  129. /**
  130. * 裁剪,例如:输出100*100大小的图片,宽高比例是1:1
  131. * @param uri 图片的uri
  132. * @param w 输出宽
  133. * @param h 输出高
  134. * @param aspectX 宽比例
  135. * @param aspectY 高比例
  136. * @return
  137. */
  138. public static Intent crop(Uri uri, int w, int h, int aspectX, int aspectY) {
  139. if (w == 0 && h == 0) {
  140. w = h = 480;
  141. }
  142. if (aspectX == 0 && aspectY == 0) {
  143. aspectX = aspectY = 1;
  144. }
  145. Intent intent = new Intent(“com.android.camera.action.CROP”);
  146. intent.setDataAndType(uri, “image/*”);
  147. intent.putExtra(“crop”, “true”);
  148. intent.putExtra(“aspectX”, aspectX);
  149. intent.putExtra(“aspectY”, aspectY);
  150. intent.putExtra(“outputX”, w);
  151. intent.putExtra(“outputY”, h);
  152. /*解决图片有黑边问题*/
  153. intent.putExtra(“scale”, true);
  154. intent.putExtra(“scaleUpIfNeeded”, true);
  155. /*解决跳转到裁剪提示“图片加载失败”问题*/
  156. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  157. intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  158. /*解决小米miui系统调用系统裁剪图片功能camera.action.CROP后崩溃或重新打开app的问题*/
  159. StringBuffer buffer = new StringBuffer();
  160. String pathName = buffer.append(“file:///”).append(FileUtils.getRootPath()).append(File.separator).append(Constant.APP_NAME).append(“.temp.jpg”).toString();
  161. cropPictureTempUri = Uri.parse(pathName);
  162. intent.putExtra(MediaStore.EXTRA_OUTPUT, cropPictureTempUri);//输出路径(裁剪后的保存路径)
  163. // 输出格式
  164. intent.putExtra(“outputFormat”, “JPEG”);
  165. // 不启用人脸识别
  166. intent.putExtra(“noFaceDetection”, true);
  167. //是否将数据保留在Bitmap中返回
  168. intent.putExtra(“return-data”, false);
  169. return intent;
  170. }
  171. /**
  172. * 处理裁剪,获取裁剪后的图片
  173. * @param context 上下文
  174. * @return
  175. */
  176. public static Bitmap dealCrop(Context context) {
  177. Bitmap bitmap = null;
  178. try {
  179. bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(cropPictureTempUri));
  180. } catch (FileNotFoundException e) {
  181. e.printStackTrace();
  182. }
  183. return bitmap;
  184. }
  185. }

github地址:PictureSelector

Android简单、灵活、高效的图片裁剪框架 Android-ImageClipper

Android图片裁剪的实现方式
Android图片的裁剪实现方式有两种:

调用系统的图片裁剪App;
调用第三方图片裁剪框架。
在这里我会贴出如何调用系统的图片裁剪App的示例代码;并且给出我自定义的图片裁剪框架的实现原理和Github的连接。

调用系统的图片裁剪App
/**
* @param srcUri 原始图片的Uri
* @param desUri 制定的裁剪后的图片要保存的路径所转成的Uri
*/
private void callSystemImageCropper(Uri srcUri, Uri desUri) {
Intent intent = new Intent(“com.android.camera.action.CROP”);
intent.setDataAndType(srcUri, “image/*”);
intent.putExtra(“crop”, “true”);
intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
intent.putExtra(“aspectX”, 1);
intent.putExtra(“aspectY”, 1);
intent.putExtra(“outputFormat”, Bitmap.CompressFormat.JPEG);
intent.putExtra(“outputX”, 400);
intent.putExtra(“outputY”, 400);
intent.putExtra(“scale”, true);
intent.putExtra(“scaleUpIfNeeded”, true);
intent.putExtra(“return-data”, false);
startActivityForResult(intent, 222);
}

等待系统裁剪完图片后,你就可以使用desUri(或者desUri对应的*对路径)对裁剪后的图片进行操作了。

这种方式虽然简单,但是对图片只能进行 正方形 裁剪,所以略有缺憾。

常用的第三方裁剪框架
在github上有很多第三方裁剪框架,但是鄙人从来没有成功同步过依赖(鄙人的家庭网络很渣),并且鄙人一旦使用第三方的东西不爽的时候,就会很容易萌生自己造轮子的想法。

这篇文章列出了常用的第三方图片裁剪框架:
Android开发常用开源框架:图片处理

我的图片裁剪框架 Android-ImageClipper
Android-ImageClipper是鄙人实现的一个简单、灵活、高效的图片裁剪框架,你既可以使用写好的Activity,也可以使用ImageClipView来自定义裁剪功能。

实现思路
将原始图片的Bitmap传入ImageClipView,ImageClipView根据所在的父容器的宽高和原始图片的Bitmap的宽高来计算出自身的宽高,以此来保证按照图片的比例正确现实图片。
绘制裁剪框,根据裁剪框的布局参数来计算出在原始图片中进行裁剪的参数,*后根据裁剪参数进行裁剪。
功能设计
矩形裁剪功能,可以自由控制矩形裁剪框的大小和其他参数,实现自由的矩形裁剪。该功能已经实现。
圆形裁剪功能,可以自由缩放圆形裁剪框的大小和位置,实现自由的圆形裁剪。该功能未实现。
椭圆裁剪功能,可以自由缩放椭圆裁剪框的大小和位置,实现自由的椭圆裁剪。该功能未实现。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速