現象描述 當我們打開京東 app 進入首頁,如果當前是沒有網路的狀態,裡面的按鈕點擊是沒有反應的。只有當我們打開網路的情況下,點擊按鈕才能跳轉頁面,按照我們一般人寫代碼的邏輯應該是這個樣子: 上面這段代碼看似沒有任何問題,完全滿足京東的網路處理需求,就寫一個 if(有網) 跳轉到下一個頁面,沒網就不 ...
現象描述
當我們打開京東 app 進入首頁,如果當前是沒有網路的狀態,裡面的按鈕點擊是沒有反應的。只有當我們打開網路的情況下,點擊按鈕才能跳轉頁面,按照我們一般人寫代碼的邏輯應該是這個樣子:
/** * 跳轉到待收貨頁面 */ public void jumpWaitReceiving() { // 判斷當前有沒有網路 if(CheckNetUtil.isNetworkAvailable(this)) { // 當前有網路我才跳轉,進入待收貨頁面 Intent intent = new Intent(this, WaitReceivingActivity.class); startActivity(intent); } } /** * 跳轉到我的錢包頁面 */ public void jumpMineWallet() { if(CheckNetUtil.isNetworkAvailable(this)) { Intent intent = new Intent(this, MineWalletActivity.class); startActivity(intent); } }
上面這段代碼看似沒有任何問題,完全滿足京東的網路處理需求,就寫一個 if(有網) 跳轉到下一個頁面,沒網就不做任何處理。但是真的沒有問題嗎? 按照京東的頁面,這些 if() 代碼估計要寫上幾十次,而且有些在 Activity,有些甚至在 Fragment 中,很難管理。如果有一天需求變動,我們估計要改動多處。我們到底有沒有更好的方式,且接著往下看。
面向切麵
我們現在想做的其實就是,我根本不想寫那麼多的 if() 代碼,而且寫得越多越不好管理,比如有一天沒網路要彈 Toast ,那麼豈不是很多地方要去改動。所以接下來,我們打算採用面向切麵的編程思想,把網路檢測切出來統一管理。第一,保證代碼的簡潔性,第二,需求有變動時我們只需要統一改動那一部分代碼,第三,有很多這裡不一一列出來了。
那麼什麼是 AOP ? 好處又有什麼?
面向切麵(AOP)其實就是把眾多方法中的所有共有代碼全部抽取出來,放置到某個地方集中管理,然後在具體運行時,再由容器動態織入這些共有代碼的話,最起碼可以解決兩個問題:
1.1 Android程式員在編寫具體的業務邏輯處理方法時,只需關心核心的業務邏輯處理,既提高了工作效率,又使代碼變更簡潔優雅。
1.2 在日後的維護中由於業務邏輯代碼與共有代碼分開存放,而且共有代碼是集中存放的,因此使維護工作變得簡單輕鬆。
那到底應該怎麼寫呢? 請看我最終的代碼,代碼如下:
/** * 跳轉到待收貨頁面 */ @CheckNet public void jumpWaitReceiving() { Intent intent = new Intent(this, WaitReceivingActivity.class); startActivity(intent); } /** * 跳轉到我的錢包頁面 */ @CheckNet public void jumpMineWallet() { Intent intent = new Intent(this, MineWalletActivity.class); startActivity(intent); }上面這段代碼,也沒看到你省了多少代碼。但其實在我們真正的開發過程中,遠不止檢測網路這麼個功能,比如還需要檢測登錄,需要上傳日誌,統計用戶行為等等。這樣,我們方法越多省的代碼就會越多,而且所有的代碼都統一進行了管理,後面維護起來也方便,況且跟最開始比起來,代碼的確也變得更加簡潔了。那好吧,我們就只看到加了一個 CheckNet ,也沒看到你判斷網路的代碼啊?別急,且接著往下看。最主要的其實還是下麵這段代碼:
1 /** 2 * 處理網路檢測切麵 3 */ 4 @Aspect 5 public class SectionAspect { 6 7 /** 8 * 找到處理的切點 9 * * *(..) 可以處理所有的方法 10 */ 11 @Pointcut("execution(@com.darren.architect_day02.CheckNet * *(..))") 12 public void checkNetBehavior() { 13 14 } 15 16 /** 17 * 處理切麵 18 */ 19 @Around("checkNetBehavior()") 20 public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable { 21 Log.e("TAG", "checkNet"); 22 // 做埋點 日誌上傳 許可權檢測(我寫的,RxPermission , easyPermission) 網路檢測 23 // 網路檢測 24 // 1.獲取 CheckNet 註解 NDK 圖片壓縮 C++ 調用Java 方法 25 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 26 CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class); 27 if (checkNet != null) { 28 // 2.判斷有沒有網路 怎麼樣獲取 context? 29 Object object = joinPoint.getThis();// View Activity Fragment ; getThis() 當前切點方法所在的類 30 Context context = getContext(object); 31 if (context != null) { 32 if (!isNetworkAvailable(context)) { 33 // 3.沒有網路不要往下執行 34 Toast.makeText(context,"請檢查您的網路",Toast.LENGTH_LONG).show(); 35 return null; 36 } 37 } 38 } 39 return joinPoint.proceed(); 40 } 41 42 /** 43 * 通過對象獲取上下文 44 * 45 * @param object 46 * @return 47 */ 48 private Context getContext(Object object) { 49 if (object instanceof Activity) { 50 return (Activity) object; 51 } else if (object instanceof Fragment) { 52 Fragment fragment = (Fragment) object; 53 return fragment.getActivity(); 54 } else if (object instanceof View) { 55 View view = (View) object; 56 return view.getContext(); 57 } 58 return null; 59 } 60 61 /** 62 * 檢查當前網路是否可用 63 * 64 * @return 65 */ 66 private static boolean isNetworkAvailable(Context context) { 67 // 獲取手機所有連接管理對象(包括對wi-fi,net等連接的管理) 68 ConnectivityManager connectivityManager = (ConnectivityManager) 69 context.getSystemService(Context.CONNECTIVITY_SERVICE); 70 if (connectivityManager != null) { 71 // 獲取NetworkInfo對象 72 NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo(); 73 74 if (networkInfo != null && networkInfo.length > 0) { 75 for (int i = 0; i < networkInfo.length; i++) { 76 // 判斷當前網路狀態是否為連接狀態 77 if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) { 78 return true; 79 } 80 } 81 } 82 } 83 return false; 84 } 85 }