自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

健壯且可讀的安卓架構(gòu)設(shè)計(jì)

移動(dòng)開發(fā) Android
本文并不是給你提供一個(gè)權(quán)威精準(zhǔn)的解決方案,更多的是去探討在靈活性、可讀性和健壯性之間有著很好平衡的App的一種開發(fā)方式。

自接觸Android以來,我一直在尋找一種比較健壯的開發(fā)方法。譬如避免在UI線程進(jìn)行IO操作,防止重復(fù)的網(wǎng)絡(luò)請求,對重要數(shù)據(jù)進(jìn)行緩存并且準(zhǔn)確的更新這些緩存等等。當(dāng)然,代碼結(jié)構(gòu)也要保持盡量清晰。

本文并不是給你提供一個(gè)權(quán)威精準(zhǔn)的解決方案,更多的是去探討在靈活性、可讀性和健壯性之間有著很好平衡的App的一種開發(fā)方式。

一些現(xiàn)有的解決方案

在Android的初期版本,許多人處理多任務(wù)時(shí)會(huì)選擇 AsyncTask 。大體上來說,AsyncTask非常難用,許多文章也提到了它的問題。后來,Honeycomb(3.0)引入了可配置性更好的 Loaders。到了2012年,基于Android Service的開源項(xiàng)目Robospice問世,帶來了新的解決方案,這里介紹了 Robospice的工作原理。

Robospice 比起 AsyncTask 的確好太多了,但是依然存在一些問題。比如下面這段常見代碼,通過Robospice在Activity中發(fā)起一個(gè)請求的過程。你并不需要細(xì)讀,只要有個(gè)大概的概念就好:

  1. FollowersRequest request = new FollowersRequest(user);  
  2. lastRequestCacheKey = request.createCacheKey();  
  3. spiceManager.execute(request, lastRequestCacheKey,  
  4.     DurationInMillis.ONE_MINUTE, 
  5.     new RequestListener<FollowerList> { 
  6.       @Override 
  7.       public void onRequestFailure(SpiceException e) { 
  8.           // On success 
  9.       } 
  10.   
  11.       @Override 
  12.       public void onRequestSuccess(FollowerList listFollowers) { 
  13.         // On failure 
  14.       } 
  15.     }); 

然后是請求的具體代碼:

  1. public class FollowersRequest extends SpringAndroidSpiceRequest<FollowerList> {  
  2.   private String user; 
  3.   
  4.   public FollowersRequest(String user) { 
  5.     super(FollowerList.class); 
  6.     this.user = user; 
  7.   } 
  8.   
  9.   @Override 
  10.   public FollowerList loadDataFromNetwork() throws Exception { 
  11.     String url = format("https://api.github.com/users/%s/followers", user); 
  12.     return getRestTemplate().getForObject(url, FollowerList.class); 
  13.   } 
  14.   
  15.   public String createCacheKey() { 
  16.       return "followers." + user; 
  17.   } 

存在的問題

你需要為每個(gè)請求都做上述的處理,代碼會(huì)顯得很臃腫:

- 對于你的每種請求你都需要繼承SpiceRequest寫一個(gè)特定的子類。

- 同樣的,對于每種請求你都需要實(shí)現(xiàn)一個(gè)RequestListener來監(jiān)聽。

- 如果你的緩存過期時(shí)間很短,用戶就需要花較長時(shí)間等待你的每個(gè)請求結(jié)束。

- RequestListener持有了Activity的隱式引用,那么是不是還需要內(nèi)存泄露的問題。

綜上,這并不是一個(gè)很好的解決方案。

五步,讓程序簡潔而健壯

在我開始開發(fā)Candyshop的時(shí)候,我嘗試了其他的方法。我試圖通過混合一些擁有有趣特性的庫來構(gòu)造一個(gè)簡單而健壯的解決方案。這是我用到的庫的列表:

* AndroidAnnotations用來處理后臺(tái)任務(wù),EBean等等……

* Spring RestTemplate用來處理 REST(含狀態(tài)傳輸)的網(wǎng)絡(luò)請求,這個(gè)庫和AndroidAnnotations配合的非常好。

* SnappyDB這個(gè)庫主要用來將一些 Java 對象緩存到本地文件中。

* EventBus 通過 Event Bus 來解耦處理 App 內(nèi)部組建間的通訊。

下圖就是我將要詳細(xì)講解的整體架構(gòu):

***步 一個(gè)易于使用的緩存系統(tǒng)

你肯定會(huì)需要一個(gè)持久化的緩存系統(tǒng),保持這個(gè)系統(tǒng)盡可能簡單。

  1. @EBean 
  2. public class Cache {  
  3.     public static enum CacheKey { USER, CONTACTS, ... } 
  4.   
  5.     public <T> T get(CacheKey key, Class<T> returnType) { ... } 
  6.     public void put(CacheKey key, Object value) { ... } 

第二步 一個(gè)符合REST的Client

這里我通過下面的例子來說明。記得要確保你使用 REST API 放在同一個(gè)地方。

  1. @Rest(rootUrl = "http://anything.com"
  2. public interface CandyshopApi { 
  3.   
  4.     @Get("/api/contacts/"
  5.     ContactsWrapper fetchContacts(); 
  6.   
  7.     @Get("/api/user/"
  8.     User fetchUser(); 
  9.   

第三步 應(yīng)用級的事件總線(Event Bus)

在程序最初的時(shí)候就初始化Event bus對象,然后應(yīng)用的全局都可以訪問到這個(gè)對象。在Android中, Application初始化是一個(gè)很好的時(shí)機(jī)。

  1. public class CandyshopApplication extends Application {  
  2.     public final static EventBus BUS = new EventBus(); 
  3.     ... 

第四步 處理那些需要數(shù)據(jù)的Activity

對于這一類的Activity,我的處理方式和Robospice非常類似,同樣是基于Service解決。不同的是,我的Service并不是Android提供的那個(gè),而是一個(gè)常規(guī)的單例對象。這個(gè)對象可以被App的各處訪問到,具體的代碼我們會(huì)在第五步進(jìn)行講解,在這一步,我們先看看這種處理Activity代碼結(jié)構(gòu)是怎么樣的。因?yàn)椋@一步可以看到的是我們簡化效果***烈的部分!

  1. @EActivity(R.layout.activity_main) 
  2. public class MainActivity extends Activity { 
  3.   
  4.     // Inject the service 
  5.     @Bean protected AppService appService; 
  6.   
  7.     // Once everything is loaded… 
  8.     @AfterViews public void afterViews() { 
  9.         // … request the user and his contacts (returns immediately) 
  10.         appService.getUser(); 
  11.         appService.getContacts(); 
  12.     } 
  13.   
  14.     /* 
  15.         The result of the previous calls will 
  16.         come as events through the EventBus. 
  17.         We'll probably update the UI, so we 
  18.         need to use @UiThread. 
  19.     */ 
  20.   
  21.     @UiThread public void onEvent(UserFetchedEvent e) { 
  22.         ... 
  23.     } 
  24.   
  25.     @UiThread public void onEvent(ContactsFetchedEvent e) { 
  26.         ... 
  27.     } 
  28.   
  29.     // Register the activity in the event bus when it starts 
  30.     @Override protected void onStart() { 
  31.         super.onStart(); 
  32.         BUS.register(this); 
  33.     } 
  34.   
  35.     // Unregister it when it stops 
  36.     @Override protected void onStop() { 
  37.         super.onStop(); 
  38.         BUS.unregister(this); 
  39.     } 
  40.   

一行代碼完成對用戶數(shù)據(jù)的請求,同樣也只需要一行代碼來解析請求所返回的數(shù)據(jù)。對于通訊錄等其他數(shù)據(jù)也可以用一樣的方式來處理,聽起來不錯(cuò)吧!

第五步——單例版的后臺(tái)服務(wù)

正如我在上一步說的那樣,這里使用的Service并不是Android提供的Service類。其實(shí),一開始的時(shí)候,我考慮使用Android提供的Services,不過***還是放棄了,原因還是為了簡化。因?yàn)?Android提供的Services通常情況下是為那些在沒有Activity展示情況下但還需要處理的操作提供服務(wù)的。另一種情況,你需要提供一些功能給其他的應(yīng)用。這其實(shí)和我的需求并不完全相符,而且用單例來處理我的后臺(tái)請求可以讓我避免使用復(fù)雜的借口,譬如:ServiceConnection,Binder等等……

這一部分可以探討的地方就多了。為了方便理解,我們從架構(gòu)切入展示當(dāng)Activity調(diào)用getUser()和getContacts()的時(shí)候究竟發(fā)生了什么。

你可以把下圖中每個(gè)serial當(dāng)作一個(gè)線程:

正如你所看到的,這是我非常喜歡的模式。大部分情況下用戶不需要等待,程序的視圖會(huì)立刻被緩存數(shù)據(jù)填充。然后,當(dāng)抓取到了服務(wù)端的***數(shù)據(jù),視圖數(shù)據(jù)會(huì)被新數(shù)據(jù)替代掉。與此對應(yīng)的是,你需要確保你的Activity可以接受多次同樣類型的數(shù)據(jù)。在構(gòu)建Activity的時(shí)候記住這一點(diǎn)就沒有任何問題啦。

下面是一些示例代碼:

  1. // As I said, a simple class, with a singleton scope 
  2. @EBean(scope = EBean.Scope.Singleton) 
  3. public class AppService { 
  4.   
  5.     // (Explained later) 
  6.     public static final String NETWORK = "NETWORK"
  7.     public static final String CACHE = "CACHE"
  8.   
  9.     // Inject the cache (step 1) 
  10.     @Bean protected Cache cache; 
  11.   
  12.     // Inject the rest client (step 2) 
  13.     @RestService protected CandyshopApi candyshopApi; 
  14.   
  15.     // This is what the activity calls, it's public 
  16.     @Background(serial = CACHE) 
  17.     public void getContacts() { 
  18.   
  19.         // Try to load the existing cache 
  20.         ContactsFetchedEvent cachedResult = 
  21.             cache.get(KEY_CONTACTS, ContactsFetchedEvent.class); 
  22.   
  23.         // If there's something in cache, send the event 
  24.         if (cachedResult != null) BUS.post(cachedResult); 
  25.   
  26.         // Then load from server, asynchronously 
  27.         getContactsAsync(); 
  28.     } 
  29.   
  30.     @Background(serial = NETWORK) 
  31.     private void getContactsAsync() { 
  32.   
  33.         // Fetch the contacts (network access) 
  34.         ContactsWrapper contacts = candyshopApi.fetchContacts(); 
  35.   
  36.         // Create the resulting event 
  37.         ContactsFetchedEvent event = new ContactsFetchedEvent(contacts); 
  38.   
  39.         // Store the event in cache (replace existing if any) 
  40.         cache.put(KEY_CONTACTS, event); 
  41.   
  42.         // Post the event 
  43.         BUS.post(event); 
  44.   
  45.     } 
  46.   

似乎每個(gè)請求之中的代碼還是有點(diǎn)多!實(shí)際上,這是我為了更好說明才進(jìn)行了展開。不難發(fā)現(xiàn),這些請求都遵守了類似的模式,所以你可以很容易的構(gòu)造一個(gè) Helper 來簡化他們。比如 getUser()可以是這樣的:

  1. @Background(serial = CACHE) 
  2. public void getUser() { 
  3.     postIfPresent(KEY_USER, UserFetchedEvent.class); 
  4.     getUserAsync(); 
  5.   
  6. @Background(serial = NETWORK) 
  7. private void getUserAsync() { 
  8.     cacheThenPost(KEY_USER, new UserFetchedEvent(candyshopApi.fetchUser())); 

那么serial是用來做什么的? 讓我們看看文檔是怎么說的:

默認(rèn)情況下,所有@Background的匿名方法都是并行執(zhí)行的。但是如果兩個(gè)方法使用了同樣名字的serial則會(huì)順序運(yùn)行在同一個(gè)線程中,一個(gè)接著一個(gè)執(zhí)行。

雖然把網(wǎng)絡(luò)請求放在一個(gè)線程中順序執(zhí)行可能會(huì)導(dǎo)致性能下降,但是這使得“先POST然后GET獲得數(shù)據(jù)”的那類事務(wù)處理起來非常容易,這是個(gè)特性值得為此犧牲一些性能。退一步講,如果你真的發(fā)現(xiàn)性能不可接受,還是可以很容易使用多個(gè)serial來解決?,F(xiàn)在版本的Candyshop中,我同時(shí)使用了四個(gè)不同的serial。

總結(jié)

這里描述的解決方案是我?guī)讉€(gè)月前想到的很初級的一個(gè)想法。今天,我已經(jīng)解決掉所有遇到的特殊情況,并且非常享受在這樣的架構(gòu)下開發(fā)。當(dāng)然,這個(gè)方案 中還有一些很棒的東西我想要和大家分享,比如:錯(cuò)誤處理、緩存超時(shí)機(jī)制、POST請求、對無用操作的忽略,但是因?yàn)槠蜻@里我就不繼續(xù)講述了。

那么,你是否也找到了能讓你享受每天工作的框架?

原文鏈接: joanzap   翻譯:zerob13

譯文鏈接: http://blog.jobbole.com/66606/

責(zé)任編輯:閆佳明 來源: blog.jobbole
相關(guān)推薦

2025-04-15 04:00:00

2013-05-27 10:58:28

Tumblr架構(gòu)設(shè)計(jì)雅虎收購

2023-05-12 08:06:46

Kubernetes多云架構(gòu)

2015-06-02 04:17:44

架構(gòu)設(shè)計(jì)審架構(gòu)設(shè)計(jì)說明書

2009-07-06 10:36:41

敏捷開發(fā)

2021-11-08 06:57:35

Redis架構(gòu)設(shè)計(jì)

2023-07-05 08:00:52

MetrAuto系統(tǒng)架構(gòu)

2009-01-15 09:43:51

Web架構(gòu)設(shè)計(jì)緩存

2012-05-11 10:38:15

Cloud Found

2023-03-27 08:05:27

數(shù)字化轉(zhuǎn)型MLOps

2015-06-02 04:34:05

架構(gòu)設(shè)計(jì)

2010-07-14 09:01:07

架構(gòu)設(shè)計(jì)

2016-01-11 11:20:43

2021-11-01 21:01:01

架構(gòu)設(shè)計(jì)軟件

2019-12-19 10:10:45

秒殺系統(tǒng)高并發(fā)

2009-07-10 09:31:57

MyEclipse U

2017-11-17 07:06:27

互聯(lián)網(wǎng)分層架構(gòu)APP

2024-08-18 14:09:24

2021-07-21 16:30:38

iOSAPP架構(gòu)

2012-09-19 13:46:37

存儲(chǔ)存儲(chǔ)設(shè)計(jì)快速表態(tài)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號