Tomcat架構深度解析:從Server到Servlet的全流程揭秘

第一章:Tomcat架構概述


1.1 Tomcat的角色與定位:Web服務器 vs Servlet容器

Tomcat 是什么?它既是一種輕量級 Web 服務器,也是一種符合 Java EE 規范的 Servlet 容器。

  • Web服務器:類似 Nginx、Apache HTTP Server,處理靜態資源請求(如 HTML、CSS、JS)。

  • Servlet容器:它能解析 Java Web 應用,執行 Servlet 邏輯,是 J2EE 架構中的核心組件。

Tomcat 專注于 Servlet/JSP 執行環境,是大多數 Java Web 項目的默認運行平臺。你可以理解為:

🧠 “Nginx 負責搬運磚(靜態內容),而 Tomcat 負責燒菜(動態內容)。”


1.2 核心功能:網絡連接器(Connector)與Servlet容器(Container)

Tomcat 架構的設計核心是 分離連接(Connector)與處理(Container)

  • Connector 負責“接收請求”:它監聽端口、解析協議(如 HTTP/AJP),把原始 Socket 請求轉換成 Java 對象(如 ServletRequest)。

  • Container 負責“處理請求”:它解析 URL、找到對應的 Servlet、執行業務邏輯并返回響應。

兩者通過 Service 組件進行綁定,形成完整的請求處理路徑。


1.3 架構圖描述(文字形式)

用文字描述 Tomcat 的核心架構圖,幫助建立層級結構的直觀印象:

┌────────────────────┐
│      Server        │  ← Tomcat 最頂層組件,負責整體生命周期
└────────┬───────────┘│┌─────▼─────┐│   Service │  ← 每個 Server 可包含多個 Service└─────┬─────┘│┌───────▼────────┐│   Connector    │ ← 監聽端口,接收并轉換 HTTP/AJP 請求└────────┬───────┘│┌───────▼────────────┐│     Engine         │ ← 請求處理的核心入口,屬于 Container└──────┬─────────────┘│┌─────▼─────┐│   Host    │ ← 虛擬主機,用于支持多域名部署└─────┬─────┘│┌────▼─────┐│ Context  │ ← 每個 Web 應用一個 Context(對應一個 WAR 包)└────┬─────┘│┌───▼────┐│Wrapper │ ← 每個 Servlet 一個 Wrapper,最終執行點└────────┘

從上圖可見,請求從最底層的 Connector 發起,最終由 Wrapper 調用 Servlet 實現類處理業務邏輯。這就是 Tomcat 的核心處理鏈路。

第二章:核心組件詳解


2.1 Server組件:管理Tomcat實例的生命周期

Server 是 Tomcat 的頂級組件,代表整個 Tomcat 實例,它的職責是控制整個服務的生命周期。

  • 代表類org.apache.catalina.core.StandardServer

  • 主要職責

    • 統一管理所有 Service

    • 監聽 SHUTDOWN 命令端口(默認8005),優雅關閉。

    • 觸發 init(), start(), stop() 生命周期方法。

💡 類比理解:

Server 就像是一個酒店的總經理,下面每個 Service 是一個功能部門,比如前臺、后廚、客房管理。

🔍 架構圖描述(Server層):
┌────────────────────┐
│      Server        │
│ 監聽8005關閉端口   │
│ 管理多個Service     │
└────────┬───────────┘↓[多個Service]

2.2 Service組件:整合Connector與Engine

Service 是連接請求(Connector)和業務處理(Engine)的橋梁。

  • 代表類org.apache.catalina.core.StandardService

  • 主要職責

    • 一個 Service 包含:

      • 一個 Engine(處理業務邏輯)

      • 一個或多個 Connector(接收外部請求)

    • 多個 Connector 可以綁定同一個 Engine,實現多協議共享邏輯處理。

💡 類比理解:

Service 就像酒店里的“接待部門”:門童(Connector)負責迎客,帶到接待柜臺(Engine)處理入住流程。

🔍 架構圖描述(Service層):
┌──────────────────────────┐
│          Service         │
│ ┌─────────────────────┐ │
│ │       Engine         │ │ ← 業務處理核心
│ └─────────────────────┘ │
│  ┌────────────┐  ┌────────────┐
│  │ Connector1 │  │ Connector2 │ ← 多個端口或協議接入
│  └────────────┘  └────────────┘
└──────────────────────────┘

2.3 Connector組件:協議解析與請求轉發

Connector 負責與客戶端打交道,是 Tomcat 與外部世界的接口。

  • 代表類org.apache.coyote.http11.Http11NioProtocol

  • 職責

    • 監聽指定端口(如 8080)。

    • 解析 HTTP 或 AJP 協議,轉換為 Request/Response 對象。

    • 將請求傳入對應的 Engine 繼續處理。

🌐 支持的協議實現:
模式類名特點
BIOHttp11Protocol同步阻塞,低性能
NIOHttp11NioProtocol異步非阻塞,推薦
APRHttp11AprProtocol / AjpAprProtocol高性能,依賴本地庫
NIO2Http11Nio2ProtocolNIO 的改進版
🧪 示例:配置 NIO Connector(在server.xml中)
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"connectionTimeout="20000"maxThreads="200" />

2.4 Container組件:Servlet容器的分層結構

Container 是 Tomcat 的核心處理器,負責執行 Servlet 邏輯。

它包含 4 層結構,層層包裹,類似俄羅斯套娃:

層級代表類作用
EngineStandardEngineService 中唯一的業務處理容器
HostStandardHost虛擬主機,支持多域名部署
ContextStandardContext一個 Web 應用對應一個 Context
WrapperStandardWrapper每個 Servlet 一個 Wrapper
💡 類比理解:

Container 像一棟辦公樓:

  • Engine 是大樓

  • Host 是樓層(不同租戶)

  • Context 是部門

  • Wrapper 是員工(Servlet)

🔍 架構圖描述(Container層):
Engine└── Host (域名)└── Context (Web應用)└── Wrapper (Servlet)

? 本章小結

  • Server 管理整個 Tomcat 實例生命周期。

  • Service 是連接外部請求(Connector)與內部業務(Engine)的橋梁。

  • Connector 接收客戶端請求并解析協議。

  • Container 是核心執行單元,包含 Engine → Host → Context → Wrapper 四級結構。

第三章:請求處理流程


3.1 請求到達 Connector 的流程(Socket → ServletRequest)

  1. 瀏覽器發送 HTTP 請求
    例如訪問 http://localhost:8080/demo/hello,TCP 三次握手后,請求數據會到達 Tomcat 監聽的端口(默認 8080)。

  2. Connector 接收請求

    • 對應類:org.apache.coyote.http11.Http11NioProtocol

    • 監聽線程(Acceptor)接收連接請求,并交給 Poller 線程注冊到 Selector(NIO 模型)。

  3. 協議解析

    • 使用 Http11Processor 解析 HTTP 協議。

    • 將解析結果封裝成 org.apache.coyote.Requestorg.apache.coyote.Response 對象。

  4. 適配成 Servlet API

    • CoyoteAdapter 將底層 Request/Response 轉換為 HttpServletRequestHttpServletResponse,進入容器處理流程。

🔍 流程圖(文字版):
瀏覽器 → TCP連接 → Connector監聽端口↓Acceptor線程接收連接↓Poller/Processor解析HTTP↓封裝為Request/Response↓CoyoteAdapter適配到Servlet API

3.2 Mapper組件的URL映射機制

Mapper 的作用是根據 URL 找到正確的 Servlet。

  • 匹配規則(從粗到細):

    1. 匹配 Host(域名)

    2. 匹配 Context(Web 應用路徑)

    3. 匹配 Wrapper(Servlet 映射規則)

例如訪問 http://localhost:8080/demo/hello

  • Host:localhost

  • Context:/demo

  • Wrapper:匹配到 /hello 的 Servlet

關鍵類:
  • org.apache.catalina.mapper.Mapper

  • org.apache.catalina.core.StandardHost

  • org.apache.catalina.core.StandardContext


3.3 Pipeline-Valve機制:請求過濾與處理鏈

Tomcat 的容器(Engine、Host、Context、Wrapper)都有一個 Pipeline(管道),里面裝著多個 Valve(閥門)。

  • Pipeline:請求處理的有序鏈路。

  • Valve:具體的處理步驟,例如日志記錄、安全檢查、壓縮等。

  • 基本原則:請求會沿著 Valve 鏈從上到下傳遞,最終交給 Servlet 處理。

示例:默認Valve鏈
EnginePipeline→ HostPipeline→ ContextPipeline→ WrapperPipeline→ StandardWrapperValve(最終調用Servlet.service())
可自定義Valve示例:
public class MyLogValve extends ValveBase {@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {System.out.println("請求URI: " + request.getRequestURI());getNext().invoke(request, response); // 繼續下一個Valve}
}

server.xml 中注冊即可生效。


3.4 Servlet的加載與執行(Wrapper → Servlet實例)

  1. Wrapper找到Servlet
    Mapper 找到的目標是某個 Wrapper,Wrapper 中保存了 Servlet 的配置信息。

  2. Servlet加載

    • 如果 Servlet 未被加載,StandardWrapper 會調用 loadServlet() 創建并初始化 Servlet 實例(調用 init() 方法)。

  3. 執行Servlet

    • 最終由 StandardWrapperValve 調用 Servlet.service(),根據請求方法分發到 doGet()doPost() 等方法。

  4. 返回響應

    • Servlet 處理完成后,將數據寫入 HttpServletResponse,由 Connector 發送回客戶端。


? 本章小結

  • Connector:接收 Socket 連接并解析協議。

  • CoyoteAdapter:適配成 Servlet API。

  • Mapper:根據 URL 定位到具體 Servlet。

  • Pipeline-Valve:處理鏈路,可擴展。

  • Wrapper:管理 Servlet 的生命周期并調用其方法。

第四章:性能優化與調優


4.1 I/O模型選擇與性能對比

Tomcat 支持多種 I/O 模型,選擇合適的模型是性能優化的第一步。

I/O 模型協議類名特點適用場景
BIO(阻塞I/O)Http11Protocol簡單穩定,但每個請求一個線程,連接多時性能差老系統、小并發
NIO(非阻塞I/O)Http11NioProtocol單線程管理多個連接,性能好,JDK自帶推薦默認
NIO2(異步I/O)Http11Nio2ProtocolJDK7+,AIO模型,適合高并發高吞吐場景
APR(本地庫)Http11AprProtocol使用Apache Portable Runtime,接近C語言性能需要原生庫,追求極限性能

切換示例server.xml):

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"maxThreads="500" connectionTimeout="20000"/>

4.2 線程池配置調優策略

Tomcat 的 Connector 內部有線程池,決定了同時能處理多少請求。

  • 核心參數

    • maxThreads:最大工作線程數(默認200)

    • minSpareThreads:啟動時的最小空閑線程數(默認10)

    • acceptCount:隊列長度,滿了會拒絕請求(默認100)

    • connectionTimeout:連接超時時間(毫秒)

調優思路

  1. 根據 CPU 核數和業務特性,計算合適的線程數(CPU 密集型:2×核數;I/O 密集型:更高)。

  2. 壓測觀察線程池是否飽和,必要時增加 acceptCount 避免拒絕連接。

  3. 調短 connectionTimeout 以減少無效連接占用。

配置示例

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"maxThreads="800"minSpareThreads="50"acceptCount="300"connectionTimeout="15000"/>

4.3 內存泄漏問題與解決方案

Tomcat 在長時間運行中,可能因類加載器或未關閉的資源造成 PermGen/Metaspace 泄漏

常見原因:

  • Web 應用熱部署后,老的 ClassLoader 未釋放。

  • JDBC 連接、線程池、定時任務未關閉。

  • 靜態集合引用持有大對象。

解決策略:

  • 禁用頻繁熱部署,生產中使用全量重啟。

  • ServletContextListener.contextDestroyed() 中手動關閉資源。

  • 啟用 org.apache.catalina.loader.WebappClassLoaderBase 的內存泄漏檢測日志:

    <Context reloadable="false"><Loader leakDetection="true"/>
    </Context>
    

4.4 高并發場景下的配置優化案例

假設業務是一個 高并發API服務,每天有數百萬請求,可以做如下優化:

  1. 啟用NIO模型,提升多連接處理能力。

  2. 加大線程池

    maxThreads="1000" minSpareThreads="100" acceptCount="500"
    

  3. 壓縮響應(減少網絡傳輸量):

    compression="on" compressionMinSize="1024"
    compressableMimeType="text/html,text/xml,text/plain,application/json"
    

  4. Keep-Alive優化

    maxKeepAliveRequests="100" keepAliveTimeout="5000"
    

  5. 反向代理配合(Nginx + Tomcat):

    • Nginx 負責 SSL 終端和靜態資源。

    • Tomcat 專注處理動態請求,減少負載。

第五章:實戰案例與代碼示例


5.1 自定義 Valve 實現請求日志記錄

場景:我們希望記錄每個 HTTP 請求的 URI 和處理耗時,這可以幫助排查性能問題。

代碼實現
server.xml 中注冊:
package com.example.tomcat;import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.ValveBase;import javax.servlet.ServletException;
import java.io.IOException;public class MyLogValve extends ValveBase {@Overridepublic void invoke(Request request, Response response) throws IOException, ServletException {long start = System.currentTimeMillis();String uri = request.getRequestURI();System.out.println("[MyLogValve] 請求URI: " + uri);// 調用下一個Valve或最終的ServletgetNext().invoke(request, response);long duration = System.currentTimeMillis() - start;System.out.println("[MyLogValve] 請求耗時: " + duration + "ms");}
}

這樣,所有到 localhost 的請求都會被我們的日志 Valve 攔截并記錄。


5.2 server.xml 配置優化示例

假設我們要優化一個高并發 API 服務的 Tomcat:

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true"><Valve className="com.example.tomcat.MyLogValve"/>
</Host>

優化要點

  • NIO 模型:提升連接并發能力。

  • 線程池加大:應對高并發。

  • 響應壓縮:減少網絡帶寬消耗。

  • Keep-Alive 優化:避免連接長時間占用。


5.3 Servlet 生命周期代碼演示

場景:展示 Servlet 的 init()service()destroy() 調用時機。

示例代碼:
package com.example.servlet;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class LifeCycleServlet extends HttpServlet {@Overridepublic void init() throws ServletException {System.out.println("[Servlet] init() - 初始化Servlet");}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {System.out.println("[Servlet] service() - 處理請求: " + req.getMethod());resp.getWriter().write("Hello, this is LifeCycleServlet");}@Overridepublic void destroy() {System.out.println("[Servlet] destroy() - 銷毀Servlet");}
}
web.xml 配置:
<servlet><servlet-name>lifeCycleServlet</servlet-name><servlet-class>com.example.servlet.LifeCycleServlet</servlet-class><load-on-startup>1</load-on-startup>
</servlet><servlet-mapping><servlet-name>lifeCycleServlet</servlet-name><url-pattern>/lifecycle</url-pattern>
</servlet-mapping>

運行結果

  1. 第一次訪問 /lifecycle 時觸發 init()

  2. 每次請求調用 service()

  3. Tomcat 關閉時調用 destroy()

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919078.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919078.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919078.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Java web】HTTP 協議詳解

一、什么是 HTTP&#xff1f;—— 互聯網的 "快遞員"你有沒有想過&#xff0c;當你在瀏覽器輸入www.baidu.com并按下回車時&#xff0c;背后發生了什么&#xff1f;為什么幾秒鐘后就能看到百度首頁&#xff1f;這一切的背后&#xff0c;都離不開一個叫HTTP的 "快…

流式數據服務端怎么傳給前端,前端怎么接收?

01 引言 大模型時代&#xff0c;尤其會話模型為了提高用戶的使用體驗&#xff0c;它不會將所有的數據加載完成一次響應給客戶端&#xff0c;而是通過數據流&#xff0c;一點點的將數據慢慢呈現出來。 正是這種有趣的交互方式一次次將SSE&#xff08;Server Sent Event&#x…

ML307C 4G通信板:工業級DTU固件,多協議支持,智能配置管理

產品概述 ML307C 4G通信板是一款基于中移物聯網ML307C模組的工業級DTU&#xff08;數據傳輸單元&#xff09;產品&#xff0c;專為工業物聯網應用設計。我們的固件支持多種工業協議&#xff0c;具備遠程配置、FOTA升級、數據加密等企業級功能&#xff0c;為您的工業設備提供穩定…

Sublime配置verilog開發環境-具備語法高亮、代碼補全、自定義代碼段及語法檢查等功能,提升FPGA開發效率!

對于在學習FPGA開發之前使用過其他集成開發工具如VS、pycharm、keil或編輯工具如Sublime、VScode、Notepad的朋友&#xff0c;在使用Vivado時可能會像博主一樣感覺自帶編輯器用起來不太舒服&#xff0c;比如不支持語法高亮顯示&#xff0c;不支持代碼自動補全等功能。因次&…

18_基于深度學習的煙霧檢測識別系統(yolo11、yolov8、yolov5+UI界面+Python項目源碼+模型+標注好的數據集)

目錄 項目介紹&#x1f3af; 功能展示&#x1f31f; 一、環境安裝&#x1f386; 環境配置說明&#x1f4d8; 安裝指南說明&#x1f3a5; 環境安裝教學視頻 &#x1f31f; 二、數據集介紹&#x1f31f; 三、系統環境&#xff08;框架/依賴庫&#xff09;說明&#x1f9f1; 系統環…

【計算機網絡架構】混合型架構簡介

引言在當今數字化浪潮席卷全球的背景下&#xff0c;網絡技術正以前所未有的速度迅猛發展&#xff0c;各種網絡架構如雨后春筍般涌現。從早期簡單的總線型、星型架構&#xff0c;到后來的環型、樹型架構&#xff0c;再到如今復雜的網狀型、云計算架構等&#xff0c;每一種架構都…

Hexo 雙分支部署指南:從原理到 Netlify 實戰

Hexo 雙分支部署指南&#xff1a;從原理到 Netlify 實戰 在 Hexo 博客部署中&#xff0c;很多人會困惑于hexo d自動部署與 GitHub 手動提交的區別&#xff0c;以及如何通過雙分支結構優雅地部署到 Netlify。本文將清晰拆解兩種部署方式的核心差異&#xff0c;并手把手教你用雙分…

【數據結構】深入理解單鏈表與通訊錄項目實現

文章目錄一、單鏈表的概念及結構1.1 什么是單鏈表&#xff1f;1.2 節點的組成1.3 單鏈表的特點二、單鏈表的實現2.1 類型定義2.2 基礎工具函數1. 鏈表打印函數2. 節點創建函數2.3 單鏈表的核心操作&#xff08;1&#xff09;插入操作1. 尾插&#xff08;SLTPushBack&#xff09…

《Python學習之字典(一):基礎操作與核心用法》

堅持用 清晰易懂的圖解 代碼語言&#xff0c;讓每個知識點變得簡單&#xff01; &#x1f680;呆頭個人主頁詳情 &#x1f331; 呆頭個人Gitee代碼倉庫 &#x1f4cc; 呆頭詳細專欄系列 座右銘&#xff1a; “不患無位&#xff0c;患所以立。” Python學習之字典&#xff08;…

[安洵杯 2019]Attack

BUUCTF在線評測BUUCTF 是一個 CTF 競賽和訓練平臺&#xff0c;為各位 CTF 選手提供真實賽題在線復現等服務。https://buuoj.cn/challenges#[%E5%AE%89%E6%B4%B5%E6%9D%AF%202019]Attack流量分析題&#xff0c;瀏覽的時候發現攻擊者上傳信息頁面&#xff0c; 直接搜索 flag 就…

復合機器人食品分揀生產線:一體化控制系統引領高效柔性新食代

在食品工業高速發展的今天&#xff0c;面對種類繁多、形態各異的原料分揀需求&#xff0c;以及日益嚴格的衛生安全與效率要求&#xff0c;傳統的固定式分揀設備已難以勝任。復合機器人食品分揀生產線憑借其融合移動&#xff08;AMR&#xff09;與操作&#xff08;機械臂&#x…

二十七、動態SQL

動態SQL介紹動態SQL&#xff1a;if與where標簽動態案例-動態更新EmpMapper&#xff08;接口&#xff09;中對應代碼塊 //動態更新員工public void update2(Emp emp);EmpMapper.xml中對應代碼塊 <!-- 動態更新員工--><update id"update2">update emp<s…

AI可行性分析:數據×算法×反饋=成功

3.1 從場景到AI可行性分析:需求拆解為“數據+算法+反饋” 核心公式: AI可行性 = 數據可獲得性 算法適配性 反饋閉環性 (任一要素為0則需求不可行) 一、傳統需求 vs AI需求本質差異 需求文檔對比(電商案例) 維度 傳統需求文檔(購物車功能) AI需求文檔(商品推薦系…

【圖論】分層圖 / 拆點

大多數都是同一個套路&#xff0c;將圖拆開成幾個圖&#xff0c;每一層都對應著一個不同的狀態&#xff0c;比如把到點 i 的狀態拆成經過了 j 次操作所得的 xx 結果&#xff0c;一般數據不會很大 目前遇到的可分為 3 類&#xff1a; ①.給你最多 k 次操作&#xff0c;求 xx 結…

VS Code配置MinGW64編譯MATIO庫

VS Code 使用 MinGW64 編譯 C 代碼并配置 MATIO 庫的完整步驟 1. 安裝 MSYS2 下載 MSYS2 訪問 MSYS2 官網下載安裝包&#xff08;選擇 x86_64 版本&#xff09;默認安裝路徑&#xff1a;C:\msys64 更新 MSYS2 包數據庫 打開 MSYS2 MinGW 64-bit&#xff08;注意不是 MSYS&…

【前端Vue】使用ElementUI實現表單中可選擇可編輯的下拉框

由于項目在vue的開發框架下&#xff0c;因此使用ElementUI組件庫進行實現。我希望可選擇可編輯的下拉框右側有跟下拉框一樣的箭頭&#xff0c;并且在未輸入任何內容時&#xff0c;點擊該框體會出現選擇列表進行填充數據的選擇&#xff0c;點擊選中數據后列表消失&#xff0c;數…

每日五個pyecharts可視化圖表-line:從入門到精通 (4)

歡迎來到pyecharts折線圖系列的第四篇文章&#xff01;在前三篇中&#xff0c;我們已經掌握了多種折線圖類型&#xff0c;包括基本折線圖、平滑折線圖、雨量流量關系圖、多X軸折線圖、堆疊區域圖和階梯圖等。在本文中&#xff0c;我們將繼續探索五種更高級的折線圖類型&#xf…

MySQL中的字符串函數

目錄 一、字符串【分割】函數&#xff1a;SUBSTRING_INDEX() SUBSTRING_INDEX函數 練習題 統計每種性別的人數 提取博客URL中的用戶名 截取出年齡 SQL83 商品id數據清洗統計 SQL250 查找字符串中逗號出現的次數 二、字符串【截取】函數&#xff1a;SUBSTRING() 基本語…

CodeBuddy IDE深度體驗:AI驅動的全棧開發新時代

在人工智能技術迅猛發展的今天&#xff0c;開發者工具正在經歷一場深刻的變革。騰訊推出的CodeBuddy IDE作為全球首個“產設研一體”的AI全棧高級工程師工具&#xff0c;重新定義了開發者的日常工作流程。 從需求分析到設計、編碼、部署&#xff0c;CodeBuddy通過AI能力將傳統…

實現Android圖片手勢縮放功能的完整自定義View方案,結合了多種手勢交互功能

主要功能特點&#xff1a;支持雙指手勢縮放圖片&#xff0c;通過ScaleGestureDetector實現平滑的縮放效果25雙擊圖片可切換初始大小和中等放大比例16使用Matrix進行圖像變換&#xff0c;保持縮放中心點為手勢焦點位置57自動縮放動畫通過Runnable實現漸進式變化1限制最小和最大縮…