iOS Universal Links 配置 – 收集了配置无效的问题

前面我们写了一篇iOS分发管理( iOS App托管和分发搭建)的文章,这一篇写一下分发后,如果用户已经安装了app需要调起app的需求

快门:地址初步检验、一篇很不错的文章

注意事项:

•    Your domain is valid (valid DNS)
•    Your file must be served over HTTPS
•    Your server shouldn’t return an error status code (>= 400)
•    Your file cannot be behind redirects
•    Your file must be served with content type “application/pkcs7-mime”
•    Your file should validate and return its contents with `openssl smime -verify -inform DER -noverify`
•    Your file should contain valid JSON (using simple JSON.parse). This can be tripped by things like having an extraneous NULL at the end of your string.
教程很多,我们先看注意事项,上面的重定向、页面报错我都有尝试,确实导致link不生效,把apple-app-site-association文件上传到某个域名指向的服务器中,如果我们测试的web页面会重定向、或者是个不存的页面,都无法正常测试你的结果。当然如果有微信登录的服务,可以配置微信的Universal links,然后调起微信试试就能验证结果。

 

下面简述一下Universal links的配置过程
截图和参考环境:Xcode Version 11.4 (11E146)

大致步骤:创建【apple-app-site-association】文件 >> 上传文件到自己的服务器 >> app内配置 >> 打包安装到手机(真机) >> 使用Safari验证结果 >> 内部逻辑

详细步骤:

1、填写apple-app-site-association文件,可以找个json文件,填写,写完后把.json后缀删除。

{
“applinks”: {
“apps”: [],
“details”: [
{
“appID”: “teamId.bundleid”,
“paths”: [ “*” ]
},
{
“appID”: “H7JOD8J4A1.bundleid”,
“paths”: [ “*” ]
}
]
}
}
我们只要配置上面的appID 和 paths 就可以了;
appID是由teamID和bundleID通过”.”链接在一起组成的,其中teamID在developer.apple.com的Membership选项下可查。但是!!,但是如果是子账号打包调试,teamID可能有变,在Signing & Capabilities中可以看到,具体见下图。我曾花费1天的时间,苦苦思考,*终发现问题是这个teamID不正确
paths的配置,建议前期简单配置,测试通过再去琢磨是否有必要丰富一下path
注意格式不要出错,一定是严格的json格式,建议在json.cn中编辑,然后贴过来。

%title插图%num
2、在服务器根目录新建.well-known目录,然后上传刚刚写好的文件,这样就可以通过https://yourHost/.well-known/apple-app-site-association来访问到刚才的文件,如果不能访问,则上传的有问题。

3、app内配置Associated Domains,见上图

前面要追加applinks: ​
不用写协议部分https://,直接写host部分就行了,
举例,假设我们现在配置的是得道app,注意格式即可,参数值只是示例:

apple-app-site-association文件中的teamID为 JDIU78976J.com.dedao.app,paths不变
上传到得到移动端的文件地址:https://m.dedao.com/.well-known/apple-app-site-association,并且可在浏览器访问到内容
在Xcode配置是:applinks:m.dedao.com
把3中配置后的app运行到真机里,或者打包安装也可以。
在4完成后的手机中,找到Safari,浏览器输入:https://m.dedao.com?id=8301280,这里假设
4、把app安装到app中,然后在Safari中打开一个部署在刚才的服务上的h5页面,加载完成后,微微下拉,即可看到打开app的提示。如果有提示,即代表配置成功。

5、如果需要内部跳转逻辑,可以实现以下代理方法,抓取到URL即可自行处理

swift:

func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
OC:

– (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable restorableObjects))restorationHandler;
取userActivity.webpageURL即可。如果微信分享使用了 universal Links的方式通信,需要单独做个判断,具体如何判断,抓一个微信回调的URL即可发现规律。

iOS图片的加载优化 initWithContentsOfFile

有的小伙伴可能没那么在意图片的加载方式,习惯了imageNamed,看到initWithContentsOfFile时也没有仔细看他的作用。

1、首先我做了一个实验,比较了两种方式对内存的影响。
测试方法,对于app的引导页(5张图)分别使用imageName和initWithContentsOfFile去初始化图片

使用imageName加载图片
[UIImage imageNamed:@”launch_iphonex”];
启动之后,进入引导页,打开Xcode的内存监测,看到每滑动一次,内存增加一次,5张图下来,累计增加了20M+ 的内存。引导页过后,内存依然没被释放(imageFileNamed的特点)

大致计算一下增加的内存:比如iPhoneX的引导图1125 * 2436,加载出来大约是3M(这里我不会很会算,可参考)

内存变化如下

使用initWithContentsOfFile去初始化图片,内存依旧会增加,但是引导页过后,内存直线下降。
内存变化如下

这里看到效果很明显,对于我们不常用/只使用一次的图片资源,使用initWithContentsOfFile去加载图片是非常不错的选择,对于高要求的的开发者几乎是一定要优化的点。

2、下面简单介绍下二者的区别:
1)imageNamed

用这个方法加载的时候,它会在系统缓存中查找并返回一个对象,如果缓存中没有找到对应的对象,就根据文件名找到,然后创建image对象,再返回。在APP的整个生命周期内,加载过的图是一直存在内存中的,内存就会持续累加。但是对于一些文件很小,我们又经常用到的小图,很实用。比如tableView中的一个箭头,在多行都会展示,这时候使用imageNamed效率是很高的。

2)initWithContentsOfFile,加载图片时,image对象的生命周期与imageView绑定,会随着imageView释放而释放掉。对于一些不常用的且大的图片,是很好的选择。有些同学习惯用asset去存图片,自动分配2倍3倍图,如果使用这个方法就不好用了,建议直接搞一张3倍图拖入某个特定的文件夹。还要注意的是,这时候自动布局的话,记得写全布局属性,他这会儿已经不会自动调整了。引导图、某一页面特殊的背景图等等都可以使用这种方式。

 

initWithContentsOfFile编码(这里只兼容了png,可以开放更多参数)

+ (UIImage *)imageFileNamed:(NSString *)imageName {

NSString *filePath = [NSString stringWithFormat:@”%@/%@.png”,[[NSBundle mainBundle] resourcePath],imageName];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];

return image;
}
OK提高我们的标准,让你的APP运行的更流畅。

Android-代码中获取Logcat打印日志并存放于文件中

1.Logcat命令的使用

  1. logcat -c 清除屏幕显示
  2. logcat -d 显示
  3. logcat 显示日志
  4. log logcat -f filename将日志输出到一个文件中
  5. logcat -v time 显示时间
  6. logcat -v time -s tag:priority 这里会输出与priority相等或者优先级比priority优先级高的

priority有5种:

  1. V — Verbose (优先级*低)
  2. D — Debug
  3. I — Info
  4. W — Warning
  5. E — Error
  6. F — Fatal
  7. S — Silent

比如我们在串口输入logcat -v time -s ScreenSaver:D
会打印出

01-02 10:30:05.690 I/ScreenSaver(  957): screen now time = 8934wait time :480

Screensaver是tag,因为I的优先级比D高,因此也会输出I的(Log.i());

2.如何在代码中运行命令行

这需要用到

    Runtime.getRunntime.exec();

exec的参数一般为String [];
比如我们要抓取日志带时间,我们的代码可以这样写

Runtime.getRuntime.exec(new String[]{"logcat","-v","time","-s","ScreenSaver:D"})

通过exec直接运行”logcat -v time -s ScreenSaver:D”,是不行的。
上面Runntime.getRunntime.exec会返回Process对象,Process有6种方法可以调用:

1.destroy():杀掉子进程
2.exitValue():返回子进程的出口值,值 0 表示正常终止
3.getErrorStream():获取子进程的错误流
4.getInputStream():获取子进程的输入流
5.getOutputStream():获取子进程的输出流
6.waitFor():导致当前线程等待,如有必要,一直要等到由该 Process对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,根据惯例,0表示正常终止

这里我们主要使用getInputStream()来获取打印日志的输入流

3.具体代码参考

这里我只截取一部分代码作为参考

  1. private FileOutputStream fos;
  2. /*核心代码*/
  3. while(captureLogThreadOpen){
  4. /*
  5. try {
  6. Thread.sleep(100);
  7. } catch (InterruptedException e) {
  8. // TODO Auto-generated catch block
  9. e.printStackTrace();
  10. }
  11. */
  12. try{
  13. /*命令的准备*/
  14. ArrayList<String> getLog = new ArrayList<String>();
  15. getLog.add(“logcat”);
  16. getLog.add(“-d”);
  17. getLog.add(“-v”);
  18. getLog.add(“time”);
  19. ArrayList<String> clearLog = new ArrayList<String>();
  20. clearLog.add(“logcat”);
  21. clearLog.add(“-c”);
  22. Process process = Runtime.getRuntime().exec(getLog.toArray(new String[getLog.size()]));//抓取当前的缓存日志
  23. BufferedReader buffRead = new BufferedReader(new InputStreamReader(process.getInputStream()));//获取输入流
  24. Runtime.getRuntime().exec(clearLog.toArray(new String[clearLog.size()]));//清除是为了下次抓取不会从头抓取
  25. String str = null;
  26. logFile = new File(logPath+“log.txt”);//打开文件
  27. fos = new FileOutputStream(logFile,true);//true表示在写的时候在文件末尾追加
  28. String newline = System.getProperty(“line.separator”);//换行的字符串
  29. //Date date = new Date(System.currentTimeMillis());
  30. //String time = format.format(date);
  31. //Log.i(TAG, “thread”);
  32. while((str=buffRead.readLine())!=null){//循环读取每一行
  33. //Runtime.getRuntime().exec(clearLog.toArray(new String[clearLog.size()]));
  34. //Log.i(TAG, str);
  35. SimpleDateFormat format = new SimpleDateFormat(“yyyy-“);
  36. Date date = new Date(System.currentTimeMillis());
  37. String time = format.format(date);
  38. fos.write((time+str).getBytes());//加上年
  39. fos.write(newline.getBytes());//换行
  40. logCount++;
  41. if(logCount>10000){//大于10000行就退出
  42. captureLogThreadOpen = false;
  43. captureLogThread = null;
  44. fos.close();
  45. break;
  46. }
  47. }
  48. fos.close();
  49. fos = null;
  50. Runtime.getRuntime().exec(clearLog.toArray(new String[clearLog.size()]));
  51. }catch(Exception e){
  52. }
  53. }

 

我们通过str=buffRead.readLine()来读取日志的每一行数据,再通过fos.write((time+str).getBytes());写入到文件中。

4.注意事项

目前android中想要抓取日志,只允许获取系统及以上权限的apk才能进行这个操作,否则只能获得本app的logcat,因此,在抓取日志之前,*好使用签名工具对app进行签名获取系统权限

iOS App托管和分发搭建

我们基本都用过蒲公英或者fir.im做应用分发,但是可有想过他们是怎么实现的吗
如果想自己包里内测包,内部分发,或者企业包的分发,可以参考

自建和三方的优劣对比
产品

优点

缺点

1 自建 稳定
UI和功能可自定义
没有使用限制
方便内测运营 需要开发和维护,投入较大
2 三方
方便快捷,无需开发
功能完备(

版本管理,应用管理
应用合并
开放API
成员管理、统计等
)

有下载次数限制,一般为100次/天,且多款app共享该下载次数
可能需要安装密码,一些场景中不方便使用密码
可能不稳定,偶尔会挂掉
未加壳、加固的内测包上传到第三方平台,被反编译的风险增加
不方便运营,如app调起
用过的三方托管平台:

蒲公英(https://www.pgyer.com)
fir.im(https://www.betaqr.com/apps)
iOS包管理方案
概述
流程步骤:

企业包:【打包】-【配置manifest】-【上传】-【开发前端下载页面】-【下载】
内测包:【添加测试机】-【打包】-【配置manifest】-【上传】-【开发前端下载页面】-【下载】
背景原理:

Apple为了保护ipa包的安全性,不开放直接安装ipa的入口,由Safari的来触发系统命令,增加了直接拿到ipa包的难度(反变异ipa包后,开发信息泄漏的风险就很大)
开发者把ipa的下载地址写入manifest文件,然后把manifest上传到云,这样就可以通过云地址来获取app的基本信息:包地址、icon大、icon小
由Safari的命令(itms-services://?action=download-manifest&url=manifest的地址)来触发内部动作,拉取manifest,读取下载地址,然后下载ipa包,并安装到手机内
用户通过前端页面,如点击按钮,由程序执行上面操作,那么这个安装的过程就被封装起来了,用户没有看到ipa包,只要点击一安装并确定就可以了。
当然了,如果通过手段,分析前端请求和代码,抓取到manifest的云地址,就可以看到ipa的下载地址,这样就拿到了ipa包,只不过是这个过程就比直接提供ipa要复杂很多,破解成本提高了很多。
具体做法
1、准备云存储服务(七牛、阿里),打开上传文件功能,有自己的api上传也可以。

2、打包勾选Additional Options,并填写下载地址、图片地址。

一般来说,先上传图到云,拿到图片地址,然后修改文件名部分就行了,下次上传还使用同样的文件名就 OK 了

配图:

%title插图%num

%title插图%num

 

3、上传ipa到配置好的地址,上传由manifest.plist文件

这里有个tips,第二步骤就是的配置就是为了打包后多生成一个manifest.plist文件,如果你很熟练,可以跳过配置,直接打包,然后自己去修改manifest文件,再上传到云,效果是一样的。具体manifest是什么样的,自己打包生成一个看下。特别是自己开发包管理的后台管理功能时,应该熟练使用manifest文件的编辑。

4、在Safari中安装app

设manifest地址为url,则可在Safari中输入itms-services://?action=download-manifest&url=url,然后会提示是否下载,确定即可下载。这里需要主要的是,如果url中有特殊字符,需要编码。在Safari中输入的链接仅仅是测试你的配置做法是否正确,真正用的时候需要前端做个叶页面,来执行我们输入的地址命令,这样会更加友好

5、企业包和内侧包

如果打企业包,在开通企业账号的前提下,打包时选择Enterprise的方式即可

如果打内测包,需要提前把测试机的UDID倒入公司的测试名单内,倒入方法这里就不啰嗦了,打包时选择Ad Hoc即可。

 

根据企业的需要,如果大范围内测,或者安全性要求比较高,或者对下载页面要求比较高,且有一定的人力资源,那么可以自己开发;如果只是内部使用,可以做的简单一点;如果人力紧张,且只是内部测试时使用,而且安全要求不是很高(代码中没什么可泄漏的)就别瞎折腾了,还是集中精力做业务吧。

iOS Universal Links 配置 – 收集了配置无效的问题

前面我们写了一篇iOS分发管理( iOS App托管和分发搭建)的文章,这一篇写一下分发后,如果用户已经安装了app需要调起app的需求

快门:地址初步检验、一篇很不错的文章

注意事项:

•    Your domain is valid (valid DNS)
•    Your file must be served over HTTPS
•    Your server shouldn’t return an error status code (>= 400)
•    Your file cannot be behind redirects
•    Your file must be served with content type “application/pkcs7-mime”
•    Your file should validate and return its contents with `openssl smime -verify -inform DER -noverify`
•    Your file should contain valid JSON (using simple JSON.parse). This can be tripped by things like having an extraneous NULL at the end of your string.
教程很多,我们先看注意事项,上面的重定向、页面报错我都有尝试,确实导致link不生效,把apple-app-site-association文件上传到某个域名指向的服务器中,如果我们测试的web页面会重定向、或者是个不存的页面,都无法正常测试你的结果。当然如果有微信登录的服务,可以配置微信的Universal links,然后调起微信试试就能验证结果。

 

下面简述一下Universal links的配置过程
截图和参考环境:Xcode Version 11.4 (11E146)

大致步骤:创建【apple-app-site-association】文件 >> 上传文件到自己的服务器 >> app内配置 >> 打包安装到手机(真机) >> 使用Safari验证结果 >> 内部逻辑

详细步骤:

1、填写apple-app-site-association文件,可以找个json文件,填写,写完后把.json后缀删除。

{
“applinks”: {
“apps”: [],
“details”: [
{
“appID”: “teamId.bundleid”,
“paths”: [ “*” ]
},
{
“appID”: “H7JOD8J4A1.bundleid”,
“paths”: [ “*” ]
}
]
}
}
我们只要配置上面的appID 和 paths 就可以了;
appID是由teamID和bundleID通过”.”链接在一起组成的,其中teamID在developer.apple.com的Membership选项下可查。但是!!,但是如果是子账号打包调试,teamID可能有变,在Signing & Capabilities中可以看到,具体见下图。我曾花费1天的时间,苦苦思考,*终发现问题是这个teamID不正确
paths的配置,建议前期简单配置,测试通过再去琢磨是否有必要丰富一下path
注意格式不要出错,一定是严格的json格式,建议在json.cn中编辑,然后贴过来。
2、在服务器根目录新建.well-known目录,然后上传刚刚写好的文件,这样就可以通过https://yourHost/.well-known/apple-app-site-association来访问到刚才的文件,如果不能访问,则上传的有问题。

3、app内配置Associated Domains,见上图

前面要追加applinks: ​
不用写协议部分https://,直接写host部分就行了,
举例,假设我们现在配置的是得道app,注意格式即可,参数值只是示例:

apple-app-site-association文件中的teamID为 JDIU78976J.com.dedao.app,paths不变
上传到得到移动端的文件地址:https://m.dedao.com/.well-known/apple-app-site-association,并且可在浏览器访问到内容
在Xcode配置是:applinks:m.dedao.com
把3中配置后的app运行到真机里,或者打包安装也可以。
在4完成后的手机中,找到Safari,浏览器输入:https://m.dedao.com?id=8301280,这里假设
4、把app安装到app中,然后在Safari中打开一个部署在刚才的服务上的h5页面,加载完成后,微微下拉,即可看到打开app的提示。如果有提示,即代表配置成功。

5、如果需要内部跳转逻辑,可以实现以下代理方法,抓取到URL即可自行处理

swift:

func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
OC:

– (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable restorableObjects))restorationHandler;
取userActivity.webpageURL即可。如果微信分享使用了 universal Links的方式通信,需要单独做个判断,具体如何判断,抓一个微信回调的URL即可发现规律。

iOS刘海屏适配,iPhoneX、iPhone12系列导航栏高度,刘海平适配

NAVIHEIGHT:(iPhoneX ? 88 : 64)、 TABBARHEIGHT: (iPhoneX ? 83 : 49)

新的iPhone、iPhone12发布后,我*件想做的事就是如何适配新iPhone,新的iPhone都是刘海屏幕

先看下各个尺寸

%title插图%num
各版本iPhone的尺寸及分辨率

%title插图%num

于是我就做了下面的宏定义(ScreenHeight就省略了):

#define iPhoneX :(ScreenHeight == 812.0f || ScreenHeight == 896.0f || ScreenHeight == 844.0f || ScreenHeight == 926.0f)

#define AdaptNaviHeight      (iPhoneX ? 24 : 0) //状态栏高度

#define AdaptTabHeight       (iPhoneX ? 34 : 0) //Tab bar 圆角部分高度

#define NAVIHEIGHT           (iPhoneX ? 88 : 64) //导航

#define TABBARHEIGHT         (iPhoneX ? 83 : 49) // 分栏

除了上面的方法外,官方的iPhone型号对照已更新,我们可以根据对照表,找到具体的手机型号。

具体方法及枚举如下

+ (NSString *)getDeviceSystemName {
static dispatch_once_t one;
static NSString *name;
dispatch_once(&one, ^{
NSString *model = [[UIDevice currentDevice] machineModel];
if (!model) return;
NSDictionary *dic = @{
@”iPhone7,2″ : @”iPhone 6″,
@”iPhone7,1″ : @”iPhone 6 Plus”,
@”iPhone8,1″ : @”iPhone 6s”,
@”iPhone8,2″ : @”iPhone 6s Plus”,
@”iPhone8,4″ : @”iPhone SE”,
@”iPhone9,1″ : @”iPhone 7″,
@”iPhone9,2″ : @”iPhone 7 Plus”,
@”iPhone9,3″ : @”iPhone 7″,
@”iPhone9,4″ : @”iPhone 7 Plus”,
@”iPhone10,1″ : @”iPhone 8″,
@”iPhone10,4″ : @”iPhone 8″,
@”iPhone10,2″ : @”iPhone 8 Plus”,
@”iPhone10,5″ : @”iPhone 8 Plus”,
@”iPhone10,3″ : @”iPhone X”,
@”iPhone10,6″ : @”iPhone X”,
@”iPhone11,2″ : @”iPhone XS”,
@”iPhone11,4″ : @”iPhone XS Max”,
@”iPhone11,6″ : @”iPhone XS Max”,
@”iPhone11,8″ : @”iPhone XR”,
};
name = dic[model];
});
return name;

}
tips:

1、关于每个控制器的适配,我采用了继承的方式,并且自定义了导航栏,所以导航栏的高度我在基控制器就做了控制,子控制器直接取自定义的导航,就可以做页面适配了。

2、关于自定义导航上的控件,每个按钮和标题都以导航的底部为参考,这样导航高度变了,也不会导致导航上控件的位置错误。

iOS – iPhone手机刘海屏判断

前言
*近写毕业设计的时候,发现 iPhoneX 之后的刘海屏手机顶部状态栏高度和底部TabBar高度和原来不一样了,这就需要我们对刘海屏手机做单独的 UI 布局适配了。

刘海屏判断
适配的核心是要对刘海屏进行判断,以下针对刘海屏手机的特征,提供了两种判断方法。

1. 安全区底部边距判断法
在 iOS 11 之后,多了安全区(下图蓝色区域)的概念。刘海屏手机因为多了下方的小黑条,底部安全区存在距离屏幕底部的边距,而且这是非刘海屏所不具有的。

%title插图%num

因此,我们可以将其作为判断刘海屏的依据。如果系统大于 iOS 11 且安全区底部到屏幕底部存在间距,就将其判断为刘海屏。代码如下:

// 刘海屏判断
#define iPhoneX ({ \
BOOL iPhoneX = NO; \
if (@available(iOS 11.0, *)) { \
if ([UIApplication sharedApplication].windows[0].safeAreaInsets.bottom > 0) { \
iPhoneX = YES; \
} \
} \
iPhoneX; \
})

2. 屏幕宽高比判断法
考虑到我不太喜欢写这种多行宏定义,据说会降低编译速度。我又继续寻找其他刘海屏的特征,终于在下面这一张图中找到了灵感。

%title插图%num

由上图所示,但凡是刘海屏手机,屏幕的纵横比都是 19 : 9。

设手机屏幕宽度为W i d t h WidthWidth,高度为 H e i g h t HeightHeight。根据屏幕纵横比规律,它只要满足以下两个条件其中一个,我们就能判它是刘海屏。
∣ H e i g h t W i d t h − 812 375 ∣ < 0.01 |\frac{Height}{Width} – \frac{812}{375}| < 0.01

Width
Height


375
812

∣<0.01

∣ H e i g h t W i d t h − 896 414 ∣ < 0.01 |\frac{Height}{Width} – \frac{896}{414}| < 0.01

Width
Height


414
896

∣<0.01

代码如下:

// 刘海屏判断
#define iPhoneX (ABS(MAX(CGRectGetWidth([UIScreen mainScreen].bounds), CGRectGetHeight([UIScreen mainScreen].bounds)) / MIN(CGRectGetWidth([UIScreen mainScreen].bounds), CGRectGetHeight([UIScreen mainScreen].bounds)) – 896 / 414.0) < 0.01 || ABS(MAX(CGRectGetWidth([UIScreen mainScreen].bounds), CGRectGetHeight([UIScreen mainScreen].bounds)) / MIN(CGRectGetWidth([UIScreen mainScreen].bounds), CGRectGetHeight([UIScreen mainScreen].bounds)) – 812 / 375.0) < 0.01)
1
2
PS:用小于 0.01 是为了避免精度问题,以及 iPhone 12 系列和前面刘海屏手机的纵横比存在一定误差。

相关常用宏
既然有了刘海屏的判断, 后面的工作就变得简单起来了。下面是我在编写相关代码的时候所写的宏,有需要的同学可以参考一下。

// 屏幕宽度
#define ScreenWidth [UIScreen mainScreen].bounds.size.width

// 屏幕高度
#define ScreenHeight [UIScreen mainScreen].bounds.size.height

// 状态栏高度
#define StatusBar_Height (iPhoneX ? 44.0f : 20.0f)

// 导航栏高度
#define NaviBar_Height (44.0f)

// 状态栏+导航栏高度
#define StatusBar_NaviBar_Height (StatusBar_Height + NaviBar_Height)

// TabBar高度
#define TabBar_Height (iPhoneX ? 49.0f + 34.0f : 49.0f)

Gradle工具的学习

Gradle 是一个工具,同时它也是一个编程框架。前面也提到过,使用这个工具可以完成app的编译打包等工作。当然你也可以用它干其他的事情。

相关 Gradle的官网:http://gradle.org/

https://docs.gradle.org/current/dsl/

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

  • settings.gradle settings.gradle除了可以include外,还可以设置一些函数。这些函数会在gradle构建整个工程任务的时候执行,所以,可以在settings做一些初始化的工作。比如:我的settings.gradle的内容:

    <pre class=”hljs groovy” data-original-code=”” 定义一个名为initminshenggradleenvironment的函数。该函数内部完成一些初始化操作=”” 比如创建特定的目录,设置特定的参数等”=”” data-snippet-id=”ext.da31982bafbbbb24ed75235f36fda35b” data-snippet-saved=”false” data-codota-status=”done”>

    1. //定义一个名为initMinshengGradleEnvironment的函数。该函数内部完成一些初始化操作//比如创建特定的目录,设置特定的参数等
    2. def initMinshengGradleEnvironment(){
    3. println“initialize Minsheng Gradle Environment …..”
    4. ……//干一些special的私活….
    5. println“initialize Minsheng Gradle Environment completes…”
    6. }
    7. //settings.gradle加载的时候,会执行initMinshengGradleEnvironment
    8. initMinshengGradleEnvironment()
    9. //include也是一个函数:
    10. include ‘CPosSystemSdk’ , ‘CPosDeviceSdk’ , ‘CPosSdkDemo’,‘CPosDeviceServerApk’,‘CPosSystemSdkWizarPosImpl’

gradle命令介绍

  1. gradle projects查看工程信息
  2. gradle tasks查看任务信息
  3. gradle task-name执行任务
    列出了好多任务,这时候就可以通过 gradle 任务名来执行某个任务。这和make xxx很像。比如:
    gradle clean是执行清理任务,和make clean类似。
    gradle properites用来查看所有属性信息。
    gradle tasks会列出每个任务的描述,通过描述,我们大概能知道这些任务是干什么的…..。然后gradle task-name执行它就好。
    这里要强调一点:Task和Task之间往往是有关系的,这就是所谓的依赖关系。比如,assemble task就依赖其他task先执行,assemble才能完成*终的输出

Gradle基本类型

gradle基于groovy, gradle提供的基本类型也都实现了script.并且提供了大量的方法和属性

Build script -> Project (每个project的build.gradle也就是一个project)
Init script -> Gradle
Settings script -> Settings (对应setting.gradle)

  • Project 每个project的build.gradle都会转换成一个project
  • Gradle
  • Settings 每一个settings.gradle都会转换成一个Settings对象

Gradle 生命周期

1.初始化创建Settings实例
2.解析settings.gradle 构造各个Project实例
3.解析每个Project对应的build.gradle,配置相应Project

即 setting.gradle -> 各个build.gradle

%title插图%num

Paste_Image.png

%title插图%num

Paste_Image.png

上面*张图是settings.gradle的配置
第二张是执行assemble 打印出来的执行顺序,也就是说settings.gradle先执行
然后是root.gradle (也就是我们根目录下的gradle) 接下来就是按照settings.gradle配置顺序进行执行~

每个对象的方法就不再赘述,api中都有。

实战

root gradle

  1. // Top-level build file where you can add configuration options common to all sub-projects/modules.
  2. buildscript {
  3. repositories {
  4. mavenCentral()
  5. }
  6. dependencies {
  7. classpath ‘com.android.tools.build:gradle:1.2.3’
  8. }
  9. }
  10. allprojects {//对所有projects都起作用
  11. repositories {
  12. mavenCentral()
  13. }
  14. }
  15. ext {//定义所有project公用参数 使用rootProject.ext.XX就能拿到相应对象
  16. compileSdk = 23
  17. minSdk = 11
  18. targetSdk = 23
  19. support = “23.1.1”
  20. buildTools = “23.0.2”
  21. glide = “3.6.0”
  22. okio = “1.4.0”
  23. okhttp = “2.4.0”
  24. fabric = “2.4.0”
  25. leakcanary = “1.3.1”
  26. logansquare = “1.1.0”
  27. dagger = “2.0.1”
  28. packageName = “com.evilsoulm.keep_nice”
  29. butterknife = “7.0.1”
  30. retrofit = “2.0.0-beta2”
  31. greendao = “2.0.0”
  32. }
  33. task clean(type: Delete) {
  34. delete rootProject.buildDir
  35. }

commonandroid.gradle android项目共用gradle 相当于基类 可以通过apply from 引入

  1. android {
  2. compileSdkVersion rootProject.ext.compileSdk
  3. buildToolsVersion rootProject.ext.buildTools
  4. defaultConfig {
  5. minSdkVersion rootProject.ext.minSdk
  6. targetSdkVersion rootProject.ext.targetSdk
  7. }
  8. compileOptions {
  9. sourceCompatibility JavaVersion.VERSION_1_7
  10. targetCompatibility JavaVersion.VERSION_1_7
  11. }
  12. dexOptions {
  13. javaMaxHeapSize “2048m”
  14. }
  15. dexOptions {
  16. preDexLibraries project.hasProperty(‘debug’)
  17. incremental project.hasProperty(‘debug’)
  18. }
  19. packagingOptions {
  20. exclude ‘.readme’
  21. exclude ‘LICENSE.txt’
  22. exclude ‘META-INF/DEPENDENCIES’
  23. exclude ‘META-INF/dependencies’
  24. exclude ‘META-INF/DEPENDENCIES.txt’
  25. exclude ‘META-INF/dependencies.txt’
  26. }
  27. }

commonproject.gradle project项目共用gradle

  1. repositories {
  2. mavenCentral()
  3. }
  4. apply plugin: ‘findbugs’
  5. apply plugin: ‘checkstyle’
  1. apply plugin: ‘com.android.application’
  2. apply plugin: ‘com.neenbedankt.android-apt’
  3. apply from: rootProject.file(‘basegradle/commonProject.gradle’)//引入commonProject
  4. apply from: rootProject.file(‘basegradle/commonAndroid.gradle’)//引入commonAndroid
  5. buildscript {
  6. repositories {
  7. mavenCentral()
  8. }
  9. dependencies {
  10. classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’
  11. }
  12. }
  13. android {
  14. defaultConfig {
  15. applicationId rootProject.ext.packageName
  16. versionCode 1
  17. versionName “1.0”
  18. }
  19. buildTypes {
  20. debug {
  21. minifyEnabled false
  22. debuggable true
  23. jniDebuggable true
  24. }
  25. release {
  26. minifyEnabled true
  27. shrinkResources false
  28. debuggable false
  29. jniDebuggable false
  30. zipAlignEnabled true
  31. proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  32. }
  33. }
  34. configurations {
  35. all*.exclude group: ‘com.google.android’, module: ‘android’
  36. all*.exclude group: ‘com.google.android’, module: ‘support-v4’
  37. all*.exclude group: ‘asm’, module: ‘asm’
  38. all*.exclude group: ‘com.google.code.gson’
  39. }
  40. productFlavors {//很棒的一个参数可以在底下更改包名,资源等。。
  41. normal {
  42. }
  43. ddebug {
  44. }
  45. }
  46. }
  47. dependencies {
  48. compile project(‘:common’)
  49. compile project(‘:model’)
  50. compile fileTree(dir: ‘libs’, include: [‘*.jar’])
  51. compile ‘com.android.support:appcompat-v7:’ + rootProject.ext.support
  52. compile ‘com.android.support:design:’ + rootProject.ext.support
  53. compile ‘com.squareup.okio:okio:’ + rootProject.ext.okio
  54. compile(‘com.squareup.okhttp:okhttp:’ + rootProject.ext.okhttp) {
  55. exclude group: ‘com.squareup.okio’, module: ‘okio’
  56. }
  57. //图片加载
  58. compile ‘com.github.bumptech.glide:glide:’ + rootProject.ext.glide
  59. //leakcanary
  60. debugCompile ‘com.squareup.leakcanary:leakcanary-android:’ + rootProject.ext.leakcanary
  61. releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:’ + rootProject.ext.leakcanary
  62. //logansquare
  63. apt ‘com.bluelinelabs:logansquare-compiler:’ + rootProject.ext.logansquare
  64. compile ‘com.bluelinelabs:logansquare:’ + rootProject.ext.logansquare
  65. //dagger2 注入
  66. apt ‘com.google.dagger:dagger-compiler:’ + rootProject.ext.dagger
  67. compile ‘com.google.dagger:dagger:’ + rootProject.ext.dagger
  68. //UI注入
  69. compile ‘com.jakewharton:butterknife:’ + rootProject.ext.butterknife
  70. //rxjava
  71. compile ‘io.reactivex:rxjava:1.0.10’
  72. compile ‘io.reactivex:rxandroid:0.24.0’
  73. //网络框架
  74. compile ‘com.squareup.retrofit:retrofit:’ + rootProject.ext.retrofit
  75. compile ‘com.squareup.retrofit:converter-gson:’ + rootProject.ext.retrofit
  76. compile ‘com.squareup.retrofit:adapter-rxjava:’ + rootProject.ext.retrofit
  77. }

写法2

另一种写法就是只有一个root.build 通过configure进行每个module的配置 ,每个module下的build.gradle就可以删除了~统一在root.build下进行管理
eg.只写了一个module

  1. buildscript {
  2. repositories {
  3. mavenCentral()
  4. }
  5. dependencies {
  6. classpath ‘com.android.tools.build:gradle:1.2.3’
  7. }
  8. }
  9. allprojects {
  10. repositories {
  11. mavenCentral()
  12. }
  13. }
  14. ext {
  15. compileSdk = 23
  16. minSdk = 11
  17. targetSdk = 23
  18. support = “23.1.1”
  19. buildTools = “23.0.2”
  20. glide = “3.6.0”
  21. okio = “1.4.0”
  22. okhttp = “2.4.0”
  23. fabric = “2.4.0”
  24. leakcanary = “1.3.1”
  25. logansquare = “1.1.0”
  26. dagger = “2.0.1”
  27. packageName = “com.evilsoulm.keep_nice”
  28. butterknife = “7.0.1”
  29. retrofit = “2.0.0-beta2”
  30. greendao = “2.0.0”
  31. }
  32. configure(project(‘:app’).subprojects) {//相当于放在app下的build.gradle
  33. apply plugin: ‘com.android.application’
  34. apply plugin: ‘com.neenbedankt.android-apt’
  35. apply from: rootProject.file(‘basegradle/commonProject.gradle’)
  36. apply from: rootProject.file(‘basegradle/commonAndroid.gradle’)
  37. buildscript {
  38. repositories {
  39. mavenCentral()
  40. }
  41. dependencies {
  42. classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’
  43. }
  44. }
  45. android {
  46. defaultConfig {
  47. applicationId rootProject.ext.packageName
  48. versionCode 1
  49. versionName “1.0”
  50. }
  51. buildTypes {
  52. debug {
  53. minifyEnabled false
  54. debuggable true
  55. jniDebuggable true
  56. }
  57. release {
  58. minifyEnabled true
  59. shrinkResources false
  60. debuggable false
  61. jniDebuggable false
  62. zipAlignEnabled true
  63. proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  64. }
  65. }
  66. configurations {
  67. all*.exclude group: ‘com.google.android’, module: ‘android’
  68. all*.exclude group: ‘com.google.android’, module: ‘support-v4’
  69. all*.exclude group: ‘asm’, module: ‘asm’
  70. all*.exclude group: ‘com.google.code.gson’
  71. }
  72. productFlavors {
  73. normal {
  74. }
  75. ddebug {
  76. }
  77. }
  78. }
  79. dependencies {
  80. compile project(‘:common’)
  81. compile project(‘:model’)
  82. compile fileTree(dir: ‘libs’, include: [‘*.jar’])
  83. compile ‘com.android.support:appcompat-v7:’ + rootProject.ext.support
  84. compile ‘com.android.support:design:’ + rootProject.ext.support
  85. compile ‘com.squareup.okio:okio:’ + rootProject.ext.okio
  86. compile(‘com.squareup.okhttp:okhttp:’ + rootProject.ext.okhttp) {
  87. exclude group: ‘com.squareup.okio’, module: ‘okio’
  88. }
  89. //图片加载
  90. compile ‘com.github.bumptech.glide:glide:’ + rootProject.ext.glide
  91. //leakcanary
  92. debugCompile ‘com.squareup.leakcanary:leakcanary-android:’ + rootProject.ext.leakcanary
  93. releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:’ + rootProject.ext.leakcanary
  94. //logansquare
  95. apt ‘com.bluelinelabs:logansquare-compiler:’ + rootProject.ext.logansquare
  96. compile ‘com.bluelinelabs:logansquare:’ + rootProject.ext.logansquare
  97. //dagger2 注入
  98. apt ‘com.google.dagger:dagger-compiler:’ + rootProject.ext.dagger
  99. compile ‘com.google.dagger:dagger:’ + rootProject.ext.dagger
  100. //UI注入
  101. compile ‘com.jakewharton:butterknife:’ + rootProject.ext.butterknife
  102. //rxjava
  103. compile ‘io.reactivex:rxjava:1.0.10’
  104. compile ‘io.reactivex:rxandroid:0.24.0’
  105. //网络框架
  106. compile ‘com.squareup.retrofit:retrofit:’ + rootProject.ext.retrofit
  107. compile ‘com.squareup.retrofit:converter-gson:’ + rootProject.ext.retrofit
  108. compile ‘com.squareup.retrofit:adapter-rxjava:’ + rootProject.ext.retrofit
  109. }
  110. }

属性

如果是单个脚本,则不需要考虑属性的跨脚本传播,但是Gradle往往包含不止一个build.gradle文件,比如我设置的utils.gradle,settings.gradle。如何在多个脚本中设置属性呢?
Gradle提供了一种名为extra property的方法。extra property是额外属性的意思,在*次定义该属性的时候需要通过ext前缀来标示它是一个额外的属性。定义好之后,后面的存取就不需要ext前缀了。ext属性支持Project和Gradle对象。即Project和Gradle对象都可以设置ext属性

在上面的例子中其实我们已经用到了ext来定义一些常亮,eg:packageName,compileversion等
现在结合groovy相关知识,再重构下上面的gradle配置.

定义一个const.gradle用来管理我们的常亮和各个库所依赖的库.

  1. ext {
  2. android_const = [
  3. compileSdk : 23,
  4. minSdk : 11,
  5. targetSdk : 23,
  6. support : “23.1.1”,
  7. buildTools : “23.0.2”,
  8. packageName: “com.evilsoulm.keep_nice”
  9. ]
  10. dep_const = [
  11. glide : “3.6.0”,
  12. okio : “1.4.0”,
  13. okhttp : “2.4.0”,
  14. fabric : “2.4.0”,
  15. leakcanary : “1.3.1”,
  16. logansquare: “1.1.0”,
  17. dagger : “2.0.1”,
  18. butterknife: “7.0.1”,
  19. retrofit : “2.0.0-beta2”,
  20. greendao : “2.0.0”
  21. ]
  22. app_dep = [
  23. “appcompat-v7” : ‘com.android.support:appcompat-v7:’ + android_const.support,
  24. “design” : ‘com.android.support:design:’ + android_const.support,
  25. “okio” : ‘com.squareup.okio:okio:’ + dep_const.okio,
  26. “okhttp” : ‘com.squareup.okhttp:okhttp:’ + dep_const.okhttp,
  27. “glide” : ‘com.github.bumptech.glide:glide:’ + dep_const.glide,
  28. “debug_leakcanary” : ‘com.squareup.leakcanary:leakcanary-android:’ + dep_const.leakcanary,
  29. “release_leakcanary”: ‘com.squareup.leakcanary:leakcanary-android-no-op:’ + dep_const.leakcanary,
  30. “glide” : ‘com.github.bumptech.glide:glide:’ + dep_const.glide,
  31. “apt_logansquare” : ‘com.bluelinelabs:logansquare-compiler:’ + dep_const.logansquare,
  32. “logansquare” : ‘com.bluelinelabs:logansquare:’ + dep_const.logansquare,
  33. “apt_dagger” : ‘com.google.dagger:dagger-compiler:’ + dep_const.dagger,
  34. “dagger” : ‘com.google.dagger:dagger:’ + dep_const.dagger,
  35. “butterknife” : ‘com.jakewharton:butterknife:’ + dep_const.butterknife,
  36. “rxjava” : ‘io.reactivex:rxjava:1.0.10’,
  37. “rxandroid” : ‘io.reactivex:rxandroid:0.24.0’,
  38. “retrofit” : ‘com.squareup.retrofit:retrofit:’ + dep_const.retrofit,
  39. “converter-gson” : ‘com.squareup.retrofit:converter-gson:’ + dep_const.retrofit,
  40. “adapter-rxjava” : ‘com.squareup.retrofit:adapter-rxjava:’ + dep_const.retrofit
  41. ]
  42. model_generator_dep = [
  43. “greendao-generator”: ‘de.greenrobot:greendao-generator:’ + dep_const.greendao
  44. ]
  45. model_dep = [
  46. “greendao”: ‘de.greenrobot:greendao:’ + dep_const.greendao,
  47. “android” : ‘com.google.android:android:4.1.1.4’
  48. ]
  49. }

根目录的build.gradle

  1. // Top-level build file where you can add configuration options common to all sub-projects/modules.
  2. apply from: “basegradle/const.gradle” //引入const.gradle 有点继承的感觉 也就是现在rootProject也就拥有const.gradle里的方法和变量
  3. buildscript {
  4. repositories {
  5. mavenCentral()
  6. }
  7. dependencies {
  8. classpath ‘com.android.tools.build:gradle:1.2.3’
  9. }
  10. }
  11. allprojects {
  12. repositories {
  13. mavenCentral()
  14. }
  15. }

使用
//老的版本

  1. apply plugin: ‘java’
  2. dependencies {
  3. compile ‘de.greenrobot:greendao:’ + rootProject.ext.greendao
  4. compile(group: ‘com.google.android’, name: ‘android’, version: ‘4.1.1.4’)
  5. }

改造后 简单清楚

  1. apply plugin: ‘java’
  2. dependencies {
  3. compile rootProject.ext.model_dep.greendao
  4. compile rootProject.ext.model_dep.android
  5. }

查看依赖树

gradle dependencies
  1. task helloShortCut << {
  2. println ‘short cut’
  3. }
  4. //打印0道9
  5. task count << {
  6. 10.times { print(“$it “) }
  7. }
  8. task intro(dependsOn: helloShortCut) << {
  9. print “i am gradle”
  10. }
  11. //动态task
  12. 10.times {
  13. time ->
  14. task “task$time” << {
  15. println ” i am task number $time”
  16. }
  17. }
  18. task8.dependsOn task1, task2, task3, task4
  19. task hello << {
  20. println ‘Hello Earth’
  21. }
  22. hello.doFirst {
  23. println ‘Hello Venus’
  24. }
  25. hello.doLast {
  26. println ‘Hello Mars’
  27. }
  28. hello << {
  29. println ‘Hello Jupiter’
  30. }
  31. task loadFile << {
  32. def files = file(‘../antLoadfileResources’).listFiles().sort();
  33. files.each {
  34. File file ->
  35. if (file.isFile()) {
  36. }
  37. }
  38. }
  39. gradle.taskGraph.whenReady {taskGraph ->
  40. if (taskGraph.hasTask(“assembleSpeedCompilation”)) {
  41. println ‘_____________________________assembleSpeedCompilationDebug’
  42. } else {
  43. println ‘_____________________________–不包含’
  44. }
  45. }
  1. //显示依赖包的存储路径task
  2. showRemoteDependencies << {
  3. configurations.compile.each { println it
  4. }}

demo->github

android studio 导入外部库文件,以及将项目中module变成library引用依赖

android studio 导入外部库文件,以及将项目中module变成library引用依赖

一:导入如百度地图等的外部类。

步骤:1.首先 将androidstudio项目显示切换到 project 状态显示项目

2.然后添加.jar文件,将所有的.jar文件放入libs文件夹内(libs文件夹就在项目文件夹下),然后在引入的.jar文件上右键然后点击 Add As Library… OK jar文件引入。

3.添加.so文件,在项目下的src目录下的main目录下新建jniLibs文件夹,然后将so文件连带着他外面的文件夹整个复制到jniLibs文件夹下(注意:so文件不能直接存在于jniLibs文件夹下,需要存在于如armeabi等文件中放入jniLibs文件夹下),倒入文件后在该文件的build.gradle中添加。(为了保证不出错,可以将.jar文件放入JinLibs将so文件放入libs文件中,使得libs跟jniLibs文件夹下都存在so跟jar。)

sourceSets{
    main(){
        jniLibs.srcDirs = ['libs']
    }
}
代码。具体放入位置如下:

apply plugin: 'com.android.library'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        minSdkVersion 11
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets{
        main(){
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile files('libs/BaiduLBS_Android.jar')
}
现在就可以使用外部类的方法了。

二:将同项目的module作为依赖包引用
1.选择你想作为library的module。选择他的build.gradle文件将*上方的代码apply plugin: 'com.android.application'改为apply plugin: 'com.android.library'。然后将下面的代码删去位置为:android下的defaultConfig下的applicationId "frame.myc.com.mycframe"。删除后代码为
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        minSdkVersion 11
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets{
        main(){
            jniLibs.srcDirs = ['libs']
        }
    }
}
2.为主文件添加依赖 mac下使用以下操作:点击file->project structure左边的module下选择你的主工程,然后右边点击dependencies,点击下方或者右方的+点开后在三个选项中选择module dependency,在弹出界面选择你刚刚修改作为library的midule文件 ok了。
其实*简单的方法就是刚开始建立module的时候就作为library来新建。仔细去新建一个module来体会一下,在选择模式的时候选择library就可以啦


出现错误:当你的程序需要引用两个及以上的module library或者其他的jar包是出现错误类型如下

Error:Execution failed for task ‘:app:transformResourcesWithMergeJavaResForDebug’.
> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK org/apache/log4j/xml/log4j.dtd
File1: /Users/minyuchun/androidwork/projectstudio/StarFaceFrame/app/build/intermediates/exploded-aar/StarFaceFrame/facelibrary/unspecified/jars/classes.jar
File2: /Users/minyuchun/androidwork/projectstudio/StarFaceFrame/app/build/intermediates/exploded-aar/StarFaceFrame/rylibrary/unspecified/jars/classes.jar

出现上述错误的原因是因为 你在引用的labrary中多个存在相同的包导致在打包是冲突 解决方式如下,在android下的 写
packagingOptions{
  exclude 'org/apache/log4j/xml/log4j.dtd'
}
''单引号中的内容为上述错误中APK后面的内容,按照上述的样式填写在 主的app.gradle 中,重新编译后运行,运行后还可能出现相同的错误 这时候请注意错误后面APK中的内容 此时应该与前一次出现的内容不相同。如果是这样的话继续按照上述的方式增加,循环*后就没有这个错误了。

%title插图%num 

 

Android开发各类常见错误解决方案

本文属于个人平时项目开发过程遇到的一些问题,记录下来并总结解决方案,希望能帮到大家解决问题,有些问题的解决方案是在StackoverFlow上找到的,建议大家遇到问题多去上面找,基本上都能找到解决方案的。

(1)将Eclipse项目导入到Android studio 中 很多点9图出现问题解决方法: 在build.gradle里添加以下两句:

com.android.bui" data-snippet-id="ext.fe2015d4a16367e45ed83e2595c5311a" data-snippet-saved="false" data-codota-status="done">Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'. > com.android.bui
  • 解决方法:
    在build.grade中添加以下代码:
  1. android{
  2. packagingOptions {
  3. exclude ‘META-INF/DEPENDENCIES.txt’
  4. exclude ‘META-INF/NOTICE’
  5. exclude ‘META-INF/NOTICE.txt’
  6. exclude ‘META-INF/LICENSE’
  7. exclude ‘META-INF/LICENSE.txt’
  8. }
  9. }

(4)未知错误

  1. Error:Timeout waiting to lock cp_proj class cache for build file ‘/Users/Mr.xiao/Desktop/AndroidShopNC2014MoblieNew/androidShopNC2014Moblie/build.gradle’
  2. (/Users/Mr.xiao/.gradle/caches/2.10/scripts/build_3cyr7hzjurcc62ge3ixidshos/cp_proj).
  3. It is currently in use by another Gradle instance.
  4. Owner PID: unknown
  5. Our PID: 1412
  6. Owner Operation: unknown
  7. Our operation: Initialize cache
  8. Lock file: /Users/Mr.xiao/.gradle/caches/2.10/scripts/build_3cyr7hzjurcc62ge3ixidshos/cp_proj/cache.properties.lock
  • 解决方案
    以上是错误提示。
    解决的思路很简单只需要把cache.properties.lock文件删除了就可以了。当时我们删除的时候会被占用这时候需要进入任务管理器结束关于java的进程就行比如 java 的jdk 删除后重启让java jdk启动 启动Android Studio就能启动APK了。

(5)修改了Android项目的*小SDK版本之后出现很多stysle文件找不到

  • 解决方案
  1. compileSdkVersion 23
  2. buildToolsVersion “23.0.3”
  3. defaultConfig {
  4. applicationId “net.mmloo2014.android”
  5. minSdkVersion 14
  6. targetSdkVersion 23
  7. }

compileSdkVersion 是多少版本的

那么compile ‘com.android.support:appcompat-v7:23.2.1’ 就是啥版本的。

(6)Android studio 编译问题:finished with non-zero exit value 2

  • 问题:
  1. Error:Execution failed for task ‘:androidShopNC2014Moblie:transformClassesWithDexForDebug’.
  2. >
  3. com.android.build.api.transform.TransformException:
  4. com.android.ide.common.process.ProcessException:
  5. java.util.concurrent.ExecutionException:
  6. com.android.ide.common.process.ProcessException:
  7. org.gradle.process.internal.ExecException:
  8. Process ‘command ‘/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/java finished with non-zero exit value 2
  • 解决方案
    这个错误在app的build.gradle里面添加下面这句就好了。
  1. android {
  2. defaultConfig {
  3. multiDexEnabled true
  4. }
  5. }

(7)Android studio 编译问题:finished with non-zero exit value 1(由于导入的依赖出现重复造成的)

  • 问题:
  1. Error:Execution failed for task ‘:app:transformClassesWithDexForDebug’.
  2. > com.[Android](http://lib.csdn.net/base/15).build.api.transform.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process ‘command ‘F:\Program Files (x86)\[Java](http://lib.csdn.net/base/17)\jdk1.8.0_31\bin\java.exe” finished with non-zero exit value 1
  • 解决方案
    这个是因为依赖包重复了 (像v4和nineoldandroids),app中实现了对easeUI的依赖,但是app和easeUI都添加了对这个包的依赖。所以就报这个错误,修改之后再报,就clean,rebuild一下。

(8)问题

  1. Error:Execution failed for task
  2. ‘:app:transformClassesWithJarMergingForDebug’.>
  3. com.android.build.api.transform.TransformException:
  4. java.util.zip.ZipException:
  5. duplicate entry: org/apache/http/ConnectionClosedException.class
  • 解决方案
    这个是在我们启动的时候报错的,而不是在编译的时候,原因是这样的,报这个错是因为有2个库中存在相同的类。大家可以看到stackoverflow上有人也提了这样的问题。只需要删除其中的一个就可以解决了。

(9)添加第三方依赖出现的问题

  1. Error:Execution failed for task ‘:app:processDebugManifest’.
  2. >
  3. Manifest merger failed :
  4. uses-sdk:minSdkVersion 14 cannot be smaller than version 19 declared in library [com.github.meikoz:basic:2.0.3]
  5. /AndroidStudioCode/EnjoyLife/app/build/intermediates/exploded-aar/
  6. com.github.meikoz/basic/2.0.3/AndroidManifest.xml
  7. Suggestion: use tools:overrideLibrary=“com.android.core” to force usage
  • 错误原因
    出现这个错误的原因是我引入的第三方库*低支持版本高于我的项目的*低支持版本,异常中的信息显示:我的项目的*低支持版本为14,而第三方库的*低支持版本为19,所以抛出了这个异常。
  • 解决方案
    在AndroidManifest.xml文件中标签中添加
  1. <uses-sdk tools:overrideLibrary=“xxx.xxx.xxx”/>

其中的xxx.xxx.xxx为第三方库包名,如果存在多个库有此异常,则用逗号分割它们,例如:

<uses-sdk tools:overrideLibrary="xxx.xxx.aaa, xxx.xxx.bbb"/>

这样做是为了项目中的AndroidManifest.xml和第三方库的AndroidManifest.xml合并时可以忽略*低版本限制。

(10)Android studio 编译问题:finished with non-zero exit value 1(由于buildtools版本太高造成的)

  • 错误
  1. Error:Execution failed for task ‘:app:transformClassesWithDexForDebug’.
  2. > com.android.ide.common.process.ProcessException:
  3. org.gradle.process.internal.ExecException:
  4. Process ‘command ‘/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/java finished with non-zero exit value 1
  • 错误原因
    buildToolsVersion版本太高,我原来的 buildToolsVersion “24.0.0” 需要jdk1.8,而我的是jdk1.7,所以一直报这个错,刚开始以为是v4包和V7包冲突,因为之前遇到这样的问题,而这次删除V4包之后依然报这个错,上stackoverflow搜了一下,把buildTools版本降下来就好了。
  • 解决方案
  1. android {
  2. compileSdkVersion 23
  3. buildToolsVersion “23.0.3”
  4. }

(11)Android studio 编译问题:Gradle DSL not found ‘android()’

  • 问题
clipboard.png

clipboard.png
  • 解决方案
  • 配置build.gradle:
  1. buildscript {
  2. repositories {
  3. jcenter()
  4. }
  5. dependencies {
  6. classpath ‘com.android.tools.build:gradle:2.1.2’
  7. }
  8. }
  9. allprojects {
  10. repositories {
  11. jcenter()
  12. }
  13. }
  14. buildscript {
  15. repositories {
  16. jcenter()
  17. }
  18. dependencies {
  19. classpath ‘com.android.tools.build:gradle:2.1.2’
  20. }
  21. }
  22. allprojects {
  23. repositories {
  24. jcenter()
  25. }
  26. }
  • 配置app/build.gradle:
  1. apply plugin: ‘com.android.application’android {
  2. compileSdkVersion 23
  3. buildToolsVersion ‘23.0.3’
  4. defaultConfig {
  5. minSdkVersion 9
  6. targetSdkVersion 23
  7. versionCode 1
  8. versionName ‘1.0’
  9. }
  10. }
  11. dependencies {
  12. compile ‘com.android.support:appcompat-v7:23.2.1’
  13. }

*后再同步一下sync即可。

(12)Android studio 编译问题:Gradle DSL not found ‘android()’

  • 问题描述
  1. Error:(51, 52) 错误: -source 1.6 中不支持 diamond 运算符
  2. (请使用 -source 7 或更高版本以启用 diamond 运算符)
  • 解决方案
  • 方案一
将标红处设置为1.7.png

将标红处设置为1.7.png
修改soure为1.7.png

修改soure为1.7.png
  • 方案二
    在build gradle中进行配置如下代码:
  1. android {
  2. compileOptions {
  3. sourceCompatibility JavaVersion.VERSION_1_7
  4. targetCompatibility JavaVersion.VERSION_1_7
  5. }
  6. }

*后同步一下即可

(13)Glide使用问题:使用Glide加载圆角图片,*次显示占位图

  • 问题描述
    *近在项目中使用Glide加载圆形图片,并且设置placehloder和error两个占位图,运行发现,*次加载图片只显示占位图,需要第二次进入的时候才会正常显示。
    如果你刚好使用了这个圆形Imageview库或者其他的一些自定义的圆形Imageview,而你又刚好设置了占位的话,那么,你就会遇到*个问题。如何解决呢?
  • 方案一
    不设置占位图
  • 方案二
    使用Glide的Transformation API自定义圆形Bitmap的转换
  1. /**
  2. * Glide圆形图片处理
  3. */
  4. static class CircleTransform extends BitmapTransformation {
  5. public CircleTransform(Context context) {
  6. super(context);
  7. }
  8. @Override
  9. protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
  10. return circleCrop(pool, toTransform);
  11. }
  12. private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
  13. if (source == null) return null;
  14. int size = Math.min(source.getWidth(), source.getHeight());
  15. int x = (source.getWidth() – size) / 2;
  16. int y = (source.getHeight() – size) / 2;
  17. Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);
  18. Bitmap result = pool.get(size, size, Bitmap.Config.RGB_565);
  19. if (result == null) {
  20. result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
  21. }
  22. Canvas canvas = new Canvas(result);
  23. Paint paint = new Paint();
  24. paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
  25. paint.setAntiAlias(true);
  26. float r = size / 2f;
  27. canvas.drawCircle(r, r, r, paint);
  28. return result;
  29. }
  30. @Override
  31. public String getId() {
  32. return getClass().getName();
  33. }
  34. }

使用方法:

 Glide.with(context).load(imageUrl).placeholder(placeholder).error(errorImage).transform(new CircleTransform(context)).into(imageView);

方案三
重写Glide的图片加载监听方法,具体如下:

  1. Glide.with(mContext)
  2. .load(url)
  3. .placeholder(R.drawable.loading_drawable)
  4. .into(new SimpleTarget<Bitmap>(width, height) {
  5. @Override public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
  6. // setImageBitmap(bitmap) on CircleImageView
  7. }
  8. });

注意事项:
该方法在listview上复用有问题的bug,如果在listview中加载CircleImageView,请不要使用该方法。

方案四:不使用Glide的默认动画:

  1. Glide.with(mContext)
  2. .load(url)
  3. .dontAnimate()
  4. .placeholder(R.drawable.loading_drawable)
  5. .into(circleImageview);

(14)json数据解析问题:json串头部出现字符:”\ufeff” 解决方法
异常信息

org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONObject

解析服务器返回 的json格式数据时,我们可能会发现,数据格式上是没有问题的,但是仔细对比会发现,在json串头部发现字符:”\ufeff”

客户端解决方案:

  1. /**
  2. * 异常信息:org.json.JSONException: Value of type java.lang.String cannot be converted to JSONObject
  3. * json串头部出现字符:”\ufeff” 解决方法
  4. * @param data
  5. * @return
  6. */
  7. public static final String removeBOM(String data) {
  8. if (TextUtils.isEmpty(data)) {
  9. return data;
  10. }
  11. if (data.startsWith(“\ufeff”)) {
  12. return data.substring(1);
  13. }
  14. else {
  15. return data;
  16. }
  17. }

服务器端解决方案:
将输出此json的php源码重新用editplus之类用utf-8无BOM的编码保存。不要用windows系统自带的记事本编辑php源码,这个BOM就是记事本这些windows自带的编辑器引入的。

(15)Android studio编译问题:not found ndk()
问题

Error:(15, 0) Gradle DSL method not found: 'ndk()' method-not-found-ndk

解决方案
出现该问题,可能是由于ndk配置在build.gradle配置文件中位置弄错导致的

  1. apply plugin: ‘com.android.application’
  2. android {
  3. compileSdkVersion 23
  4. buildToolsVersion “23.0.2”
  5. defaultConfig {
  6. applicationId “com.guitarv.www.ndktest”
  7. minSdkVersion 17
  8. targetSdkVersion 23
  9. versionCode 1
  10. versionName “1.0”
  11. ndk {
  12. moduleName = “HelloJNI”
  13. }
  14. sourceSets.main {
  15. jni.srcDirs = []
  16. jniLibs.srcDir “src/main/libs”
  17. }
  18. }
  19. buildTypes {
  20. release {
  21. minifyEnabled false
  22. proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  23. }
  24. }
  25. }

(16)Android studio导入其他的项目:UnsupportedMethodException
问题

  1. UnsupportedMethodException
  2. Unsupported method: AndroidProject.getPluginGeneration().
  3. The version of Gradle you connect to does not support that method.
  4. To resolve the problem you can change/upgrade the target version of Gradle you connect to.
  5. Alternatively, you can ignore this exception and read other information from the model.
错误截图

错误截图

解决方案

将根目录中的build.gradle文件中的gradle版本号,出现错误之前,我的是1.3.0,修改成2.2.0之后重新编译一下就可以运行了。

  1. dependencies {
  2. classpath ‘com.android.tools.build:gradle:1.3.0’
  3. }

将这个版本号改成你其他项目能够运行成功的版本号即可

(17)Android studio更新到2.1.1之后使用CollapsingToolbarLayout出现Error inflating class CollapsingToolbarLayout
之前在项目中使用了CollapsingToolbarLayout,效果还是可以的,但是Android stuido更新到2.1.1版本之后出现Error inflating class CollapsingToolbarLayout 异常崩溃
异常信息如下所示:

  1. com.test.android/com.test.android.ui.activity.RandomActivity}: android.view.InflateException: Binary XML file line #22: Error inflating class android.support.design.widget.CollapsingToolbarLayout
  2. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
  3. at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
  4. at android.app.ActivityThread.access$800(ActivityThread.java:151)
  5. at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
  6. at android.os.Handler.dispatchMessage(Handler.java:102)
  7. at android.os.Looper.loop(Looper.java:135)
  8. at android.app.ActivityThread.main(ActivityThread.java:5254)
  9. at java.lang.reflect.Method.invoke(Native Method)
  10. at java.lang.reflect.Method.invoke(Method.java:372)
  11. at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  12. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
  13. Caused by: android.view.InflateException: Binary XML file line #22: Error inflating class android.support.design.widget.CollapsingToolbarLayout
  14. at android.view.LayoutInflater.createView(LayoutInflater.java:633)
  15. at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:743)
  16. at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
  17. at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
  18. at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
  19. at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
  20. at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
  21. at android.view.LayoutInflater.inflate(LayoutInflater.java:365)
  22. at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:276)
  23. at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:136)
  24. at com.test.android.ui.activity.RefreshableActivity.onCreate(RefreshableActivity.java:31)
  25. at android.app.Activity.performCreate(Activity.java:5990)
  26. at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
  27. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
  28. at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
  29. at android.app.ActivityThread.access$800(ActivityThread.java:151)
  30. at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
  31. at android.os.Handler.dispatchMessage(Handler.java:102)
  32. at android.os.Looper.loop(Looper.java:135)
  33. at android.app.ActivityThread.main(ActivityThread.java:5254)
  34. at java.lang.reflect.Method.invoke(Native Method)
  35. at java.lang.reflect.Method.invoke(Method.java:372)
  36. at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  37. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
  38. Caused by: java.lang.NoSuchMethodError: No static method setLayoutDirection(Landroid/graphics/drawable/Drawable;I)V in class Landroid/support/v4/graphics/drawable/DrawableCompat; or its super classes (declaration of ‘android.support.v4.graphics.drawable.DrawableCompat’ appears in /data/app/com.test.android-1/base.apk)
  39. at android.support.design.widget.CollapsingToolbarLayout.setStatusBarScrim(CollapsingToolbarLayout.java:663)
  40. at android.support.design.widget.CollapsingToolbarLayout.<init>(CollapsingToolbarLayout.java:197)
  41. at android.support.design.widget.CollapsingToolbarLayout.<init>(CollapsingToolbarLayout.java:132)
  42. at java.lang.reflect.Constructor.newInstance(Native Method)
  43. at java.lang.reflect.Constructor.newInstance(Constructor.java:288)
  44. at android.view.LayoutInflater.createView(LayoutInflater.java:607)
  45. at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:743)
  46. at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
  47. at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
  48. at android.view.LayoutInflater.rInflate(LayoutInflater.java:809)
  49. at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
  50. at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
  51. at android.view.LayoutInflater.inflate(LayoutInflater.java:365)
  52. at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:276)
  53. at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:136)
  54. at com.test.android.ui.activity.RefreshableActivity.onCreate(RefreshableActivity.java:31)
  55. at android.app.Activity.performCreate(Activity.java:5990)
  56. at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
  57. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
  58. at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
  59. at android.app.ActivityThread.access$800(ActivityThread.java:151)
  60. at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
  61. at android.os.Handler.dispatchMessage(Handler.java:102)
  62. at android.os.Looper.loop(Looper.java:135)
  63. at android.app.ActivityThread.main(ActivityThread.java:5254)
  64. at java.lang.reflect.Method.invoke(Native Method)
  65. at java.lang.reflect.Method.invoke(Method.java:372)

解决方案
在项目的build.gradle文件中添加下面一行,同步一下即可

  1. compile (‘com.android.support:support-v4:23.4.0’){
  2. force = true;
  3. }

StackOverFlow解决方案

(18)Android studio gradle编译异常

java.lang.UnsupportedClassVersionError: com/android/build/gradle/AppPlugin : Unsupported major.minor version 52.0

很显然是class版本不支持。经查询,Android Studio2.2必须使用JDK8及以上版本,而且是强制的。
所以呢,赶紧下了个JDK8*新版的。安装完毕,把JAVA_HOME指向了JDK8,实测JDK7和8是可以共存的。
那么,重启Android Studio后问题解决,Build Successful !

(19)电脑突然断电,Android studio 工程代码全部报错,找不到android sdk 的依赖包,clean、重启都没有用
前几天公司搬家,正准备同步代码,突然断电、等把电脑搬到新办公楼,打开AS发现所有的项目代码报错,找不到android 依赖包,clean、重启都没有用,

(20)recycleview嵌套列表项显示不全问题
解决方案:

*个RecyclerView的Adapter(即父RecyclerView):

  1. @Override
  2. public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  3. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.shop_item,null); 解决条目显示不全
  4. MyHolder holder = new MyHolder(view);
  5. return holder;
  6. }

第二个RecyclerView的Adapter(即子RecyclerView):

  1. @Override
  2. public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  3. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.check_item, parent,false);//解决宽度不能铺满
  4. MyHolder holder = new MyHolder(view);
  5. return holder;
  6. }

(21)Android手机真机调试,日志不打印的解决方案:

  1. 1、在拨号界面输入:*#*#2846579#*#* 进入测试菜单界面。
  2. 2、Project Menu–后台设置–LOG设置
  3. 3LOG开关–LOG打开 LOG级别设置–VERBOSE
  4. 4、Dump&Log– 全部选中
  5. 5、重启手机

(22)java.lang.IndexOutOfBoundsException Inconsistency detected. Invalid item position 2(offset:2).state:4
解决方案:
Recyclerview在下拉刷新时,如果在数据没更新到之前将list clear 之后,迅速滑动会造成crash,所以一般在下拉刷新之前,等数据刷新回来再把之前的数据进行清除。

(23)**使用友盟分享——微信、朋友圈分享出现java.lang.NoClassDefFoundError: org.apache.http.entity.mime.MultipartEntity
**
解决方案: 造成这样的原因是因为缺少httpmime_jar,添加是httpmime_jar包之后即可正常分享

(24)Fragment中调用getActivity()出现空指针异常
解决方案:

  1. 对于上面的问题,可以考虑下面这两种解决办法:
  2. 1、不保存fragment的状态:在MyActivity中重写onSaveInstanceState方法,将super.onSaveInstanceState(outState);注释掉,让其不再保存Fragment的状态,达到fragment随MyActivity一起销毁的目的。
  3. 2、重建时清除已经保存的fragment的状态:在恢复Fragment之前把Bundle里面的fragment状态数据给清除。方法如下:
  4. if(savedInstanceState!= null)
  5. {
  6. String FRAGMENTS_TAG = “android:support:fragments”;
  7. savedInstanceState.remove(FRAGMENTS_TAG);
  8. }

(25)RecyclerView嵌套使用切换页面出现自动滚动问题
原因:

造成这样的原因是由于子RecyclerView抢占焦点导致的,如果你去查看RecyclerView的源码会发现,它会在构造方法中调用setFocusableInTouchMode(true),所以,设为false可以解决这个问题。
解决方案
在子RecyclerView中调用如下方法

  1. //设置焦点不需要
  2. secondRvList.setFocusableInTouchMode(false);
  3. secondRvList.requestFocus();

(26)Android 7.0设备拍照闪退问题
原因:

Android 7.0 做了一些系统权限更改,为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问,此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。而此权限更改有多重副作用,其中之一就是当传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。在应用间共享文件对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的*简单方式是使用 FileProvider 类。点击查看Android官方说明
解决方案
1.在清单文件添加如下代码

  1. <provider
  2. android:name=“android.support.v4.content.FileProvider”
  3. android:authorities=“你的应用包名.fileProvider”
  4. android:exported=“false”
  5. android:grantUriPermissions=“true”>
  6. <meta-data
  7. android:name=“android.support.FILE_PROVIDER_PATHS”
  8. android:resource=“@xml/provider_paths”/>
  9. </provider>
android:authorities="com.alex.demo.FileProvider" 自定义的权限  
android:exported="false" 是否设置为独立进程  
android:grantUriPermissions="true" 是否拥有共享文件的临时权限  
android:resource="@xml/external_storage_root" 共享文件的文件根目录,名字可以自定义  

2.在xml文件夹目录下新建provider_paths文件,名字自定义,添加如下代码

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <resources>
  3. <paths>
  4. <external-path
  5. name=“camera_photos”
  6. path=“” />
  7. </paths>
  8. </resources>

3.调用系统相机处代码处理

  1. //调用系统相机拍照
  2. Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  3. if (cameraIntent.resolveActivity(getActivity().getPackageManager()) != null) {
  4. cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, parUri(tempFile));
  5. startActivityForResult(cameraIntent, REQUEST_CAMERA);
  6. }
  7. /**
  8. * 生成uri
  9. *
  10. * @param cameraFile
  11. * @return
  12. */
  13. private Uri parUri(File cameraFile) {
  14. Uri imageUri;
  15. String authority = getContext().getPackageName()+ “.provider”;
  16. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  17. //通过FileProvider创建一个content类型的Uri
  18. imageUri = FileProvider.getUriForFile(getContext(), authority, cameraFile);
  19. } else {
  20. imageUri = Uri.fromFile(cameraFile);
  21. }
  22. return imageUri;
  23. }