1. 程式人生 > >OkHttp的使用簡介及封裝,實現更簡潔的呼叫

OkHttp的使用簡介及封裝,實現更簡潔的呼叫

最近將專案使用的網路請求庫換成了OkHttp,體驗感覺上升了好幾個檔次啊,-。-,之前專案是好幾年前的,封裝了原生的httpClient,沒有實現非同步請求,每次都要自己開個執行緒,然後再實現退出的時候把執行緒關了,還要實現本地快取,啊,聽起來好麻煩有木有,然後我終於受不了了,自己封裝了下OkHttp(。。。其實這個是好久前寫的程式碼,一直沒機會實際運用,剛好可以當小白鼠)。。。。不廢話了

1.首先,OkHttp本身是有快取這個東西的,只是如果你不去設定,是不起作用的 


client.setCache(new Cache(context.getCacheDir(),maxCacheSize));
.

 
設定快取目錄和快取大小,OkHttp內部是使用LRU來管理快取的 
2.當然,設定了快取和目錄還是不夠的,http請求總該有個過期時間吧,快取是由HTTP訊息頭中的”Cache-control”來控制的,常見的取值有private、no-cache、max-age、must-revalidate等,預設為private。 

倫理片http://www.dotdy.com/

下面是作用:
  • public 所有內容都將被快取
  • private 內容只快取到私有快取中
  • no-cache 所有內容都不會被快取
  • no-store 所有內容都不會被快取到快取或 Internet 臨時檔案中
  • must-revalidation/proxy-revalidation 如果快取的內容失效,請求必須傳送到伺服器/代理以進行重新驗證
  • max-age=xxx (xxx is numeric) 快取的內容將在 xxx 秒後失效, 這個選項只在HTTP 1.1可用, 並如果和Last-Modified一起使用時, 優先順序較高

一般來說我們用到的是no-cache和max-age比較多,既然我們要實現快取,那麼自然就要在我們的每一條的請求頭裡面新增這個屬性,OkHttp提供了Interceptor 攔截器這個東西,做過web應該明白,就是在你一條http請求要傳送之前,攔截下來,做一些處理然後再繼續傳送,因此,我們就可以新增一個攔截器,在請求前把Cache-control:max-age=36000新增到請求頭裡去。

Prettyprint程式碼  收藏程式碼
  1. <code class="hljs java has-numbering">  
  2. Interceptor cacheInterceptor = <span class="hljs-keyword">new</span> Interceptor() {  
  3.             <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> Response <span class="hljs-title">intercept</span>(Chain chain) <span class="hljs-keyword">throws</span> IOException {  
  4.                 Response originalResponse = chain.proceed(chain.request());  
  5.                 <span class="hljs-keyword">return</span> originalResponse.newBuilder()  
  6.                         .removeHeader(<span class="hljs-string">"Pragma"</span>)<span class="hljs-comment">//Pragma:no-cache。在HTTP/1.1協議中,它的含義和Cache-Control:no-cache相同。為了確保快取生效</span>  
  7.                         .header(<span class="hljs-string">"Cache-Control"</span>, String.format(<span class="hljs-string">"max-age=%d"</span>, maxCacheAge))<span class="hljs-comment">//新增快取請求頭</span>  
  8.                         .build();  
  9.             }  
  10.         };  
  11. </code>  

3.嗯,差不多到這一步就已經快完成了,剩下的就是呼叫OkHttp的方法了。

Prettyprint程式碼  收藏程式碼
  1. <code class="hljs avrasm has-numbering">.  
  2. Request<span class="hljs-preprocessor">.Builder</span> requestBuilder = new Request<span class="hljs-preprocessor">.Builder</span>()<span class="hljs-preprocessor">.url</span>(url)<span class="hljs-preprocessor">.cacheControl</span>(cacheControl)<span class="hljs-comment">;</span>  
  3. .</code>  

一個OkHttp的請求大致是這樣子的,url是必須的,然後如果我們要實現快取,cacheControl也是必須的,OkHttp提供了CacheControl這個類,裡面FORCE_CACHE 和FORCE_NETWORK分別表示只從快取獲取資料和只通過網路請求獲取資料,有了前面的兩步設定,這時我們是可以通過設定FORCE_CACHE 來從快取獲取資料而不通過網路獲取伺服器的資料的(前提是你本地要有快取,也就是必須先通過網路請求獲取到一次資料才能獲取到快取),程式碼沒什麼,就不貼了。

嗯,我們的網路請求實現本地快取已經實現,當你網路請求失敗的時候又不希望展示給使用者的是一片空白,那你就可以呼叫本地之前的快取了!!你別告訴我這樣你就滿足了,這每次都要判斷是不是請求失敗了,要不要請求快取,這想想都蛋疼啊!!!

所以我對OkHttp進行封裝,實現了只查詢快取,網路請求失敗自動查詢本地快取等功能 


支援4種不同的查詢方式

*ONLY_NETWORK 只查詢網路資料

*ONLY_CACHED 只查詢本地快取

*CACHED_ELSE_NETWORK 先查詢本地快取,如果本地沒有,再查詢網路資料

*NETWORK_ELSE_CACHED 先查詢網路資料,如果沒有,再查詢本地快取

支援get和post請求,預設查詢方式為NETWORK_ELSE_CACHED,可通過Builder來指定預設查詢方式

===================我是分隔符================= 
先貼程式碼

Prettyprint程式碼  收藏程式碼
  1. <code class="hljs sql has-numbering"> //實現一個最基本的請求方法  
  2. private <span class="hljs-operator"><span class="hljs-keyword">Call</span> request(Request request, Callback callback){  
  3.         <span class="hljs-keyword">if</span>(DEBUG){  
  4.             Log.d(<span class="hljs-string">"OKHttp"</span>,request.toString());</span>  
  5.         }  
  6.         <span class="hljs-operator"><span class="hljs-keyword">Call</span> <span class="hljs-keyword">call</span> = client.newCall(request);</span>  
  7.         <span class="hljs-operator"><span class="hljs-keyword">call</span>.enqueue(callback);</span>  
  8.         return <span class="hljs-operator"><span class="hljs-keyword">call</span>;</span>  
  9.     }  
  10. </code>  
Prettyprint程式碼  收藏程式碼
  1. <code class="hljs java has-numbering"><span class="hljs-comment">//實現自己的回撥,添加了onStart和onFinish方法</span>  
  2. <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Callback</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">com</span>.<span class="hljs-title">squareup</span>.<span class="hljs-title">okhttp</span>.<span class="hljs-title">Callback</span> {</span>  
  3.     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onStart</span>(){  
  4.     }  
  5.     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFinish</span>(){  
  6.     }  
  7.     <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span>(Request request, IOException e);  
  8.     <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onResponse</span>(Response response) <span class="hljs-keyword">throws</span> IOException;  
  9. }</code>  
Prettyprint程式碼  收藏程式碼
  1. <code class="hljs java has-numbering"><span class="hljs-comment">//定義一個公用的方法,實現最基本的封裝,無論是post還是get都適用</span>  
  2. <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">request</span>(String url, String method, RequestBody requestBody, <span class="hljs-keyword">final</span> CacheControl cacheControl, Headers headers,Object tag ,<span class="hljs-keyword">final</span> Callback callback){  
  3.         <span class="hljs-keyword">final</span> Request.Builder requestBuilder = <span class="hljs-keyword">new</span> Request.Builder().url(url).cacheControl(cacheControl);  
  4.         <span class="hljs-keyword">if</span>(headers!=<span class="hljs-keyword">null</span>){  
  5.             requestBuilder.headers(headers);  
  6.         }  
  7.         requestBuilder.method(method,requestBody);<span class="hljs-comment">//如果是get請求,這裡requestBody就應該傳個null</span>  
  8.         requestBuilder.tag(tag==<span class="hljs-keyword">null</span>?url:tag);<span class="hljs-comment">//OkHttp的tag是對請求的標誌,可以通過tag來獲取到請求和取消請求,這裡如果你不傳,就將當前url設定為tag</span>  
  9.         <span class="hljs-keyword">final</span> Request request = requestBuilder.build();  
  10.         request(request,<span class="hljs-keyword">new</span> Callback() {<span class="hljs-comment">//這裡是回撥</span>  
  11.             <span class="hljs-annotation">@Override</span>  
  12.             <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onStart</span>() {  
  13.                 <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  14.                     callback.onStart();  
  15.                 }  
  16.             }  
  17.             <span class="hljs-annotation">@Override</span>  
  18.             <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFinish</span>() {  
  19.                 <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  20.                     callback.onFinish();  
  21.                 }  
  22.             }  
  23.             <span class="hljs-annotation">@Override</span>  
  24.             <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span>(Request request, IOException e) {  
  25.                 <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  26.                     callback.onFailure(request,e);  
  27.                     callback.onFinish();  
  28.                 }  
  29.             }  
  30.             <span class="hljs-annotation">@Override</span>  
  31.             <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onResponse</span>(Response response) <span class="hljs-keyword">throws</span> IOException {  
  32.                 <span class="hljs-keyword">if</span>(response.code()==<span class="hljs-number">504</span>){<span class="hljs-comment">//OkHttp如果快取請求不到是會報504的</span>  
  33.                     <span class="hljs-keyword">if</span>(CacheControl.FORCE_CACHE == cacheControl){  
  34.                         <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  35.                             callback.onFailure(request,<span class="hljs-keyword">new</span> IOException(<span class="hljs-string">"cached not found"</span>));  
  36.                             callback.onFinish();  
  37.                         }  
  38.                         <span class="hljs-keyword">return</span>;  
  39.                     }  
  40.                 }  
  41.                 <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  42.                     callback.onResponse(response);  
  43.                     callback.onFinish();  
  44.                 }  
  45.             }  
  46.         });  
  47.     }  
  48. </code>  
Prettyprint程式碼  收藏程式碼
  1. <code class="hljs oxygene has-numbering"><span class="hljs-comment">//實現只請求網路和只請求快取的方法</span>  
  2.  <span class="hljs-keyword">public</span> void requestFromNetwork(<span class="hljs-keyword">final</span> String url,String <span class="hljs-function"><span class="hljs-keyword">method</span>,<span class="hljs-title">RequestBody</span> <span class="hljs-title">requestBody</span>, <span class="hljs-title">Headers</span> <span class="hljs-title">headers</span>,<span class="hljs-title">Object</span> <span class="hljs-title">tag</span>,<span class="hljs-title">final</span> <span class="hljs-title">Callback</span> <span class="hljs-title">callback</span>)<span class="hljs-comment">{  
  3.         request(url,method,requestBody,CacheControl.FORCE_NETWORK,headers,tag,callback);  
  4.     }</span>  
  5.     <span class="hljs-title">public</span> <span class="hljs-title">void</span> <span class="hljs-title">requestFromCached</span><span class="hljs-params">(String url,String <span class="hljs-keyword">method</span>,RequestBody requestBody,Headers headers ,Object tag,<span class="hljs-keyword">final</span> Callback callback)</span><span class="hljs-comment">{  
  6.         request(url,method,requestBody,CacheControl.FORCE_CACHE,headers,tag,callback);  
  7.     }</span></span></code>  

重點來了!! 
定義了4種請求型別 
*ONLY_NETWORK 只查詢網路資料

*ONLY_CACHED 只查詢本地快取

*CACHED_ELSE_NETWORK 先查詢本地快取,如果本地沒有,再查詢網路資料

*NETWORK_ELSE_CACHED 先查詢網路資料,如果沒有,再查詢本地快取

前兩種都沒什麼好說的,直接呼叫寫好的兩個方法requestFromNetwork和requestFromCached就行了 
後面兩種: 

影音先鋒電影http://www.iskdy.com/
1.CACHED_ELSE_NETWORK 先查詢本地快取,如果本地沒有,再查詢網路資料,我們就需要自己再傳一個Callback c2回調了,當回撥執行成功的時候,我們直接就呼叫方法的回撥的onResponse就行,其餘情況我們就需要查詢網路的資料,到了這一步,說明本地沒有快取了,所以直接呼叫requestFromNetwork就行

Prettyprint程式碼  收藏程式碼
  1. <code class="hljs java has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">request</span>(<span class="hljs-keyword">final</span> String url, <span class="hljs-keyword">final</span> CacheType cacheType, <span class="hljs-keyword">final</span> String method, <span class="hljs-keyword">final</span> RequestBody requestBody, <span class="hljs-keyword">final</span> Headers headers, <span class="hljs-keyword">final</span> Object tag , <span class="hljs-keyword">final</span> Callback callback){  
  2.         <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>)callback.onStart();  
  3.         <span class="hljs-keyword">switch</span> (cacheType){  
  4.             <span class="hljs-keyword">case</span> ONLY_NETWORK:<span class="hljs-comment">//只查詢網路資料</span>  
  5.                 requestFromNetwork(url,method,requestBody,headers,tag,callback);  
  6.                 <span class="hljs-keyword">break</span>;  
  7.             <span class="hljs-keyword">case</span> ONLY_CACHED:<span class="hljs-comment">//只查詢本地快取</span>  
  8.                 requestFromCached(url,method,requestBody,headers,tag,callback);  
  9.                 <span class="hljs-keyword">break</span>;  
  10.             <span class="hljs-keyword">case</span> CACHED_ELSE_NETWORK:  
  11.                 requestFromCached(url,method,requestBody,headers,tag, <span class="hljs-keyword">new</span> Callback() {  
  12.                     <span class="hljs-annotation">@Override</span>  
  13.                     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onStart</span>() {  
  14.                         <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  15.                             callback.onStart();  
  16.                         }  
  17.                     }  
  18.                     <span class="hljs-annotation">@Override</span>  
  19.                     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFinish</span>() {  
  20.                         <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  21.                             callback.onFinish();  
  22.                         }  
  23.                     }  
  24.                     <span class="hljs-annotation">@Override</span>  
  25.                     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span>(Request request, IOException e) {  
  26.                         requestFromNetwork(url,method,requestBody,headers,tag,callback);  
  27.                     }  
  28.                     <span class="hljs-annotation">@Override</span>  
  29.                     <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onResponse</span>(Response response) <span class="hljs-keyword">throws</span> IOException {  
  30.                         <span class="hljs-keyword">if</span>(response.code()==<span class="hljs-number">200</span>){<span class="hljs-comment">//response.isSuccessful()OkHttp是有這個方法判斷請求是否成功的,但判斷的方法是根據狀態碼是否是20開頭(200201202203等,具體區別就不在這裡描述了,有興趣的百度)來判斷的,但只有200返回的資料才是我們想要的</span>  
  31.                             <span class="hljs-keyword">if</span>(callback!=<span class="hljs-keyword">null</span>){  
  32.                                 callback.onResponse(response);  
  33.                                 callback.onFinish();  
  34.                             }  
  35.                         }<span class="hljs-keyword">else</span>{  
  36.                             requestFromNetwork(url,method,requestBody,headers,tag,callback);  
  37.                         }  
  38.                     }  
  39.                 });  
  40.                 <span class="hljs-keyword">break</span>;  
  41.             <span class="hljs-keyword">case</span> NETWORK_ELSE_CACHED:  
  42.                 requestFromNetwork(url,method,requestBody,headers,tag, <span class="hljs-keyword">new</span> Callback() {  
  43. 相關推薦

    OkHttp的使用簡介封裝實現簡潔呼叫

    最近將專案使用的網路請求庫換成了OkHttp,體驗感覺上升了好幾個檔次啊,-。-,之前專案是好幾年前的,封裝了原生的httpClient,沒有實現非同步請求,每次都要自己開個執行緒,然後再實現退出的時候把執行緒關了,還要實現本地快取,啊,聽起來好麻煩有木有,然後我終於受不了了,自己封裝了下OkHttp(。

    QoS最佳實踐實現好的帶寬管理

    哪些 重新 開發 鏈路 定義 設備 中標 處理 服務 服務質量(QoS)使管理員能夠在通過公司網絡時確定某些數據流量的優先級。但是為了使QoS工作,必須首先進行大量的規劃和協調。如果你的網絡遇到帶寬和延遲問題,請確保遵循這些最佳實踐指南,以便使用QoS技術實現更好的帶寬管理

    使用TS+Sequelize實現簡潔的CRUD

    http pil ebo 事情 sync stat 開發 export ron 如果是經常使用Node來做服務端開發的童鞋,肯定不可避免的會操作數據庫,做一些增刪改查(CRUD,Create Read Update Delete)的操作,如果是一些簡單的操作,類似定時腳本什

    通過代理模式對第三方網路請求框架進行封裝實現任意切換網路框架

     最近在網上學習了一篇課程,講的是通過代理模式對第三方框架進行封裝。 感覺講的很不錯,受益良多,特此記錄。 首先什麼是代理模式? 代理模式就是:為其他物件提供一種代理,以控制對這個物件的訪問。 舉個例子:沒空下去吃飯,找個同事幫忙買飯就是代理模式;平常租房子, 嫌麻

    mysql資料庫--檢視的簡介使用資料的備份與還原

    檢視: 檢視:view,是一種有結構(有行有列)但是沒結果(結構中不存在真實的資料)的虛擬表,虛擬表的結構來源不是自己定義, 而是從對應的基表中產生(檢視的資料來源)。 建立檢視: 基本語法:create view 檢視名字 as select 語句; -- sele

    Nosql簡介 RedisMemchche,MongoDb的區別

    本篇文章主要介紹Nosql的一些東西,以及Nosql中比較火的三個資料庫Redis、Memchache、MongoDb和他們之間的區別。以下是本文章的閱讀目錄 一、Nosql介紹 Nosql介紹 Nosql的全稱是Not Only Sql,這個概念早起就有人提出

    Facebook 開源 DeepFocus實現逼真的 VR 影象

       Facebook 開源了一種基於 AI 可實現更逼真 VR 影象的系統 DeepFocus。 DeepFocus 可與高階原型頭戴裝置配合使用,實時渲染模糊和各種焦距。例如,當有頭戴支援 DeepFocus 的裝置觀看附近的物體時,它會立即聚焦並變得清晰,而背

    js閉包的用途(匿名自執行函式快取實現封裝實現面向物件)

    文章轉載自:http://blog.csdn.net/sunlylorn/article/details/6534610 我們來看看閉包的用途。事實上,通過使用閉包,我們可以做很多事情。比如模擬面向物件的程式碼風格;更優雅,更簡潔的表達出程式碼;在某些方面提升程式碼的

    Okhttp二次封裝OkhttpClient使用單例模式封裝回撥封裝成主執行緒日誌攔截器

    public class HttpUtils {     private static final String TAG = "HttpUtils";     private static volatile HttpUtils instance;  

    資料結構圖文解析之:棧的簡介C++模板實現

    0. 資料結構圖文解析系列 1. 棧的簡介 1.1棧的特點 棧(Stack)是一種線性儲存結構,它具有如下特點: 棧中的資料元素遵守”先進後出"(First In Last Out)的原則,簡稱FILO結構。 限定只能在棧頂進行插入和刪除操作。 1.2棧的相關概念 棧的相關概念: 棧頂與棧底:允許元素

    windows ssh客戶端putty 簡介用其實現windows與linux的檔案傳輸

    windows ssh客戶端putty 簡介  一、Putty簡介      Putty是一個免費小巧的Win32平臺下的telnet,rlogin和ssh客戶端。它的主程式只有364k, 但是功能絲毫不遜色於商業的telnet類工具。  官方主頁:http://www.

    ReactNative系列之十九metro-bundle主要api簡介優化打包實現

    1.RN的打包bundle概要從ReactNative的0.50(準確來講應該是0.4x)之後到目前的最新版本0.55版本,都使用metro-bundle來進行壓縮打bundle檔案。實際上打bundle的好處有幾個1.100個檔案打包成一個檔案,jsCore載入時效率相對高

    對webuploader二次封裝實現表單多欄位多圖片上傳!

         由於公司專案使用到了這個功能,而我在百度谷歌都無法找到相應的外掛,所以決定自己封裝一個外掛來實現。由於博主是後端開發人員,對前端的jq不熟悉,踩了很多坑才完成。 只需要給指定的div指定方法即可。 前後的東西已經封裝好了。

    Vue爬坑之路 二:使用Muse-UI前端框架axios實現簡單登入頁

    一:安裝UI元件 Muse UI 基於 Vue2.0 開發,Vue2.0是當下最快的前端框架之一,小巧,api友好,可用於開發的複雜單頁應用,安裝的方式有很多種,官方推薦的是使用npm輔助安裝: 在專案的根目錄中開啟命令提示符輸入: npm

    PageRank演算法簡介Map-Reduce實現

    PageRank對網頁排名的演算法,曾是Google發家致富的法寶。以前雖然有實驗過,但理解還是不透徹,這幾天又看了一下,這裡總結一下PageRank演算法的基本原理。 一、什麼是pagerank PageRank的Page可是認為是網頁,表示網頁排名,也

    android4.2系統實現應用層呼叫乙太網/3G網絡卡

            android開發的SDK中,沒有提供3G網絡卡和乙太網的操作方法,但是有些裝置(包括平板和手機)可以在設定中開啟“乙太網”功能,就可以利用usb介面轉網線,或者3G網絡卡連線上網。也就是說系統中提供了Ethern

    自定義瀏覽器協議實現web程式呼叫本地程式

    參考了一下qq的方式。 tencent://Message/?Uin=000000&websiteName=qzone.qq.com&Menu=yes 在登錄檔裡面新增下面,就能實現 Windows Registry Editor Version 5.00

    cocos2dx 圖片變灰正常顯示實現(lua可以呼叫)

    圖片變灰, 採用shader就可以實現,   有2中方法, 1,  像CCSprite一樣create 2.  把CCSprite傳進來, 並把圖片要不要變灰的flag傳進來, 具體實現看程式碼和後面的使用方法 ---------------------------

    使用system函式實現程式裡面呼叫命令列

        以前在Linux C程式裡面使用過這個函式,確實很好用。     現在換到Windows下面,沒想到同樣有這個函式可以用,非常開心!     使用方法如下: 1 2 3

    【5】JMicro微服務基於RSAAES加密實現安全服務呼叫

    JMicro是基於Java實現的微服務平臺,最近花了兩個周未實現服務間安全呼叫支援。 JMicro服務呼叫分兩個部份,分別為內部服務間相互呼叫和外部客戶端通過API閘道器呼叫JMicro叢集內部服務,前者支援雙向加密加簽,並且支援全RSA加密(效率底,安全性高)及RSA+AES混合加密解密,後者只支援RSA+