Android学习笔记
本文于 2028 天之前发表,文中内容可能已经过时。
1. ViewStub的应用
在不显示布局时我们会使用 View.GONE 和 View.VISIBLE 属性去控制View的布局显示,但是在该使用过程中,该View的对象还是会被建立(只要通过inflate就会创建对象),被实例化,所以会耗费内存。
这时候就会引入ViewStub这个控件,该控件是一个轻量级的View,它是一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。
其原理就是通过控制ViewStub来控制View的对象的创建和渲染
特点:
- ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
- ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。
因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。
所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。ViewStub是能被使用一次。一旦被指定infate后,其存在的特性就会消失。
2. SurfaveView控件的理解
3. AOSP(Android OPen Source Project)Android开源工程
4.内存泄露:
static所修饰的方法和变量拥有和app一样长的生命周期
- 静态变量导致的内存泄漏:
静态变量(方法)中对context的使用,导致Activity不能被回收 - 单例模式导致的内存泄露:
单例中同样使用了static方法,对context的使用不能被释放
上述两种的解决方法都为传入context.getApplicationContext(),其存在的生命周期和app生命周期一样长。
- 非静态内部类持有外部引用导致的内存泄漏:(非静态内部类和外部类相互绑定,而静态内部类与外部类相互分离)
内部类持有外部类的引用,外部类无法正常回收(将其改为静态内部类)
Handler引起的内存泄漏:- 静态内部类+弱引用:
static + WeakReference<>() - Handler.Callback的方式
- 静态内部类+弱引用:
1 | Handler mHandler = new Handler(new Handler.Callback() { |
- 流文件打开后要close(),动画要cancel(),数据库的使用要关闭
- Listview的优化,ViewHolder对item的复用
- Bitmap的三级缓存和压缩技术
5. Android进阶——Android消息机制之Looper、Handler、MessageQueen
1. Handler涉及哪些类,各自功能是什么?
- Handler:将Message对象发送到MessageQueue中去,同时将自己的引用赋值给Message#target.
- Lopper:将Message对象从MessageQueue中取出来,并交给Handler#dispatchMessage(Message)方法,不是调用Handler#handleMessage(Message)方法
- MessageQueue:负责插入和取出Message
- Message:所传递的信息的载体
- ThreadLocal:
2. 发送消息的方式
1 | //常用 |
3. MessageQueue中的Message是有序的吗?根据什么排序?
- 是有序的,根据Message#when排序的
- Message#when是一个时间,用于表示Message期望被分发的时间,该值是SystemClock#uptimeMillis()与delayMillis之和
- 因为System.currentTimemillis()可以被修改,所以不用此表示
4. 子线程可以创建Handler对象吗?
不能直接调用Handler的无参构造方法
- 先要调用Looper.prepare()在当前线程初始化一个Looper
- 通过构造方法传入一个Looper
- 主线程调用Handler的无参构造会存在一个自动绑定的过程
5. Looper是如何与Thread关联的?
通过ThreadLocal关联的
6.Handler有哪些构造方法
1 | public Handler() { |
7. 子线程如何获取当前线程的Looper
1 | Looper.myLooper() |
内部原理就是同过上面提到的sThreadLocal#get()来获取Looper
1 | // Looper.java:203 |
8. 如何在任意先成获取主线程的Looper
1 | Looper.getMainLooper() |
9. 如何判断当前线程是不是主线程
方法一:
1 | Looper.myLooper() == Looper.getMainLooper() |
方法二:
1 | Looper.getMainLooper().getThread() == Thread.currentThread() |
方法三: 方法二的简化版
1 | Looper.getMainLooper().isCurrentThread() |
10. Looper.loop()会退出吗?
不会自动推出,通过调用Looper#quit()或者Looper#quitSafely()让他退出
两个方法都是调用了 MessageQueue#quit(boolean) 方法,当 MessageQueue#next() 方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null 结束当前调用,否则的话即使 MessageQueue 已经是空的了也会阻塞等待。
11. Looper.loop() 方法是一个死循环为什么不会阻塞APP
6. Http协议
- HTTP 0.9 这个版本只有GET方法
- 1.0 这个版本有GET HEAD POST这三个方法
- HTTP 1.1 这个版本是当前版本,包含GET HEAD POST OPTIONS PUT DELETE TRACE CONNECT这8个方法
- 报文:HTTP应用程序之间发送的数据块(起始行,首部,主体)
Http请求所经历的过程
建立tcp连接(三次握手) >> 客户端向服务端发送请求命令 >> 客户端发送请求头信息 >> 服务器应答 >> 服务器应答头信息 >> 服务器向客户端发送数据 >> 服务器关闭tcp连接
- 请求方法(Method):GET和POST、 不常用的有PUT,DELETE、HEAD、options
HEAD 与 GET 的使用方式完全相同。
区别在于,HEAD 请求的返回响应中没有 Body
用途:比如下载需求,返回的 Headers 中有下载内容的大小,可以用于显示进度。
GET、PUT、DELETE 都是幂等操作,POST不是幂等的,不安全
PUT、DELETE除第一种方法外,只能通过先在服务端重写HTTP请求方法(自定义HttpMessageHandler来实现),然后再在客户端请求报文头指定“X-HTTP-Method-Override”值为PUT或DELETE来实现;
PUT 请求,客户端方法与POST方法相同,只是TYPE指定为:PUT;服务器端与POST方法相同;
DELETE请求,客户端方法与GET方法相同,只是TYPE指定为:DELETE;服务器端与GET方法相
PUT和POST的区别
1、PUT请求时,如果用相同参数访问二次接口,Post 仅会产生一条记录
使用场合例如:
用户的账户二维码只和用户关联,而且是一一对应的关系,此时这个api就可以用PUT
2、POST请求时,如果用相同参数访问二次接口,Post 会产生多条记录
使用场合例如:
在我们的支付系统中,一个api的功能是创建收款金额二维码,它和金额相关,每个用户可以有多个二维码,如果连续调用则会创建新的二维码,这个时候就用POST
Get请求添加body请求吗?
在规定 HTTP 语义及内容的 RFC 7231 中,并未限制 GET 请求中是否允许携带交互数据!所以,有些 HTTP 服务允许这种行为,而另外一些(特别是缓存代理)则不允许这种行为。
Apache Http Client 和 OkHttpClient 都不支持 GET 请求发送 Body 数据,而 AsyncHttpClient 是可以的。
所以一般情况下,get请求不使用body进行数据传输,一般是拼接到url中实现
options的主要用途
OPTIONS请求方法的主要用途有两个:
1、获取服务器支持的HTTP请求方法;也是黑客经常使用的方法。
2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
TRACE
TRACE方法被用于激发一个远程的,应用层的请求消息回路(译注:TRACE方法让客户端测试到服务器的网络通路,回路的意思如发送一个请返回一个响应,这就是一个请求响应回路,)。最后的接收者或者是接收请求里Max-Forwards头域值为0源服务器或者是代理服务器或者是网关。TRACE请求不能包含一个实体。
TRACE方法允许客户端知道请求链的另一端接收什么,并且利用那些数据去测试或诊断。Via头域值(见14.45)有特殊的用途,因为它可以作为请求链的跟踪信息。利用Max-Forwards头域允许客户端限制请求链的长度去测试一串代理服务器是否在无限回路里转发消息。
如果请求是有效的,响应应该在响应实体主体里包含整个请求消息,并且响应应该包含一个Content-Type头域值为”message/http”的头域。TRACE方法的响应不能不缓存。
CONNECT(连接)
HTTP1.1协议规范保留了CONNECT方法,此方法是为了能用于能动态切换到隧道的代理服务器(proxy,译注:可以为代理,也可以是代理服务器)。
- 状态码:
1xx:临时性消息
1 | 100:继续发送 |
2xx:成功
1 | 200:OK (最常见) |
3xx:重定向
1 | 301:域名永久移动 |
4xx:客户端错误
1 | 400:客户端请求错误,服务器不理解请求的语法。 |
5xx:服务器错误
1 | 500:服务器内部错误 (最常见) |
http协议osi七层协议和TCP/IP五层协议
HTTP使用80端口
HTTPS使用443端口
7.Https协议
协议过程,中间人攻击
缺点:
证书费用高
访问速度慢
Hypertext Transfer Protocol Secure 缩写:HTTPS
Https经Http进行通信,但利用SSL/TLS来加密数据包
TLS/SSL协议主要依赖于三类基本算法:散列函数Hash、对称加密和非对称加密。
非对称加密:身份认证和密钥协商(RSA)
对称加密:对数据进行加密(AES)
散列函数加密:验证信息的完整性(MD5,SHA1,SHA256)
CA证书验证机制,解决中间人攻击的方式
https的SSL/TSL密钥是在应用层和传输层之间的
- 申请证书不需要提供私钥,确保私钥永远只能服务器掌握
- 证书的合法性仍然依赖于非对称加密算法,证书主要是增加了服务器信息以及签名
- 内置CA对应的证书称为根证书,颁发者和使用者相同,自己为自己签名,即自签名证书
- 证书=公钥+申请者与颁发者信息+签名
- 公钥放在数字证书中。只有证书是可信的,公钥就是可信的。
- 服务器证书server.pem的签发者为中间证书机构inter,inter根据证书inter.pem验证server.pem确实为自己签发的有效证书
- 中间证书inter.pem的签发CA为root,root根据root.pem验证inter.pem为自己签发的合法证书
- 客户端内置新人CA的root.pem证书,因此服务器证书server.pem的被信任
https的加密过程
8.Gradle
1 | Ant: 长江后浪推前浪,前浪已经over了 |
Gradle:是一个基于Groovy语言的开源项目自动化构建工具
Groovy: 基于java虚拟机的动态语言、面向对象/脚本,完全兼容Java语法
9. ClassLoader
插件化技术的核心
- 作用:
Android虚拟机运行的Dex字节码(将Class文件合并优化生成的产物),ClassLoader用来加载dex文件。 - 委托双亲机制:顶层(父类)无法加载这个类,则由由自己加载,如果还加载不到,则会报ClassNotFound错误。(可以用爸爸的钱就绝对不用自己的钱,如果爸爸没有钱,再用自己的, 如果自己还是没有钱,那么就classnotfound异常)
10. Android原生与js交互
一、js调用Android方法
方法一:通过 Webview 的 addJavascriptInterface() 进行对象映射
优点:使用简单,仅将Android对象和JS对象映射即可
缺点:存在漏洞问题
1)允许WebView加载JS
1 | webView.getSettings().setJavaScriptEnabled(true); |
2)编写JS接口
1 | public class JsInterface { |
3)给WebView添加JS接口
1 | webView.addJavascriptInterface(new JsInterface(this), "launcher");// 此处的 launcher 可以自定义,最终是 JS 中要使用的对象 |
4)js代码中调用java方法
1 | if (window.launcher){ // 判断 launcher 对象是否存在 |
方法二:通过 WebViewClient 的 shouldOverrideUrlLoading() 方法回调拦截 url
优点:不存在方式一的漏洞
缺点:JS获取Android方法的返回值复杂
1)JS代码中,约定协议
1 | function callAndroid(){ |
2)Android 代码中,通过设置 WebViewClient 对协议进行拦截处理
1 | webView.setWebViewClient(new WebViewClient() { |
方法三:通过 WebChromeClient 的 onJsAlert() 、 onJsConfirm() 、 onJsPrompt()方法回调拦截 JS 对话框 alert() 、 confirm() 、 prompt() 消息
1)JS代码中,约定协议
1 | // 调用 prompt() |
2)Android 代码中,通过设置 WebChromeClient 对协议进行拦截处理
1 | webView.setWebChromeClient(new WebChromeClient() { |
二、Android调用JS方法
方法一:通过WebView的loadUrl()
1)编写JS方法
1 | var callJS = function(str){ |
2)使用webView.loadUrl()调用JS方法
1 | // Android 调用 JS 方法 |
方法二: 通过 WebView 的 evaluateJavascript()
- 该方法比第一种方法效率更高,使用更简洁;
- 该方法执行不会刷新页面,而第一种方法( loadUrl )则会;
- Android 4.4 以后才能使用。
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
11.RecyclerView缓存机制
缓存优先级(从高到低)
- mAttachedScrap获取
- mCachedViews获取
- mRecyclerPool获取
- 在RecyclerView中,并不是每次绘制表项,都会重新创建ViewHolder对象,也不是每次都会重新绑定ViewHolder数据
- RecyclerView通过Recycler获得下一个待绘制表项
- Recycler有4个层次用于缓存ViewHolder对象、优先级从高到低依次为ArrayList
mAttachedScrap、ArrayList mCachedViews、ViewCacheExtension mViewCacheExtension、RecycledViewPool mRecyclerPool。如果四层缓存都未命中,则重新创建饼绑定ViewHolder对象 - RecyclerViewPool对ViewHolder按viewType分类存储(通过SparseArray),同类ViewHolder存储在默认大小为5的ArrayList中
- 葱mRecyclerPool中复用的ViewHolder需要重新绑定数据,从mAttachedScrap中复用的ViewHolder不需要重新创建也不需要重新绑定数据。
12.性能优化
- 启动速度
- 页面显示速度
优化原因(即 页面显示速度慢的原因) a. 页面需绘制的内容(布局 & 控件)太多,从而导致页面测量时间过长 b. 绘制效率过低,从而导致绘制时间过长
- 降低onDraw()方法的复杂度
- 避免过度绘制(Overdraw)
- 避免嵌套布局,复杂布局,简化布局,使用 < viewStub/> < include/> < merge/>等标签布局
- 布局属性 wrap_content 会增加布局测量时计算成本,应尽可能少用
- 响应速度
优化方案:使用多线程,将大量 & 耗时操作放在工作线程中执行
- 多线程的方式 包括:AsyncTask、继承 Thread类、实现 Runnable接口、Handler消息机制、HandlerThread等
- 注:实际开发中,当一个进程发生了ANR后,系统会在 /data/anr目录下创建一个文件 traces.txt,通过分析该文件可定位出ANR的原因
- 稳定性
- 内存优化
- 优化原因 避免因不正确使用内存 & 缺乏管理,从而出现 内存泄露(ML)、内存溢出(OOM)、内存空间占用过大 等问题,最终导致应用程序崩溃(Crash)
- 内存回收策略
- 内存分配策略
注:用1个实例讲解 内存分配
1 | public class Sample { |
- 内存回收策略
GC垃圾回收机制
- 常见问题
bitmap图片资源
内存抖动
优化方案: 尽量避免频繁创建大量、临时的小对象
代码质量&数量
常见内存问题
总结