读源码长知识 | Android卡顿真的是因为”掉帧“?
suiw9 2024-11-17 01:42 24 浏览 0 评论
作者:唐子玄
Andriod 界面卡顿是因为掉帧,而掉帧是因为生产帧的速度跟不上消费帧的速度。
消费帧的速度与屏幕刷新率挂钩,屏幕就像连环画,若一秒播放 60 帧,消费一帧的速度为 1000/60 = 16.6 ms,即每 16.6 ms 屏幕就会去取下一帧的显示内容,若没取到,只能继续显示上一帧,画面就停滞了,这就称为“掉帧”,听上去好像丢失了无法找回的东西一样,其实它是形容“显示内容错过了一次显示机会”,描述的是屏幕硬件的一种行为。
屏幕为啥会没有取到显示内容?得从软件层找原因。带着这个问题,读一下 Framework 源码。
Choreographer
ViewRootImpl是一个 Activity 中所有视图的根视图,View 树的遍历就由它发起:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
// 编舞者
Choreographer mChoreographer;
// 触发遍历 View 树
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 向 Choreographer 抛 View树遍历任务
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// 遍历 View 树任务
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 构建遍历 View 树任务实例
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}
ViewRootImpl通过抛任务到Choreographer来触发 View 树遍历。
Choreographer是 android.view包下的一个类,字面意思是“编舞者”,隐含着“需要将两者同步”的意思,编舞即是将动作和节奏同步。而Choreographer是将"绘制内容"和"垂直同步信号"同步。
存放绘制任务
public final class Choreographer {
// 输入任务
public static final int CALLBACK_INPUT = 0;
// 动画任务
public static final int CALLBACK_ANIMATION = 1;
// view树遍历任务
public static final int CALLBACK_TRAVERSAL = 2;
// COMMIT任务
public static final int CALLBACK_COMMIT = 3;
// 暂存任务的链式数组
private final CallbackQueue[] mCallbackQueues;
// 主线程消息处理器
private final FrameHandler mHandler;
// 抛绘制任务
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
// 延迟抛绘制任务
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
// 抛绘制任务的具体实现
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 1. 将绘制任务根据类型暂存在链式结构中
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
// 2. 订阅下一个垂直同步信号
if (dueTime <= now) {
// 立刻订阅下一个垂直同步信号
scheduleFrameLocked(now);
} else {
// 在未来的某个时间点订阅垂直同步信号
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
// 主线程消息处理器
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_DO_SCHEDULE_CALLBACK:
// 在未来时间点订阅垂直同步信号
doScheduleCallback(msg.arg1);
break;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
// 订阅下一个垂直同步信号
scheduleFrameLocked(now);
}
}
}
}
}
Choreographer接收到新的绘制任务后,会执行两个动作:
- 绘制任务入链:
public final class Choreographer {
// 绘制任务链
private final class CallbackQueue {
// 任务链头结点
private CallbackRecord mHead;
// 绘制任务入链(按时间升序)
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
if (entry == null) {
mHead = callback;
return;
}
// 头插入
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
//中间插入或尾插入
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}
}
// 绘制任务结点
private static final class CallbackRecord {
// 下一个绘制任务
public CallbackRecord next;
// 绘制任务应该在这个时刻被执行
public long dueTime;
// 描述绘制任务的代码段
public Object action;
...
// 执行绘制任务
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
}
Choreographer接收有四种任务类型,分别是输入、动画、View树遍历、COMMIT。每个任务被抽象成CallbackRecord,同类任务按时间先后顺序组成一条任务链CallbackQueue。四条任务链存放在mCallbackQueues[]数组结构中。
- 订阅下一个垂直同步信号
public final class Choreographer {
private void scheduleFrameLocked(long now) {
// 若已经订阅过下个垂直同步信号,则什么也不做
if (!mFrameScheduled) {
// 当下一个垂直同步信号到来时,需要执行绘制任务
mFrameScheduled = true;
if (USE_VSYNC) {
// 不管走哪个分支,最终都会调用scheduleVsyncLocked()来注册接收垂直同步信号
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
...
}
}
}
// 委托 DisplayEventReceiver 来注册垂直同步信号
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
// 绘制一帧
void doFrame(long frameTimeNanos, int frame) {
synchronized (mLock) {
// 若不需要响应这个垂直同步信号,则直接返回,该帧不绘制任何东西
if (!mFrameScheduled) {
return;
}
...
}
...
}
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
// 垂直同步信号回调
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
// 向主线程抛任务,绘制一帧
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
// 绘制当前帧
doFrame(mTimestampNanos, mFrame);
}
}
}
// 垂直同步信号接收器
public abstract class DisplayEventReceiver {
// 注册接收下一个垂直同步信号
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "...");
} else {
// 向SurfaceFlinger订阅下一个垂直同步信号
nativeScheduleVsync(mReceiverPtr);
}
}
...
}
并不是每个垂直同步信号都会被上层订阅并处理,只有当Choreographer订阅了下一个垂直同步信号,SurfaceFlinger才会把信号通过onVsync()回调给上层。
图中第一个垂直信号到来之前,上层调用了postCallback()向Choreographer抛绘制任务,同时订阅了下一个信号并把mFrameScheduled置为 true,表示需要绘制下一帧。当第一个信号产生时,onVsync()被回调,同时将doFrame()抛到主线程执行,执行完毕后将mFrameScheduled置为 false,因没有后续订阅动作,所以上层不会收到后续的onVsync()回调,也不会绘制任何新东西。
执行绘制任务
ViewRootImpl向Choreographer抛绘制任务后,任务并没有立马执行,而是被暂存在绘制任务链中,并注册接收下一个垂直同步信号。只有当下一个信号通过onVsync()回调后,它才会被执行:
public final class Choreographer {
// 垂直同步信号接收器
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
// 发送异步消息到主线程,执行当前的Runnable,即doFrame()
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
// 绘制一帧的内容
doFrame(mTimestampNanos, mFrame);
}
}
}
每当垂直同步信号回调时,都会向主线程推送一个待执行的绘制帧任务doFrame()。
public final class Choreographer {
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
// 如果没有订阅这一帧的垂直同步信号,则直接退出不绘制
if (!mFrameScheduled) {
return; // no work to do
}
...
try {
// 处理这一帧的输入事件
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 处理这一帧的动画
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 处理这一帧的 View 树遍历
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 所有绘制任务结束后执行 COMMIT 任务
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
...
}
...
}
}
绘制每一帧时,会按照“输入事件”、“动画”、“View 树遍历”、“COMMIT”这样的顺序处理任务。
处理函数doCallback()定义如下:
public final class Choreographer {
// 暂存绘制任务的链式数组
private final CallbackQueue[] mCallbackQueues;
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 从指定类型的任务链中根据当前时间点取出要执行的任务
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
...
}
try {
// 执行任务链上被取出的所有任务
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
} finally {
...
}
}
// 任务实体类
private static final class CallbackRecord {
public CallbackRecord next;
public Object action;
...
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
// 执行任务
((Runnable)action).run();
}
}
}
}
追踪到这,ViewRootImpl推送过来的 "View 树遍历" 任务总算被执行了。
其中extractDueCallbacksLocked()是任务链CallbackQueue的方法,用于获取待当前时间点需要被执行的所有任务:
private final class CallbackQueue {
// 任务链头结点
private CallbackRecord mHead;
// 获取当前时间节点之前的所有任务,这些任务都需要在当前帧被执行
public CallbackRecord extractDueCallbacksLocked(long now) {
CallbackRecord callbacks = mHead;
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
CallbackRecord last = callbacks;
CallbackRecord next = last.next;
// 遍历任务链,以现在时间点为分割线,从链上摘取过去的任务
while (next != null) {
if (next.dueTime > now) {
last.next = null;
break;
}
last = next;
next = next.next;
}
mHead = next;
// 以链的形式返回所有过去任务
return callbacks;
}
}
绘制当前帧时,会以当前时刻为分割线,从任务链上摘取所有以前的任务,并按时间先后顺序逐个执行。
在纸上演算一遍上面的代码:
上图表示在第一个垂直同步信号还未到来时,上层已经向任务链中已添加了 3 个任务,其中前两个的执行时刻在第一个信号到来之前,而第三个任务在第一个信号后执行。
在第一个任务入链时就完成了对第一个信号的订阅,而第三个任务在第一个信号之后执行,所以它的订阅行为doScheduleCallback()先被存放在主线程消息队列中,待第一个信号到来之后再订阅第二个信号。
当第一个垂直同步信号到来后,doFrame()被抛到主线程,它从任务链中摘取当前时间节点之前的任务1和2并执行它们。当任务被执行完,就从主线程消息队列中取出下一条消息“为任务3订阅下一个垂直同步信号”并执行。所有这些都完成后,主线程只能发呆,等待下一个垂直同步信号。
当第二个垂直同步信号到来之后,任务链中剩下的任务3都被取出并抛到主线程执行。任务链空了,当任务3执行完毕后,主线程彻底没事做,只能等到上层再向Choreographer抛任务。
卡顿的原因是掉帧?
推迟
上述情况,绘制任务都能在一个帧间隔内完成,若任务很耗时,执行时间超过了帧间隔会怎么样?
第一个垂直信号到来后,任务1和2被抛到主线程执行,这次它们执行时间略长,超过了一个帧间隔,导致订阅下一个信号的函数迟迟未被执行。对于上层来说,错过了第二个onVsync()回调,这意味着,任务1和2错过了一次显示时机,任务3错过了一次渲染时机。对于底层来说,显示器在发出垂直同步信号时会向图形缓冲取显示内容,这次没拿到内容,只能继续显示上一帧图像。
任务1和2执行完,主线程才“订阅下一个信号”,当第三个信号到来时,显示器从图形缓冲中取到了任务1和2的渲染结果,而任务3也被抛到主线程执行。
这个 case 中,任务链中所有的绘制任务都被推迟显示了。
合并
若主线程中除了绘制任务外,还有别的耗时任务,情况会怎么样?
假设第一个垂直同步信号到来前,上层已经抛了3个绘制任务到任务链中,它们执行的时刻分别被设定于第一、二、三个垂直同步信号之间。
当第一个垂直同步信号到来时,doFrame()被抛到主线程消息队列,但主线程被一个 I/O 任务占用着,遂doFrame()一直没有得到执行机会。而且订阅后续信号的函数也无法被执行,所以第二、三个onVsync()不会被调用,直到 I/O 操作执行完毕。
推迟执行的doFrame()会有什么不一样?
public final class Choreographer {
private Choreographer(Looper looper, int vsyncSource) {
...
// 帧间隔 = 1 秒 / 刷新率
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
}
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
private long mTimestampNanos;
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
// 记录垂直信号到达的时刻
mTimestampNanos = timestampNanos;
...
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
// 垂直信号到达时刻会和doFrame()一起被抛到主线程
doFrame(mTimestampNanos, mFrame);
}
}
void doFrame(long frameTimeNanos, int frame) {
synchronized (mLock) {
...
// 当前帧实际被绘制的时刻
startNanos = System.nanoTime();
// 计算当前帧绘制延迟 = 当前时刻 - 本应绘制的时刻
final long jitterNanos = startNanos - frameTimeNanos;
// 如果延迟大于帧间隔
if (jitterNanos >= mFrameIntervalNanos) {
// 计算跳过的帧数,如果帧数超过阈值则log警告
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
// 计算帧延迟相对于帧间隔的偏移量
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
// 修正帧绘制时刻,将其对齐到最新到达的垂直同步信号
frameTimeNanos = startNanos - lastFrameOffset;
}
...
// 将上一帧绘制时刻更新为最新垂直同步信号时刻
mLastFrameTimeNanos = frameTimeNanos;
}
try {
// 渲染当前帧(传入的frameTimeNanos是最新的垂直同步信号时刻)
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
...
}
...
}
}
doFrame()在绘制帧之前,会计算帧延迟,即当前时刻和帧本该被绘制时刻的差值。
如果帧延迟大于帧间隔,则会修正绘制时刻frameTimeNanos,将其对齐到当前时刻前的一个垂直同步信号时刻。然后该时刻被传入doCallbacks(),作为从任务链上摘取任务的依据,所有在此之前的任务都会被摘下。
此时,任务1、2、3都会被摘下,因为相对于对其后的frameTimeNanos,它们都是过去的任务,所以这次doFrame()的执行会将原本三次doFrame()的绘制任务一并执行,如果绘制任务执行的足够快,就有机会在下一个垂直同步信号到来之前订阅它,这样任务1、2、3的绘制的内容就会在下一帧被展示出来,如下图所示:
当主线程被耗时操作占用时,会发生掉帧,即本该被渲染及展示的帧会错过展示机会。但对于上层来说,什么东西都没有掉,只是本该在不同帧间隔执行的绘制任务被合并在一起执行展示。
如果三个绘制任务很耗时,会发生什么?
这时候,doCallbacks()中的某些条件就会被触发:
public final class Choreographer {
void doFrame(long frameTimeNanos, int frame) {
synchronized (mLock) {
...
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
// 如果延迟大于帧间隔
if (jitterNanos >= mFrameIntervalNanos) {
...
// 计算帧延迟相对于帧间隔的偏移量
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
// 修正帧绘制时刻,将其对齐到最新的垂直同步信号
frameTimeNanos = startNanos - lastFrameOffset;
}
...
// 将上一帧绘制时刻更新为最新垂直同步信号时刻
mLastFrameTimeNanos = frameTimeNanos;
// 如果该帧相对于上一帧来说是旧帧, 跳过当前帧并订阅下一个垂直同步信号
if (frameTimeNanos < mLastFrameTimeNanos) {
scheduleVsyncLocked();
return;
}
}
try {
// 渲染当前帧
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 在输入,动画,View树遍历任务都执行完毕之后,执行 COMMIT 任务
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
...
}
...
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
...
// 如果是 COMMIT 任务
if (callbackType == Choreographer.CALLBACK_COMMIT) {
// 计算帧绘制延迟
final long jitterNanos = now - frameTimeNanos;
// 如果帧绘制延迟 >= 2倍帧间隔
if (jitterNanos >= 2 * mFrameIntervalNanos) {
// 计算帧偏移量并追加一个帧间隔的时长
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos;
// 将mLastFrameTimeNanos对其到当前时刻前的第二个垂直同步信号时刻
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
...
}
}
因为doFrame()执行时间超过了2个帧间隔,当输入、动画、View树遍历任务执行完毕,COMMIT任务执行时,触发了帧时间对其,将mLastFrameTimeNanos往前偏移了一段时间,对其到绘制任务结束时刻前的第二个垂直同步信号时刻。这样做的目的是防止因这次超时绘制而导致更多的掉帧。因为doFrame()在绘制当前帧之前会校验当前帧和上一帧的绘制时刻:
// 如果该帧相对于上一帧来说是旧帧, 跳过当前帧并订阅下一个垂直同步信号
if (frameTimeNanos < mLastFrameTimeNanos) {
scheduleVsyncLocked();
return;
}
如果在上一个doFrame()快执行完时,上层又向Choreographer抛了若干绘制任务,这些绘制任务的绘制时刻frameTimeNanos必然小于上一帧绘制结束时刻mLastFrameTimeNanos,若不执行COMMIT任务将mLastFrameTimeNanos向前对其,则新的绘制任务将会错过一次执行时机。
在这里我也分享一份几位大佬一起收录整理的Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
如果你有需要的话,可以私信我【资料】我发给你
喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗~
相关推荐
- 俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)
-
发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...
- 如何强制所有流量使用 HTTPS一网上用户
-
如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...
- https和http的区别(https和http有何区别)
-
“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...
- 快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别
-
什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...
- 简述HTTPS工作原理(简述https原理,以及与http的区别)
-
https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...
- 21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)
-
HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...
- 一次安全可靠的通信——HTTPS原理
-
为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...
- 为什么有的网站没有使用https(为什么有的网站点不开)
-
有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...
- HTTPS是什么?加密原理和证书。SSL/TLS握手过程
-
秘钥的产生过程非对称加密...
- 图解HTTPS「转」(图解http 完整版 彩色版 pdf)
-
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...
- HTTP 和 HTTPS 有何不同?一文带你全面了解
-
随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...
- HTTP与HTTPS的区别,详细介绍(http与https有什么区别)
-
HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...
- 一文让你轻松掌握 HTTPS(https详解)
-
一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...
- 如何在Spring Boot应用程序上启用HTTPS?
-
HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...
- 一文彻底搞明白Http以及Https(http0)
-
早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
tinymce 号称富文本编辑器世界第一,大家同意么?
-
- 最近发表
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)