我自横刀向天笑
Http 方面的知识平常也会遇到,但是一般了解的也不深,今天记录几个常遇到的知识点。
http 报文可以分为请求报文和响应报文,格式大同小异,主要分为三个部分:
请求报文格式:
1 | <method> <request-url> <version> |
响应报文格式:
1 | <version> <status> <reason-phrase> |
各个标签的解释:
1 | <method> 指请求方法,常用的主要是Get、 Post、Head 还有其他一些我们这里就不说了,有兴趣的可以自己查阅一下 |
常见的状态码主要有
200 OK 请求成功,实体包含请求的资源
301 Moved Permanent 请求的URL被移除了,通常会在Location首部中包含新的URL用于重定向。
304 Not Modified 条件请求进行再验证,资源未改变。
404 Not Found 资源不存在
206 Partial Content 成功执行一个部分请求。这个在用于断点续传时会涉及到。
在请求报文和响应报文中都可以携带一些信息,通过与其他部分配合,能够实现各种强大的功能。这些信息位于起始行之下与请求实体之间,以键值对的形式,称之为首部。每条首部以回车换行符结尾,最后一个首部额外多一个换行,与实体分隔开。
这里我们重点关注一下
Date
Cache-Control
Last-Modified
Etag
Expires
If-Modified-Since
If-None-Match
If-Unmodified-Since
If-Range
If-Match
当我们发起一个http请求后,服务器返回所请求的资源,这时我们可以将该资源的副本存储在本地,这样当再次对该url资源发起请求时,我们能快速的从本地存储设备中获取到该url资源,这就是所谓的缓存。缓存既可以节约不必要的网络带宽,又能迅速对http请求做出响应。
先摆出几个概念:
- 新鲜度检测
- 再验证
- 再验证命中
- 新鲜度检测
我们需要通过检测资源是否超过一定的时间,来判断缓存资源是否新鲜可用。那么这个一定的时间怎么决定呢?其实是由服务器通过在响应报文中增加Cache-Control:max-age
,或是Expire
这两个首部来实现的。值得注意的是 Cache-Control 是 http1.1 的协议规范,通常是接相对的时间,即多少秒以后,需要结合last-modified
这个首部计算出绝对时间。而 Expire 是 http1.0 的规范,后面接一个绝对时间。- 再验证
如果通过新鲜度检测发现需要请求服务器进行再验证,那么我们至少需要告诉服务器,我们已经缓存了一个什么样的资源了,然后服务器来判断这个缓存资源到底是不是与当前的资源一致。逻辑是这样没错。那怎么告诉服务器我当前已经有一个备用的缓存资源了呢?我们可以采用一种称之为条件请求
的方式实现再验证。- Http定义了5个首部用于条件请求:
If-Modified-Since
If-None-Match
If-Unmodified-Since
If-Range
If-Match
简单的说 Http + 加密 + 认证 + 完整性保护 = Https
传统的 Http 协议是一种应用层的传输协议,Http 直接与 TCP 协议通信,其本身存在一些缺点:
因此,在一些需要保证安全性的场景下,Http 无法抵御这些攻击。
Https 则可以听过增加的 SSL\TLS,支持对于通信内容的加密,以及通信双飞的身份验证。
近代密码学中加密的方式主要有两类:
对称秘钥加密是指加密与解密过程使用同一把秘钥。这种方式的优点是处理速度快,但是如何安全的从一方将秘钥传递到通信的另一方是一个问题。
非对称秘钥加密是指加密与解密使用两把不同的秘钥。这两把秘钥,一把叫公开秘钥,可以随意对外公开。一把叫私有秘钥,只用于本身持有。得到公开秘钥的客户端可以使用公开秘钥对传输内容进行加密,而只有私有秘钥持有者本身可以对公开秘钥加密的内容进行解密。这种方式克服了秘钥交换的问题,但是相对于对称秘钥加密的方式,处理速度较慢。
SSL\TLS的加密方式则是结合了两种加密方式的优点。首先采用非对称秘钥加密,将一个对称秘钥使用公开秘钥加密后传输到对方。对方使用私有秘钥解密,得到传输的对称秘钥。之后双方再使用对称秘钥进行通信。这样即解决了对称秘钥加密的秘钥传输问题,又利用了对称秘钥的高效率来进行通信内容的加密与解密。
SSL\TLS采用的混合加密的方式还是存在一个问题,即怎么样确保用于加密的公开秘钥确实是所期望的服务器所分发的呢?也许在收到公开秘钥时,这个公开秘钥已经被别人篡改了。因此,我们还需要对这个秘钥进行认证的能力,以确保我们通信的对方是我们所期望的对象。
目前的做法是使用由数字证书认证机构颁发的公开秘钥证书。服务器的运营人员可以向认证机构提出公开秘钥申请。认证机构在审核之后,会将公开秘钥与共钥证书绑定。服务器就可以将这个共钥证书下发给客户端,客户端在收到证书后,使用认证机构的公开秘钥进行验证。一旦验证成功,即可知道这个秘钥是可以信任的秘钥。
IP(Internet Protocol)协议提供了主机和主机间的通信。
为了完成不同主机的通信,我们需要某种方式来唯一标识一台主机,这个标识就是著名的 IP 地址。通过 IP 地址,IP 协议就能帮我们把一个数据包发送给对方。
上面说到,IP 协议提供了主机与主机间的通信。TCP 协议在 IP 协议提供的主机间通信功能的基础上,完成了两个主机上进程对进程的通信。
有了 IP,不同主机可以进行通信。但是计算机收到数据后,并不知道数据属于哪个进程(简单讲,进程就是一个正在运行的应用程序)。TCP的作用就在于,让我们知道这个数据属于哪个进程,从而完成进程间的通信。
为了标识数据属于哪个进程,我们给需要进行 TCP 通信的进程分配一个唯一的数字来标识它。这个数字就是我们常说的端口号。
TCP 的全称是 Transmission Control Protocol,大家对它说的最多的,大概是面向连接的特性了。之所以说它是有连接的,是说进行通信前,通信双方需要先经过一个三次握手的过程。三次握手完成后,连接便建立了。这时候我们才可以接受/发送数据。(与之对应的 UDP,不需要经过握手,就可以直接发送数据)。
三次握手过程:
SYN
,假设此时 sequence number 为 x
。这个 x
是由操作系统根据一定的规则生成的,不妨认为它是一个随机数。SYN
后,会向客户端再发送一个 SYN
,此时服务器的 seq number = y
。与此同时,会 ACK x+1
,告诉客户端“已经收到了 SYN
,可以发送数据了”。SYN
后,回复一个 ACK y+1
,这个 ACK
则是告诉服务器,SYN
已经收到,服务器可以发送数据了。经过这 3 步,TCP 连接就建立了。这里需要注意的有三点:
ACK
的时候,TCP 协议是允许我们携带数据的。之所以做不到,是 API 的限制导致的。Socket 是 TCP 层的封装,通过 Socket,我们就能进程 TCP 通信。
在 Java 的 SDK 中,socket 的共有两个接口:用于监听客户连接的 ServerSocket 和用于通信的 Socket。使用 socket 的步骤如下:
Socket Demo:
1、创建 ServerSocket 并监听客户连接
1 | public class EchoServer { |
2、使用 Socket 连接服务端
1 | public class EchoClient { |
3、通过 Socket 获取输入/输出流进行通信
首先,我们来实现服务端(服务端就是不停的输入数据,然后写会给客户端):
1 | public class EchoServer { |
接下来,是客户端的实现(客户端在读取用户输入的同时,又会读取服务器的响应,所以需要创建一个线程来读取服务器的响应):
1 | public class EchoClient { |
注意:
项目中用到的自定义 view 还是有限的,所有有些知识点也是遗漏,这次做一个稍微全面点的总结,复习一下。
自定义 view 需要至少重写两个构造函数
1 | public MyView(Context context) { |
而且大部分时候只需重写两个函数:onMeasure()、onDraw()。onMeasure() 方法负责对当前 view 的尺寸进行测量,onDraw()方法负责把当前这个 view 绘制出来。
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) |
在 onMeasure 方法中,参数 widthMeasureSpec 与 heightMeasureSpec 包含了尺寸的测量模式和尺寸大小。我们知道 int 类型数据占用 32 个 bit,这个参数数值将 int 数据的前两个 bit 用于区分不同的布局模式,后面 30 个 bit 用于存放尺寸的数据。Android 内置类 MeasureSpec 可以帮助计算测量模式与尺寸:
1 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); |
请注意,这里的尺寸大小并不是最终我们的 View 的尺寸大小,而是父 view 提供的参考大小。至于测量模式,表示了 view 与父 view 直接的关系,测量模式有三种:
测量模式 | 表示意思 |
---|---|
UNSPECIFIED | 父容器没有对当前View有任何限制,当前View可以任意取尺寸 |
EXACTLY | 当前的尺寸就是当前View应该取的尺寸 |
AT_MOST | 当前尺寸是当前View能取的最大尺寸 |
测量模式与布局时 wrap_content、match_parent 以及写成固定尺寸对应关系:
match_parent –> EXACTLY。match_parent 要利用父 View 给子 View 提供的剩余空间,而父 View 的剩余空间是确定的,也就是这个测量模式对应的数值锁存放的尺寸。
wrap_content –> AT_MOST。就是我们想要将大小设置为包裹我们的 View 内容,那么尺寸就是父 View 给我们作为参考的尺寸,是要不超过这个尺寸就可以,具体尺寸可以根据我们的需求去设定。
固定尺寸 –>EXACTLY。用户自己制定尺寸的大小。
1 | private int getMySize(int defaultSize, int measureSpec) { |
1 | protected void onDraw(Canvas canvas) { |
1 | <resources> |
在布局文件中使用自定义的属性;
注意:需要再根标签中设定命名空间,命名空间的名称可以自己定义,命名空间的值为固定:“http://schemas.android.com/apk/res-auto”。
在自定义 View 里面通过参数 AttributeSet 把定义的属性取出。
1 | private int defalutSize; |
sh
这个列表包含与网页抓取和数据处理的Python库
fake_useragent - 实现随机请求头的设置。
pandas - 为了解决数据分析任务而创建的,可用来创建数据表。
用于解析和操作简单文本的库。
解析和处理特定文本格式的库。
处理人类语言问题的库。
异步网络编程库
电子邮件解析库
解析/修改网址和网络地址库。
提取网页内容的库。
用于WebSocket的库。
本文转自伯乐在线http://python.jobbole.com/82633/
生命周期:onCreate()
-> onStart()
- > onResume()
-> onPause()
-> onStop()
-> onDestroy()
特殊的生命周期
资源相关的系统配置发生改变会导致 activity 被杀死并重新创建。系统配置发生改变以后,activity 会销毁,其 onPause、onStop、onDestroy 会相继执行,因为是异常销毁,系统会调用 onSaveInstanceState 来保存 activity 当前状态,这个方法的调用时机是在 onStop 之前,与 onPause 无时序关系。activity 重建后会调用 onRestoreInstanceState,并且把销毁时 onSaveInstance 保存的 Bundle 对象作为参数同时传递给 onRestoreInstance 和 onCreate,onRestoreInstance 在 onStart 方法之后回调。同时,onRestoreInstance 方法会自动帮我们做一些恢复工作。另外,和 activity 一样,每个 view 都有自己相关的 onSaveInstanceState 方法和 onRestoreInstanceState 方法。
应用场景:假设 Activity A 位于栈顶,从 A 跳转 B 时声明周期
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
standard - 默认模式
默认的启动模式,既标准模式,在不指定启动模式的前提下,系统默认使用该模式启动 Activity,每次启动一个 Activity 都会重写一个新的实例,不管这个实例存在不存在。该 Activity 属于启动它的 Activity 所在的任务占中。
应用场景:默认。
singleTop - 栈顶复用模式
该模式下,当前栈中已存在该 Activity 实例并且该实例位于栈顶,则不会重新创建 Activity 的实例,而是复用栈顶的实例,并且将 Intent 对象传入,回调 onNewIntent 方法。其余情况,与 standard 模式相同,会重新创建实例。
应用场景:登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏。
singleTask - 栈内复用模式
该模式下,如果栈中存在这个 Activity 的实例就会复用这个 Activity,不管它是否位于栈顶,复用时,会将它上面的 Activity 全部出栈,并且会回调该实例的 onNewIntent 方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过 taskAffinity 属性指定。如果这个任务栈不存在,则会创建这个任务栈。
应用场景:应用主页面、WebView页面、扫一扫页面、电商中:确认订单页面,付款页面。
singleInstance - 全局唯一模式
该模式具备 singleTask 模式的所有特性,并且这种模式下的 Activity 会单独占用一个 Task 栈,具备全局唯一性,即整个系统中只有一个实例。由于栈内复用的特性,后续的请求不会创建新的 Activity 实例,除非这个特殊的任务栈被销毁。以 singleInstance 模式启动的 Activity 在整个系统中是单利的,如果启动这样的 Activity 时已经存在一个实例,那么会把它所在的任务调度到前台,重用这个实例。
应用场景:系统 Launcher、锁屏键、来电显示等系统应用。
任务相关性。
创建过程:Fragment - onAttach -> Fragment - onCreate -> Fragment - onCreateView -> Activity - onCreate -> Fragment - onActivityCreate -> Activity - onStart -> Fragment - onStart -> Activity - onResume -> Fragment - onResume
销毁过程:Fragment - onPause -> Activity - onPause -> Fragment - onStop -> Activity - onStop -> Fragment - onDestroyView -> Fragment - onDestroy -> Fragment - onDetach -> Activity - onDestroy
在 Activity 创建完回调 onResume 后创建 menu,回调 onCreateOptionMenu。
调用者和service在同一个进程里,所以运行在主进程的main线程中。所以不能进行耗时操作,可以采用在service里面创建一个Thread来执行任务。service影响的是进程的生命周期,讨论与Thread的区别没有意义。
任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例。
启动方式:
start 方式启动服务
步骤:
1、定义一个类继承 service
2、清单文件中配置 service
3、使用 context 的 startService(Intent) 方法启动 service
4、不使用时,调用 stopService(Intent) 方法停止服务
生命周期:
onCreate() – > onStartCommand() – > onDestory()
注意:如果服务已经开启,不会重复回调onCreate()方法,如果再次调用context.startService()方法,service而是会调用onStart()或者onStartCommand()方法。停止服务需要调用context.stopService()方法,服务停止的时候回调onDestory被销毁。
特点:
一旦服务开启就跟调用者(开启者)没有任何关系了。开启者不能调用服务里面的方法。
bind 方式启动服务
步骤:
定义一个类继承 service
清单文件中注册 service
使用 context 的 bindService(Intent,ServiceConnection,int) 方法启动 service
不再使用时,调用 unbindService(ServiceConnection) 方法停止该服务
生命周期:
onCreate() – > onBind() –> onUnbind() – > onDestory()
注意:
绑定服务不会调用 onStart 或者 onStartCommand 方法
特点:
bind 的方式启动服务,绑定服务,服务的生命周期跟随绑定者。绑定者可以调用服务里面的方法。
调用者和 service 不再同一个进程中, service 在单独的进程中的 main 线程,是一种跨进程通信方式。
在服务的内部创建一个内部类,提供一个方法,可以间接调用服务的方法;
把暴露的接口文件的扩展名改为 .aidl 文件,去掉访问修饰符;
实现服务的 onbind 方法,继承 Bander 和实现 aidl 定义的接口,提供给外界可调用的方法
在 Activity 中绑定服务 bindService;
在服务成功绑定的时候回调 onServiceConnected 方法,床底一个 IBinder 对象
aidl 定义的接口 .Stub.asInterface(binder) 调用接口里面的方法
IntentService 是 Service 的子类,比普通的 Service 增加了额外的功能。
Service 本身存在的两个问题:
IntentService 特征:
广播被分为两种不同的类型:“普通广播(Normal broadcasts)”和“有序广播(Ordered broadcasts)”。普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高,但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播;然而有序广播是按照接收者声明的优先级别(声明在intent-filter元素的android:priority属性中,数越大优先级别越高,取值范围:-1000到1000。也可以调用IntentFilter对象的setPriority()进行设置),被接收者依次接收广播。如:A的级别高于B,B的级别高于C,那么,广播先传给A,再传给B,最后传给C。A得到广播后,可以往广播里存入数据,当广播传给B时,B可以从广播中得到A存入的数据。
发送广播
1 | Context.sendBroadcast() |
注意:BroadCastReceiver 生命周期很短,在 onReceiver 中需要进行耗时操作时,应该考虑在 Service 中开启一个新线程处理。
静态注册和动态注册区别
小结:
contentprovider是android四大组件之一的内容提供器,它主要的作用就是将程序的内部的数据和外部进行共享,为数据提供外部访问接口,被访问的数据主要以数据库的形式存在,而且还可以选择共享哪一部分的数据。这样一来,对于程序当中的隐私数据可以不共享,从而更加安全。contentprovider是android中一种跨程序共享数据的重要组件。
原文:
https://blog.csdn.net/mynameishuangshuai/article/details/51491074
新年要立新的 flag,2018 过去就过去吧,2019重新开始吧。列一下 2019 年想要学习的东西吧。有些可能很零碎,先写出来,再去归类总结吧。
四大组件的使用、Activity启动模式、Service的start和bind
学习资料:
学习资料:
Android 四大组件 - 简书
彻底弄懂Activity四大启动模式 - Android研发专栏 - CSDN博客
Android 自定义 View 合集 - Android - 掘金
了解HTTP/HTTPS、状态码、header,get和post等;掌握网络请求API和相关网络库;熟悉长连接。
HTTP 必知必会的那些
Android HttpURLConnection详解 - 简书
手把手教你写 Socket 长连接
了解drawable加载规则以及图片缓存
Android中Bitmap内存优化 - 简书
Android drawable微技巧,你所不知道的drawable的那些细节 - 郭霖的专栏 - …
Android照片墙完整版,完美结合LruCache和DiskLruCache - 郭霖的专栏 - …
Android DiskLruCache完全解析,硬盘缓存的最佳方案 - 郭霖的专栏 - CSDN博…
了解文件流、sqlite
Java 文件流总结 - 简书 详细介绍了文件输入输出流
Java 随机访问文件_w3cschoolJava随机访问文件
Java 序列化的高级认识 Java的序列化原理以及对象输入输出流
Android SQLite详解 - 简书sqlite数据库如何使用
Concurrent database access - Dmytro Danylyk sqlite到底是线程安全的吗?
Android Handler的基本使用 - 简书 了解Handler最基本的使用方式,文章清晰易懂
Android Handler详解 - 简书简单理解Handler的内部原理
Binder原理、AIDL的使用、多进程的定义和特性
https://blog.csdn.net/universus/article/details/6211589
View的measure、layout和draw,View的工作原理
图解View测量、布局及绘制原理 - 简书
android ListView 工作原理 - Android - 掘金
事件分发原理和规则
Android事件分发机制,大表哥带你慢慢深入 - 简书
Android ViewGroup事件分发机制 - Hongyang - CSDN博客
Handler、Looper、Thread三者之间的关系;得知道子线程创建Handler为什么会报错,如何才能不报错
源码角度讲解子线程创建Handler报错的原因 - 曹银飞的专栏 - CSDN博客
setResult和finish的顺序关系
onSaveInstanceState()和onRestoreInstanceState()
onNewIntent()和onConfigurationChanged()
setResult()的调用时机 - 沙翁 - 博客园【推荐理由】清晰易懂,直接了当。
onSaveInstanceState()和onRestoreInstanceState()使用详解…【推荐理由】简单好懂。
关于onConfigurationChanged方法及常见问题解决 - 朱小姐。的博客 - CSDN…
先start再bind,如何停止一个Service
Service onStartCommand的返回值
bindService后,ServiceConnection里面的回调方法运行在哪个线程?它们的调用时机分别是什么?
Service的onCreate运行在哪个线程?
Android中startService和bindService的区别 - 简书
Service: onStartCommand 诡异的返回值 - CodingMan - CSDN博…
Service的onCreate、onStartCommand、onDestory等全部生命周期方法都运行在UI线程,ServiceConnection里面的回调方法也是运行在UI线程,大家一定要记住。
ContentProvider的生命周期
ContentProvider的onCreate和CRUD运行在哪个线程?它们是线程安全的吗?
ContentProvider的内部存储只能是sqlite吗?
android ContentProvider onCreate()在 Application……
ContentProvider总结 - 简书
注意:ContentProvider的底层是Binder,当跨进程访问ContentProvider的时候,CRUD运行在Binder线程池中,不是线程安全的,而如果在同一个进程访问ContentProvider,根据Binder的原理,同进程的Binder调用就是直接的对象调用,这个时候CRUD运行在调用者的线程中。另外,ContentProvider的内部存储不一定是sqlite,它可以是任意数据。
知道AsyncTask的工作原理,知道其串行和并行随版本的变迁
Android源码分析—带你认识不一样的AsyncTask - 任玉刚 - CSDN博客 【推荐理由】只看这一篇文章就够了
https://android.googlesource.com/platform/frameworks/base/ /android-8.1.0_r46/core/java/android/os/AsyncTask.java
今天有个需求,有个地区列表要改成可展开收起的列表,正好可以探索一下 recyclerview 的各个组成部分。
今天着重记录一下:ViewHolder、Adapter、AdapterDataObservable、RecyclerViewDataObServer、LayoutManager、Recycler、RecyclerPool。
先上一张图大致描述下他们之间的关系,这张图是 adapter.nofifyXX() 时 Recyclerview 的执行逻辑涉及到的一些类:
对于Adapter来说,一个ViewHolder就对应一个data。它也是Recycler缓存池的基本单元。
1 | public abstract static class ViewHolder { |
上面列举了 ViewHolder 中最重要的 4 个属性:
itemView:会被当做 child view 来添加到 RecyclerView 中。
mPosition:标记当前 ViewHolder 在 Adapter 中所处的位置。
mItemViewType:当前 ViewHolder 的 Type,在 ViewHolder 保存到 RecyclerPool 时,主要靠这个类型来对 ViewHolder 做复用。
mFlags:标记ViewHolder的状态,比如 FLAG_BOUND(显示在屏幕上)、FLAG_INVALID(无效,想要使用必须rebound)、FLAG_REMOVED(已被移除)等。
其工作是把 data 和 view 绑定,即上面说的一个 data 对应一个 ViewHolder。主要负责 ViewHolder 的创建以及数据变化时通知 RecyclerView。
Adaper 会引用一个数据集合,getItemCount() 用来告诉 RecyclerView 展示的总条目。另外,Adapter 不是直接映射 data -> ViewHolder,而是 data position -> data type ->ViewHolder。所以对于 ViewHolder 来说,它知道的只是它的 view type。
Adapter 是数据源的直接接触者,当数据源发生变化时,它需要通知给 RecyclerView。这里使用的模式是观察者模式,AdapterDataObservable 是数据源变化是的被观察者, RecyclerViewDataObservable 是观察者。开发中使用的 notifyXX() 刷新 ui,就是抵用的 adapterDataObservable 的 notifyChanged() 。
继承自 AdapterDataObserver,用来监听 Adapter 的数据变化。
它是RecyclerView的布局管理者,RecyclerView在onLayout时,会利用它来layoutChildren,它决定了RecyclerView中的子View的摆放规则。但不止如此, 它做的工作还有:
对于 LayoutManager 来说,它是 ViewHolder 的提供者。对于 RecyclerView 来说,它是 ViewHolder 的管理者,是 RecyclerView 最核心的实现。下面这张图大致描述了它的组成:
1 | final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); |
相信你在许多 RecyclerView 的 crash log 中都看到过这个单词。它是指 View 在 RecyclerView 布局期间进入分离状态的子视图。即它已经被 deatach(标记为 FLAG_TMP_DETACHED 状态)了。这种 View 是可以被立即复用的。
它在复用时,如果数据没有更新,是不需要调用 onBindViewHolder 方法的。如果数据更新了,那么需要重新调用 onBindViewHolder。
mAttachedScrap 和 mChangedScrap 中的 View 复用主要作用在 adapter.notifyXXX 时。这时候就会产生很多scrap 状态的 view。 也可以把它理解为一个 ViewHolder 的缓存。不过在从这里获取 ViewHolder 时完全是根据ViewHolder 的position 而不是 item type。如果在 notifyXX 时 data 已经被移除掉你,那么其中对应的ViewHolder 也会被移除掉。
可以把它理解为 RecyclerView 的一级缓存。它的默认大小是3, 从中可以根据 item type 或者 position 来获取ViewHolder。可以通过 RecyclerView.setItemViewCacheSize() 来改变它的大小。
它是一个可以被复用的 ViewHolder 缓存池。即可以给多个 RecycledView 来设置统一个 RecycledViewPool。这个对于多 tab feed 流应用可能会有很显著的效果。它内部利用一个 ScrapData 来保存 ViewHolder 集合:
1 | class ScrapData { |
一个 ScrapData 对应一种 type 的 ViewHolder 集合。看一下它的获取 ViewHolder 和保存 ViewHolder 的方法:
1 | //存 |
{} 大括号标识一个对象,大部分情况下要有成对的属性和值,或者函数;
1 | var LangShen = {"Name":"Langshen","AGE":"28"}; |
[] 中括号标识一个数组,也可以理解为一个数组对象。
1 | var LangShen = [ "Name","LangShen","AGE","28" ]; |
{ } 和[ ] 一起使用,我们前面说到,{ } 是一个对象,[ ] 是一个数组,我们可以组成一个对象数组
1 | var LangShen = { "Name":"Langshen", |
1 | var arrayObj = new Array(); //创建一个数组 |
要说明的是,虽然第二种方法创建数组指定了长度,但实际上所有情况下数组都是变长的,也就是说即使指定了长度为5,仍然可以将元素存储在规定长度以外的,注意:这时长度会随之改变。
1 | arrayObj. push([item1 [item2 [. . . [itemN ]]]]);// 将一个或多个新元素添加到数组结尾,并返回数组新长度 |
1 | var testGetArrValue = arrayObj[1]; //获取数组的元素值 |
1 | arrayObj.pop()//移除最后一个元素并返回该元素 |
1 | arrayObj.slice(start, [end]); //以数组的形式返回数组的一部分,注意不包括 end 对应的元素,如果省略 end 将复制 start 之后的所有元素 |
1 | arrayObj.slice(0); //返回数组的拷贝数组,注意是一个新的数组,不是指向 |
数组元素的排序
1 | arrayObj.reverse(); //反转元素(最前的排到最后、最后的排到最前),返回数组地址 |
突然觉得这个分类,应该改名奇巧淫技,或许会更好听点。偷鸡摸狗的话,并不是说这个是见不得人的东西。不过以后再说吧,毕竟不是今天的重点
公司要每个技术序列的人都要做技术分享,一开始的时候确实没有想好要讲什么,毕竟同行,说的浅了没技术含量,说的深了担心自己给自己挖坑(滑稽脸)。最后,还是选择了 xposed 这个主题,因为 xposed 对于安卓来说确实是一个牛逼的存在,使用起来还是挺牛逼的,但是过程不是很复杂,作为技术去分享的话,可以自己控制一下。比如,设计的 Android 系统启动,xposed 的原理分析,还有一些 xposed 项目的分析,感觉这些应该能蒙混过去的把。。。心慌的,毕竟第一次。
百度说:
Xposed 框架是一款可以在不修改 APK 的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
维基说:
Xposed框架(Xposed framework)是一套开源的、在 Android 高权限模式下运行的框架服务,可以在不修改 APK 文件的情况下修改程序的运行(修改系统),基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。这套框架需要设备打开了 Root 权限方可安装使用。
OK,其实 xposed 就是一个框架,通过这个框架你能拿到很多系统级别的东西,比如系统级别的数据返回( Mac、手机型号、IMEI 等),这样你就可以直接欺骗一些应用,去设置你想设置的手机参数;再比如可以拿到运行在系统上的各个应用的试了,这样你就可以调用这些实例的方法,把 App 的界面改成自己想要的样子,去掉界面里不喜欢的东西(广告),自动抢红包,消息防撤回,步数修改等等。
当然,高收益常常伴随的是高风险,在这里,想要使用 xposed,风险就是需要 root 你的手机,经常搞基的人是知道这个的,具体操作就不在这里细说了,我用的机子是小米的,系统刷成开发版可以直接修改 root 权限的,还是挺方便的,VirtualXposed 倒是可以不用 root,不过属于阉割版,可玩性肯定就没那么好了。
有了 xposed,我们可以加载很多 App 插件,理论上通过这些插件,我们可以 hook 到系统任意一个 Java 进程(zygote、systemserver、systemui)。
首先,我们来简单梳理一下 Android 系统的启动流程:
启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在 ROM)开始执行。加载引导程序 Bootloader 到RAM,然后执行。
引导程序 BootLoader
引导程序 BootLoader 是在 Android 操作系统开始运行前的一个小程序,它的主要作用是把系统 OS 拉起来并运行。
Linux 内核启动
内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置,它首先在系统文件中寻找 init.rc 文件,并启动 init 进程。
init 进程启动
初始化和启动属性服务,并且启动 zygote 进程。
init 进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行 init.rc 脚本。
zygote 进程启动
创建 JavaVM 并为 JavaVM 注册 JNI,创建服务端 Socket,启动 SystemServer 进程。在这个阶段,你可以看到启动动画。
SystemServer 进程启动
启动 Binder 线程池和 SystemServiceManager,并且启动各种系统服务。
Launcher启动
被 SystemServer 进程启动的 ActivityManagerService 会启动 Launcher,Launcher 启动后会将已安装应用的快捷图标显示到界面上。
在 Android 系统中,应用程序进程以及系统服务进程 SystemServer 都是有 zygote 进程孵化出来的,而 zygote 进程则是由 init 进程启动的(毕竟基于 Linux 系统),zygote 进程在启动的时候会创建一个 Dalvik(5.0以前默认,之后ART,空间交换时间) 虚拟机实每当它孵化一个新的应用程序简称时,都会将这个 Dalvik/ART 虚拟机实例复制到新的应用进程里面去,从而使得每一个应用程序进程都有各一个独立的 Dalvik/ART 虚拟机实例。而 zygote 进程对应的执行文件是/system/bin/app_process,而 Xposed 所做的就是替换了 app_process。
Xposed 用自己实现的 app_process 替换了系统提供的 app_process,加载一个额外的jar包,然后入口从原来的:com.android.internal.osZygoteInit.main() 被替换成了: de.robv.android.xposed.XposedBridge.main(), 然后创建的 Zygote 进程就变成 Hook 的 Zygote 进程了,而后面 Fork 出来的进程也是被Hook过的。
相关的一些文档:
XposedInstaller
这是 Xposed 的插件管理和功能控制 APP,也就是说 Xposed 整体管控功能就是由这个APP来完成的,它包括启用Xposed 插件功能,下载和启用指定插件APP,还可以禁用 Xposed 插件功能等。注意,这个app要正常无误得运行必须能拿到 root 权限。
Xposed
这个项目属于 Xposed 框架,其实它就是单独搞了一套 xposed 版的 zygote。这个 zygote 会替换系统原生的zygote。所以,它需要由 XposedInstaller 在 root 之后放到 /system/bin 下。
XposedBridge
这个项目也是 Xposed 框架,它属于 Xposed 框架的Java部分,编译出来是一个 XposedBridge.jar 包。
XposedTools
Xposed 和 XposedBridge 编译依赖于Android源码,而且还有一些定制化的东西。所以XposedTools就是用来帮助我们编译 Xposed 和 XposedBridge的。
要上主菜了,先给大家说几句,其实 Xposed 插件就是一个完整的 apk,跟我们平常做的 apk 并没有多大的不同,只不过在项目中加了一些标识,让 Xposed 能狗识别,给这个 apk 特殊的权限,可以去影响其他 apk 的运行。
创建 Android 项目
当然,首先应该创建一个 Android 项目。
修改 AndroidManifest.xml
在 application 节点下添加三个
1 | <meta-data |
引入 Xposed Framework API
在 app/build.gradle
文件中声明 Xposed Framework API 的jar包依赖。
1 | compileOnly 'de.robv.android.xposed:api:82' |
创建 Xposed 入口文件
在 assets 文件夹下面创建一个名为 xposed_init 的文件, XposedBridge 会从 assets 目录中的 xposed_init 中获取入口点,比如:
1 | top.programan.xposeddemo.XposedUtil |
编写入口类
在此之前我们先修改下我们的MainActivity.java,修改下TextView显示的文字:
1 | class MainActivity : AppCompatActivity() { |
然后编写入口类,代码如下:
1 | class XposedInit : IXposedHookLoadPackage { |
OK,到这里一个简单的 Xposed 项目已经完成了,安装我们完成的 apk,记得要在 XposedInstaller 里面选择该插件,然后重启手机。不出意外的话,应该是可以正常运行的。
如上发现,一个普通的 Xposed 项目是不难实现,更难实现的应该是怎么样去逆向逆向 hook 的应用,并找到逆向 hook 对应的代码。
IXposedHookLoadPackage
1
2
3
4
5
6
7
8 > /**
> * Get notified when an app ("Android package") is loaded.
> * This is especially useful to hook some app-specific methods.
> *
> * <p>This interface should be implemented by the module's main class. Xposed will take care of
> * registering it as a callback automatically.
> */
>
翻译起来就是:
当APP加载的时候就会收到通知,对于hook一些应用的特殊方法时十分有效,插件的主方法应该实现这个接口,Xposed将会自动的为他注册回调。
所以来说这个接口的作用很明显,如果是一个应用级别的插件那么入口类就需要实现的这个接口,当插件要hook的应用启动时就会回调这个接口的方法。Xposed自动帮我们做,很贴心。猜测源码实现应该是一个观察者模式。
使用代码如下:
1 | class XposedInit : IXposedHookLoadPackage { |
IXposedHookLoadPackage接口的回调方法handleLoadPackage的参数XC_LoadPackage.LoadPackageParam的源码如下:
1 | /** |
可以看到主要是要加载的应用的一些相关信息。
IXposedHookZygoteInit
1
2
3
4
5
6
7
8
9
10
11
12 > /**
> * Hook the initialization of Zygote process(es), from which all the apps are forked.
> *
> * <p>Implement this interface in your module's main class in order to be notified when Android is
> * starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
> * be applied for every app. Only the Android framework/system classes are available at that point
> * in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
> * and its variants.
> *
> * <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
> */
>
翻译起来的大体意思就是:
hook初始的Zygote进程,所有的应用都会适用。在你插件的主方法实现这个接口,这样当android启动的时候这个接口就会被通知,你修改和hook的地方对所有的应用都会有效,假如你只想对某个应用起作用建议你只需要实现IXposedHookLoadPackage这个接口就行了。
使用代码如下:
1 | class XposedZygoteInit : IXposedHookZygoteInit { |
IXposedHookZygoteInit接口的回调方法initZygote的参数StartupParam的源码如下:
1 | /** Data holder for {@link #initZygote}. */ |
在Zygote启动时调用,用于系统服务的Hook 回调方法initZygote()。
IXposedHookInitPackageResources
1
2
3
4
5
6
7
8 > /**
> * Get notified when the resources for an app are initialized.
> * In {@link #handleInitPackageResources}, resource replacements can be created.
> *
> * <p>This interface should be implemented by the module's main class. Xposed will take care of
> * registering it as a callback automatically.
> */
>
翻译起来的大体意思就是:
当应用资源被初始化的时候会被通知调用,在handleInitPackageResources方法里面可以替换资源。应该在主方法里面被实现,Xposed会自动注册通知调用回调方法。
1 | class XposedZygoteInit : IXposedHookInitPackageResources { |
IXposedHookInitPackageResources接口的回调方法handleInitPackageResources的参数InitPackageResourcesParam的源码如下:
1 | /** |
有了这个XResource对象,就可以拿到通过覆写hookLayout方法拿到布局资源树。
1 | class XposedZygoteInit : IXposedHookInitPackageResources { |
我们看下回调接口LayoutInflatedParam的源码:
1 | /** |
注意上面的View变量,这个就是布局资源树,可以进行遍历,拿到特定的控件,玩一些狂拽炫酷的东西。
XposedHelpers
findAndHookMethod
具体的使用和相关的参数介绍上面已经说过了,就不再重复,不过需要关注下findAndHookMethod的参数MethodHookParam,具体的源码如下:
1 | /** |
需要关注下thisObject,代表调用该方法的对象实例,如果是静态方法,返回一个Null,比如这里调用onCreate()方法的是MainActivity,那么获得的就是MainActivity实例。
其他的一些方法
优势
不足
如果有时间的话,跟大家一块 hook 一下抖音,实现自动翻页的功能。
以上,是本次分享的全部了。温馨提示,搞基需谨慎。
命令名 | 功能描述 | 使用举例 |
---|---|---|
mkdir | 创建一个目录 | mkdir dirname |
rmdir | 删除一个目录 | rmdir dirname |
mvdir | 移动或重命名一个目录 | mvdir dir1 dir2 |
cd | 改变当前目录 | cd dirname |
pwd | 显示当前目录的路径名 | pwd |
ls | 显示当前目录的内容 | ls -la |
dircmp | 比较两个目录的内容 | dircmp dir1 dir2 |
命令名 | 功能描述 | 使用举例 |
---|---|---|
cat | 显示或连接文件 | cat filename |
pg | 分页格式化显示文件内容 | pg filename |
more | 分屏显示文件内容 | more filename |
od | 显示非文本文件的内容 | od -c filename |
cp | 复制文件或目录 | cp file1 file2 |
rm | 删除文件或目录 | rm filename |
mv | 改变文件名或所在目录 | mv file1 file2 |
ln | 联接文件 | ln -s file1 file2 |
find | 使用匹配表达式查找文件 | find . -name “*.c” -print |
file | 显示文件类型 | file filename |
open | 使用默认的程序打开文件 | open filename |
命令名 | 功能描述 | 使用举例 |
---|---|---|
head | 显示文件的最初几行 | head -20 filename |
tail | 显示文件的最后几行 | tail -15 filename |
cut | 显示文件每行中的某些域 | cut -f1,7 -d: /etc/passwd |
colrm | 从标准输入中删除若干列 | colrm 8 20 file2 |
paste | 横向连接文件 | paste file1 file2 |
diff | 比较并显示两个文件的差异 | diff file1 file2 |
sed | 非交互方式流编辑器 | sed “s/red/green/g” filename |
grep | 在文件中按模式查找 | grep “^[a-zA-Z]” filename |
awk | 在文件中查找并处理模式 | awk ‘{print $1 $1}’ filename |
sort | 排序或归并文件 | sort -d -f -u file1 |
uniq | 去掉文件中的重复行 | uniq file1 file2 |
comm | 显示两有序文件的公共和非公共行 | comm file1 file2 |
wc | 统计文件的字符数、词数和行数 | wc filename |
nl | 给文件加上行号 | nl file1 >file2 |
命令名 | 功能描述 | 使用举例 |
---|---|---|
passwd | 修改用户密码 | passwd |
chmod | 改变文件或目录的权限 | chmod ug+x filename |
umask | 定义创建文件的权限掩码 | umask 027 |
chown | 改变文件或目录的属主 | chown newowner filename |
chgrp | 改变文件或目录的所属组 | chgrp staff filename |
xlock | 给终端上锁 | xlock -remote |
命令名 | 功能描述 | 使用举例 |
---|---|---|
make | 维护可执行程序的最新版本 | make |
touch | 更新文件的访问和修改时间 | touch -m 05202400 filename |
dbx | 命令行界面调试工具 | dbx a.out |
xde | 图形用户界面调试工具 | xde a.out |
命令名 | 功能描述 | 使用举例 |
---|---|---|
ps | 显示进程当前状态 | ps u |
kill | 终止进程 | kill -9 30142 |
nice | 改变待执行命令的优先级 | nice cc -c *.c |
renice | 改变已运行进程的优先级 | renice +20 32768 |
命令名 | 功能描述 | 使用举例 |
---|---|---|
date | 显示系统的当前日期和时间 | date |
cal | 显示日历 | cal 8 1996 |
time | 统计程序的执行时间 | time a.out |
命令名 | 功能描述 | 使用举例 |
---|---|---|
telnet | 远程登录 | telnet hpc.sp.net.edu.cn |
rlogin | 远程登录 | rlogin hostname -l username |
rsh | 在远程主机执行指定命令 | rsh f01n03 date |
ftp | 在本地主机与远程主机之间传输文件 | ftp ftp.sp.net.edu.cn |
rcp | 在本地主机与远程主机 之间复制文件 | rcp file1 host1:file2 |
ping | 给一个网络主机发送 回应请求 | ping hpc.sp.net.edu.cn |
阅读和发送电子邮件 | ||
write | 给另一用户发送报文 | write username pts/1 |
mesg | 允许或拒绝接收报文 | mesg n |
命令名 | 功能描述 | 使用举例 |
---|---|---|
history | 列出最近执行过的 几条命令及编号 | history |
r | 重复执行最近执行过的 某条命令 | r -2 |
alias | 给某个命令定义别名 | alias del=rm -i |
unalias | 取消对某个别名的定义 | unalias del |
命令名 | 功能描述 | 使用举例 |
---|---|---|
uname | 显示操作系统的有关信息 | uname -a |
clear | 清除屏幕或窗口内容 | clear |
env | 显示当前所有设置过的环境变量 | env |
who | 列出当前登录的所有用户 | who |
whoami | 显示当前正进行操作的用户名 | whoami |
tty | 显示终端或伪终端的名称 | tty |
stty | 显示或重置控制键定义 | stty -a |
du | 查询磁盘使用情况 | du -k subdir |
df | 显示文件系统的总空间和可用空间 | df /tmp |
w | 显示当前系统活动的总信息 | w |
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true