1. 程式人生 > >Fresco圖片框架內部實現原理探索

Fresco圖片框架內部實現原理探索

流行的網路框架

目前流行的網路圖片框架: 
Picasso、Universal Image Loader、Volley的(ImageLoader、NetworkImageView)、Glide和Fresco

簡明的介紹下(具體細節和功能可看原始碼和wiki): 
其中Picasso和Universal Image Loader相比其它的算是最輕量級的圖片框架了,它們擁有較少的方法數,Universal Image Loader是這五個框架中定製性最強的,它內部實現還是按網路框架的套路走:HttpUrlConnection+執行緒池+Handler,支援漸顯效果。 
而Picasso只有一些圖片載入框架應有的基本功能,所以因此它是最輕量的,在需求只要基本的圖片載入與雙快取功能下,可以選Picasso作為專案的基礎庫,Picasso它內部預設是使用OkHttpClient作為載入網路圖片的下載器,畢竟不用自家用誰的,在OkHttpClient沒有的情況下則使用HttpUrlConnection,同上面一樣,下載器+執行緒池+Handler,不過它內部的執行緒池比較有意思,執行緒池的執行緒數量是根據當前的網路環境來動態改變的,wifi網路下為4,4G為3,3G為2,2G為1,其它情況下預設為3,支援漸顯效果。 
Volley的沒什麼可說的,基本功能都有,網路框架的附贈功能。

Glide的話,Google官方推薦,支援Gif、圖片縮圖、本地視訊解碼、請求和動畫生命週期的自動管理、漸顯動畫、支援OkHttp和Volley等等,預設是使用HttpUrlConnection載入圖片的,原始碼灰常多,200多個類,不想看

Fresco我認為是這幾個框架中效能最佳的一個框架,著重介紹,它內部用了大量的建造者模式、單例模式、靜態工廠模式、生產/消費者模式。內部實現比較複雜,就拿圖片載入來說,是通過在非同步執行緒中回撥圖片的輸入流,然後通過一系列讀取、寫入、轉化成EncodedImage,然後再Decode成Bitmap,通過Handler轉給UI執行緒顯示,通過IO操作儲存在硬碟快取目錄下。

Fresco效能上的優點

優一

1、支援webp格式的圖片,是Google官方推行的,它的大小比其它格式圖片的大小要小一半左右,目前各個大公司都漸入的使用這種圖片格式了,比如:Youtube、Gmail、淘寶、QQ空間等都已嚐鮮,使用該格式最大的優點就是輕量、省流量、圖片載入迅速。而Fresco是通過jni來實現支援WebP格式圖片。

優二

2、5.0以下系統:使用”ashmem”(匿名共享記憶體)區域儲存Bitmap快取,這樣Bitmap物件的建立、釋放將永遠不會觸發GC,關於”ashmem”儲存區域,它是一個不在Java堆區的一片儲存記憶體空間,它的管理由

Linux核心驅動管理,不必深究,只要知道這塊儲存區域是別於堆記憶體之外的一塊空間就行了,且這塊空間是可以多程序共享的,GC的活動不會影響到它。5.0以上系統,由於記憶體管理的優化,所以對於5.0以上的系統Fresco將Bitmap快取直接放到了堆記憶體中。

關於”ashmem”的儲存區域,我們的應用程式並不能像訪問堆記憶體一樣直接訪問這塊記憶體塊,但是也有一些例外,對於Bitmap而言,有一種為”Purgeable Bitmap”可擦除的Bitmap點陣圖是儲存在這塊記憶體區域中的,BitmapFactory.Options中有這麼一個屬性inPurgeable

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

所以通過配置inPurgeable = true這個屬性,這樣解碼出來的Bitmap點陣圖就儲存在”ashmem”區域中,之後用到”ashmem”中得圖片時,則把這個圖片從這個區域中取出來,渲染完畢後則放回這個位置。

既然Fresco中Bitmap快取在5.0以前是放在”ashmem”中,GC並不會回收它們,且也不會被”ashmeme”內建的清除機制回收它們,所以這樣雖然使得在堆中不會造成記憶體洩露,而在這塊區域可能造成記憶體洩露,Fresco中採取的辦法則是使用引用計數的方式,其中有一個SharedReference這個類,這個類中有這麼兩個方法:addReference()和deleteReference(),通過這兩個基本方法來對引用進行計數,一旦計數為零時,則對應的資源將會清除(如:Bitmap.recycle()等),而Fresco為了考慮更容易被我們使用,又提供了一個CloseableReference類,該類可以說是SharedReference類上功能的封裝,CloseableReference同時也實現了Cloneable、Closeable介面,它在呼叫.clone()方法時同時會呼叫addReference()來增加一個引用計數,在呼叫.close()方法時同時會呼叫deleteReference()來刪除一個引用計數,所以在使用Fresco的使用,我們都是與CloseableReference類打交道,使用CloseableReference必須遵循以下兩條規則: 
1、在賦值CloseableReference給新物件的時候,呼叫.clone()進行賦值 
2、在超出作用域範圍的時候,必須呼叫.close(),通常會在finally程式碼塊中呼叫

void gee() {
  CloseableReference<Val> ref = foo();
  try {
    haa(ref);
  } finally {
    ref.close();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

遵循這些規則可以有效地防止記憶體洩漏。

優三

3、使用了三級快取:Bitmap快取+未解碼圖片快取+硬碟快取。 
其中前兩個就是記憶體快取,Bitmap快取根據系統版本不同放在了不同記憶體區域中,而未解碼圖片的快取只在堆記憶體中,Fresco分了兩步做記憶體快取,這樣做有什麼好處呢?第一個好處就如上的第二條,第二個好處是加快圖片的載入速度,Fresco的載入圖片的流程為:查詢Bitmap快取中是否存在,存在則直接返回Bitmap直接使用,不存在則查詢未解碼圖片的快取,如果存在則進行Decode成Bitmap然後直接使用並加入Bitmap快取中,如果未解碼圖片快取中查詢不到,則進行硬碟快取的檢查,如有,則進行IO、轉化、解碼等一系列操作,最後成Bitmap供我們直接使用,並把未解碼(Encode)的圖片加入未解碼圖片快取,把Bitmap加入Bitmap快取中,如硬碟快取中沒有,則進行Network操作下載圖片,然後加入到各個快取中。

既然Fresco使用了三級快取,而有兩級是記憶體快取,所以當我們的App在後臺時或者在記憶體低的情況下在onLowMemory()方法中,我們應該手動清除應用的記憶體快取,我們可以使用下面的方式:

        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        //清空記憶體快取(包括Bitmap快取和未解碼圖片的快取)
        imagePipeline.clearMemoryCaches();
        //清空硬碟快取,一般在設定介面供使用者手動清理
        imagePipeline.clearDiskCaches();

        //同時清理記憶體快取和硬碟快取
        imagePipeline.clearCaches();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

優四

4、Fresco框架的ImagePipeline設計圖 
這裡寫圖片描述 
從設計圖中可以看出,UIThread只做圖片的顯示和從記憶體快取中載入圖片這兩件事,而其它事情如:圖片的Decode、記憶體快取的寫、硬碟快取的IO操作、網路操作等都用非UIThread來處理了,這使得UIThread專注介面的顯示,而其它工作由其它執行緒完成,使UI更加流暢。

Fresco中的MVC模式

Fresco框架整體是一個MVC模式

DraweeView——View 
DraweeController——Control 
DraweeHierarchy——Model

它們之間的關係大致如下: 
DraweeHierarchy意為檢視的層次結構,用來儲存和描述圖片的資訊,同時也封裝了一些圖片的顯示和檢視層級的方法。 
DraweeView用來顯示頂層檢視(getTopLevelDrawable())。DraweeController控制載入圖片的配置、頂層顯示哪個檢視以及控制事件的分發。 
【注】DraweeView目前版本時繼承於ImageView,但這並不意味著我們可以隨意的使用ImageView相關的方法(如:setScaleType等),官方並不建議我們使用,因為後期DraweeView將繼承於View,所以最好只使用DraweeView控制元件內建的方法。

DraweeHierarchy

DraweeHierarchy除了描述了檢視的資訊和儲存6種檢視外,其中還對我們提供了一些額外的方法,比如:讓圖片漸漸顯示Fade效果、設定預設狀態下顯示的圖片、設定點選時顯示的圖片和載入失敗時顯示的圖片等方法,這些方法可以在我們載入其它圖片時保持一些良好的互動效果,值得注意的是,DraweeHierarchy是一個介面,只提供了一個預設方法:

public interface DraweeHierarchy {

  /**
   * Returns the top level drawable in the corresponding hierarchy. Hierarchy should always have
   * the same instance of its top level drawable.
   * @return top level drawable
   */
  Drawable getTopLevelDrawable();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這個方法從官方註釋上來看,是得到當前檢視中最頂層的那個Drawable,如: 
下面這個檢視層次最頂層的檢視為FadeDrawable

   o FadeDrawable (top level drawable)
   |
   +--o ScaleTypeDrawable
   |  |
   |  +--o BitmapDrawable
   |
   +--o ScaleTypeDrawable
      |
      +--o BitmapDrawable
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

所以,在Fresco框架中,並不是一個DraweeView只能設定一個圖片(Drawable),而是可以設定一個檢視圖層(類似Android中的LayerDrawable可以設定檢視疊加),然後通過DraweeHolder在不同狀態下得到(getTopLevelDrawable())最頂層那個圖片從而使得DraweeView顯示不同的檢視,比如下面這個按下時顯示一個overlay(頂層)圖片效果: 
這裡寫圖片描述 
我想這也是Facebook不想把DraweeView這個元件單純的定義為一個的ImageView的原因吧。

DraweeView

DraweeView是官方給我們提供顯示圖片的一個基類,我們在使用過程中大多時候並不需要用到它,而是用到一個官方已經簡單封好的SimpleDraweeView類,DraweeView類中提供了與DraweeController和DraweeHierarchy互動的介面,而與它們之間的互動本質上是通過一個DraweeHolder類進行互動,這類DraweeHolder是協調DraweeView、DraweeHierarchy、DraweeController這三個類互動工作的核心類,像平時我們都會這樣使用:

比如:配置一個DraweeHierarchy簡便起見通常會使用SimpleDraweeView直接設定:

SimpleDraweeView simpleDraweeView = (SimpleDraweeView) findViewById(R.id.drawee_view);
        Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
        simpleDraweeView.setImageURI(uri);
        //建立一個DraweeHierarchy
        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()).setPlaceholderImage(getDrawable(R.drawable.holder)).build();
        //設定一個hierarchy
simpleDraweeView.setHierarchy(hierarchy);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其實上述方法最終都是通過mController.setHierarchy(hierarchy);來設定的,本質是呼叫DraweeHolder類封裝好的setHierarchy()方法,可以看看其內部: 
DraweeHolder::setHierarchy()

  public void setHierarchy(DH hierarchy) {
    //...
    if (mController != null) {
      mController.setHierarchy(hierarchy);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

所以通過simpleDraweeView.setHierarchy(hierarchy);來設定等價於通過構建一個DraweeController來設定,如下:

        DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(url).setXxx()...build();
        //通過DraweeController設定
        controller.setHierarchy();
        simpleDraweeView.setController(controller);
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

這個DraweeHolder類的出現,更準確的定位是降低耦合度、解耦的定位。

對於上面我們知道了通過simpleDraweeView.setHierarchy(hierarchy);來設定等價於通過DraweeController::setHierarchy()來設定,那麼simpleDraweeView.setController(controller);有沒有等價的呢?答案是沒有,因為要載入圖片那麼就必須設定一個DraweeController來控制圖片的載入,(當然我們如果設定了SimpleDraweeView的一些屬性,那麼預設也會建立一個DraweeHierarchy),而我們平時簡便的寫法:

SimpleDraweeView simpleDraweeView = (SimpleDraweeView) findViewById(R.id.drawee_view);
        Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
        simpleDraweeView.setImageURI(uri);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

這是一段最基本的寫法,我們都知道這樣設定了Fresco內部就自動會給我們載入圖片了,而網上也流傳著另外一種載入圖片的方法,為:

        DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(url).build();
        simpleDraweeView.setController(controller);
  • 1
  • 2
  • 1
  • 2

其實這兩種寫法都是一種寫法,Fresco真正載入圖片僅僅只有這一種方法,就是通過simpleDraweeView.setController(controller);來設定,只不過我們可以對DraweeController和DraweeHierarchy做各種各樣的配置來達到我們想要的效果,我們可以看看simpleDraweeView.setImageURI(uri);的原始碼,其實還是通過setController(controller);設定一個控制器來控制圖片的載入,原始碼為:

  public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mSimpleDraweeControllerBuilder
        .setCallerContext(callerContext)
        .setUri(uri)
        .setOldController(getController())
        .build();
    setController(controller);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所以,使用Fresco通用的寫法便是:

        SimpleDraweeView simpleDraweeView = (SimpleDraweeView) findViewById(R.id.drawee_view);
        Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");

        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
                .setFadeDuration(400)
                .setPlaceholderImage(getDrawable(R.drawable.holder))
                .setFailureImage(getDrawable(R.drawable.fail))
                .build();

        DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(uri).setOldController(simpleDraweeView.getController()).build();
        controller.setHierarchy(hierarchy);

        simpleDraweeView.setController(controller);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

或者直接使用ImageRequet:

    ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).build();
    DraweeController draweeController = Fresco.newDraweeControllerBuilder().setImageRequest(imageRequest).setOldController(simpleDraweeView.getController()).build();
    simpleDraweeView.setController(controller);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

當然配置其它屬性只要設定Controller和Hierarchy相應的方法即可。 
】:上面中用到了一個這個方法simpleDraweeView.getController();當然也還有simpleDraweeView.getHierarchy();,這兩個方法是返回當前DraweeView所設定的Control和Hierarchy,使用這兩個方法的好處是複用以前建立的Control和Hierarchy物件,因為重新建立一個物件肯定不如複用好,而且建立相對耗時,所以官方也建議我們複用這兩個物件。如果你用simpleDraweeView.getHierarchy()來載入圖片,那麼它將不可能為空,除非你什麼不設定,而getController()則可能為空,所以在使用工廠方法

Fresco.newDraweeControllerBuilder().setUri(uri).setOldController(simpleDraweeView.getController()).build();
  • 1
  • 1

來建立一個DraweeController的時候給它配置一個setOldController(),如果這裡面這個引數為null,那麼就會重新建立一個DraweeController,如果不為空則複用當前傳入的。

順便說一句,直接使用SimpleDraweeView就足夠了,畢竟配置功能都落在DraweeController和DraweeHierarchy身上,SimpleDraweeView僅僅起個顯示最頂層檢視的作用。

DraweeController

關於DraweeController,好像在上面已經講的差不多了,它主要就是起個控制圖片的載入和配置以及決定頂層顯示哪個檢視的作用,其它的它也可以設定設定個ControllerListener來監聽圖片載入的進度,也可以配置一個ImageRequest來設定漸進式JPEG圖片的載入,具體使用可以看其官方文件。

認識Fresco中的檢視層次

 *     o FadeDrawable (top level drawable)
 *     |
 *     +--o ScaleTypeDrawable
 *     |  |
 *     |  +--o Drawable (placeholder image)
 *     |
 *     +--o ScaleTypeDrawable
 *     
            
           

相關推薦

Fresco圖片框架內部實現原理探索

流行的網路框架 目前流行的網路圖片框架:  Picasso、Universal Image Loader、Volley的(ImageLoader、NetworkImageView)、Glide和Fresco 簡明的介紹下(具體細節和功能可

@Autowired內部實現原理

dao boolean ice fields ann 打印 sys ava sta @Autowiredprivate CustomerDao customerDao; public void addCustomer() { customerDa

HashMap內部實現原理

   HashMap的底層是基於陣列+連結的一個複合資料結構,非同步的 允許null鍵值 繼承於map介面來實現,通過put和get方法來進行資料的操作.陣列被分為一個個的bucket.雜湊值決定了鍵值對在陣列中的位置.具有相同雜湊值的鍵值對會組成連結串列,當連結串列長度超過

Fresco圖片框架

優一:        1、支援webp格式的圖片,是Google官方推行的,它的大小比其它格式圖片的大小要小一半左右,使用該格式最大的優點就是輕量、省流量、圖片載入迅速。而Fresco是通過jni來實現支援WebP格式圖片。 優二:        2、5.0以下系統:使用”ashm

JAVA中的集合原始碼分析一:ArrayList的內部實現原理

作為以java為語言開發的android開發者,集合幾乎天天都要打交道,無論是使用頻率最高的ArrayList還是HashSet,都頻繁的出現在平時的工作中。但是其中的原理之前卻一直沒深入探究,接下來記錄一下這次自己學習ArrayList原始碼的過程。 一.構造方法:

UE4移動元件詳解(一)——移動框架實現原理

前言 關於UE4的移動元件,我寫了一篇非常詳細的分析文件。由於篇幅比較大,我將其拆分成三個部分。分別從移動框架與實現原理,移動的網路同步,移動元件的優化與改造三個方面來寫。這三篇文件中難免有問題和漏洞,所以我也會在發現問題時及時更新和修改,也希望大家能給出一些

從原始碼的角度來談一談HashMap的內部實現原理

HashMap可以說是我們一個熟悉又陌生的Java中常用的儲存資料的API。說他熟悉,是因為我們經常使用他,而說他陌生是因為我們大部分時間是隻知道他的使用,而並不知道他內部的原理,但是在面試考察的時候又最喜歡去問這個原理。今天,我就來從原始碼的角度,談談對HashMap的理解

深入Java 1.5列舉型別的內部實現原理

Java是一種面向物件的高階程式語言。它的出眾之處就在於它的簡潔。一個程式設計師所要做的就是建立類(Create Class)以及定義介面(Define Interface),如此而已。當然,這種簡潔和優美是有代價的,比如失去了Enum這種廣泛使用的資料型別就是一個不小的損

Android Bundle App內部實現原理

core中的程式碼 首先我們在程式碼中呼叫了 manager = SplitInstallManagerFactory.create(this) 1.SplitInstallManagerFactory.class public class SplitIn

基於.net搭建熱插拔式web框架實現原理

第一節:我們為什麼需要一個熱插拔式的web框架? 模組之間獨立開發 假設我們要做一個後臺管理系統,其中包括“使用者活躍度”、“產品管理”、"賬單管理"等模組。每個模組中有自己的業務特性,這些模組都與具體業務高度耦合,很難由一個團隊開發完所有模組。這樣看來,由資料事業部的同事來開發“使用者活躍度”模

IOS SDWebImage內部實現原理

想必大家都很熟悉SDWebImage了,專案中也經常用。可大家知道它的實現原理嗎?今天就跟大家分享一下。 先看一下下面這幅圖: 圖片解釋:記憶體層面的相當是個快取器,以Key-Value的形式儲存圖片。當記憶體不夠的時候會清除所有快取圖片。用搜索檔案系統

[App探索]JSBox中幽靈觸發器的實現原理探索

前言 幽靈觸發器是鍾穎大神的JSBox中的一個功能,在app程序被殺死的情況下,也可以將通知固定在通知欄,即便使用者點選清除,也能馬上再彈出,永遠不消失,除非使用者關閉App的通知許可權或者解除安裝App,才可以消失。這個功能確實比較有意思,而且鍾穎大神在介紹視訊裡有提到是目前JSBox獨有的,說明實現得非

我理解的MVCC內部實現原理

  MySQL InnoDB儲存引擎,實現的是基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基於鎖的併發控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖

Jquery 圖片輪播實現原理總結

以前要做圖片輪播效果的時候,總是在網上找一段jquery的複製貼上進去,只索取不奉獻,今個就把我對這個的實現原理講解一下。 首先說下,我在網上找的例子全是用的UL 實現,其實大可不必,只要是能包含img標籤的HTML標籤都可以做輪播效果。利用jquery的淡入淡出函式(fadeIn和fadeOut)。廢

C++ vector的內部實現原理及基本用法

本文基於STL vector原始碼,但是不考慮分配器allocator,迭代器iterator,異常處理try/catch等內容,同時對_Ucopy()、 _Umove()、 _Ufill()函式也不會過度分析。一、vector的定義template<class _Ty

web 前端圖片懶載入實現原理

前端時間面試的時候老是被問到圖片懶載入實現及原理,由於自己在實際專案中並沒有用過,只是瞭解過大概,所以回答起來都不盡如人意,趁這段時間空閒下來有時間好好研究下,話不多說,直奔主題~ 一、html(這裡只列出相關的結構,body那些就不列了~) <ul>

Java併發(4)深入分析java執行緒池框架實現原理(一)

先說說我個人對執行緒池的理解:執行緒池顧名思義是一個裝有很多執行緒的池子,這個池子維護著從執行緒建立到銷燬的怎個生命週期以及執行緒的分配,使用者只需要把任務提交給這個執行緒池而不用去關心執行緒池如何建立執行緒,執行緒池會自己給這些任務分配執行緒資源來完成任務。 java的E

個人對rand()、srand()函式之間的關係及其內部實現原理的猜測

歡迎轉載,轉載請註明出處,謝謝 //首先我們先只看rand()函式,有函式如: int main(int argc,char* argv[]) { int tmp=0; for(int i=0;i<50;++i) { // 獲取 0~19 的偽隨機數,存入t

分析電商網站圖片放大的實現原理

先分析下大致原理,放大原理實際上是一張正常圖片,再加上一張放大n倍圖片,根據滑鼠在正常圖片上的位置,再將位置乘n倍,定位到放大圖片,顯示。 先來看看效果 <!DOCTYPE html

Struts2框架實現原理和工作流程

Struts2為提供了一個為使用者快速構建應用程式的平臺。 Struts2是基於OpenSymphony的網路工程框架。 Struts2實現模型檢視控制器(MVC)設計模式。 在Struts2的模型、檢視和控制器中分別實現了Action,result和FilterDispa