demonstração do ambiente de construção do Android

Reimpresso de http://blog.joanzapata.com/robust-architecture-for-an-android-app/

Arquitetura robusta e legível para um aplicativo Android

Desde os primeiros dias do Android, tenho procurado uma maneira robusta de construir aplicativos Android, manter as operações de IO fora do UI Thread, evitar chamadas de rede duplicadas, armazenar coisas relevantes em cache, atualizar o cache no momento certo, etc. .. com a sintaxe mais limpa possível.

Esta postagem do blog não fornecerá uma implementação precisa, mas uma maneira possível de estruturar um aplicativo com um bom equilíbrio entre flexibilidade, legibilidade e robustez.

Algumas soluções existentes

No início do Android, a maioria das pessoas confiava no  AsyncTasks para processos de longa execução. Basicamente: foi uma droga, já tem muitos artigos sobre esse assunto. Mais tarde, o Honeycomb introduziu Loaders  que eram melhores para suportar alterações de configuração. Em 2012,   foi lançado  o RobospiceService , baseado em um Android  rodando em segundo plano. Este  infográfico  mostra como funciona.

É ótimo comparado a  AsyncTask, mas ainda tenho alguns problemas com ele. Aqui está o código médio para fazer uma solicitação com o Robospice, em uma atividade. Não há necessidade de lê-lo com precisão, é apenas para se ter uma ideia:

FollowersRequest request = new FollowersRequest(user);  
lastRequestCacheKey = request.createCacheKey();  
spiceManager.execute(request, lastRequestCacheKey,  
    DurationInMillis.ONE_MINUTE, 
    new RequestListener<FollowerList> {
      @Override
      public void onRequestSuccess(FollowerList listFollowers) {
        // On failure
      }

      @Override
      public void onRequestFailure(SpiceException e) {
          // On success
      }
    });

E a solicitação que o acompanha:

public class FollowersRequest extends SpringAndroidSpiceRequest<FollowerList> {  
  private String user;

  public FollowersRequest(String user) {
    super(FollowerList.class);
    this.user = user;
  }

  @Override
  public FollowerList loadDataFromNetwork() throws Exception {
    String url = format("https://api.github.com/users/%s/followers", user);
    return getRestTemplate().getForObject(url, FollowerList.class);
  }

  public String createCacheKey() {
      return "followers." + user;
  }
}

Problemas

  1. Este código parece horrível e você tem que fazer isso para  cada  solicitação!
  2. Você precisa criar uma  SpiceRequest subclasse para cada tipo de solicitação
  3. Você precisa criar um  RequestListener para cada solicitação
  4. Se o cache expirar em breve, o usuário terá que esperar a cada chamada
  5. Se o cache expirar após um longo período, o usuário poderá ver dados obsoletos
  6. Mantém  RequestListener uma referência implícita à atividade, e quanto a vazamentos de memória?

Não tão bom…

Conciso e robusto em cinco etapas

Quando comecei a trabalhar na  Candyshop , tentei outra coisa. Misturei diferentes bibliotecas com funcionalidades muito interessantes e tentei fazer algo conciso mas robusto.

Aqui está um esquema global do que explicarei nas próximas partes.

Esquema de estrutura global

Passo 1 — Um sistema de cache fácil de usar

Você precisará de um sistema de cache persistente. Mantenha simples.

@EBean
public class Cache {  
    public static enum CacheKey { USER, CONTACTS, ... }

    public <T> T get(CacheKey key, Class<T> returnType) { ... }
    public void put(CacheKey key, Object value) { ... }
}
Passo 2 — Um cliente REST

Dou isso como exemplo, apenas certifique-se de que a lógica da API REST que você está usando permaneça em um só lugar.

@Rest(rootUrl = "http://anything.com")
public interface CandyshopApi {

    @Get("/api/contacts/")
    ContactsWrapper fetchContacts();

    @Get("/api/user/")
    User fetchUser();

}
Passo 3 — Um barramento de eventos para todo o aplicativo

Instancie-o em um local estratégico, acessível de qualquer lugar do app, o  Application objeto é um bom candidato para isso.

public class CandyshopApplication extends Application {  
    public final static EventBus BUS = new EventBus();
    ...
}
Passo 4 — Uma atividade que precisa de alguns dados!

Minha solução é, como o Robospice, baseada em um serviço, mas não em Android. Um objeto singleton normal, compartilhado pelo aplicativo. Veremos o código desse serviço no passo 5. Mas agora vamos ver como fica o código da Activity, porque  era isso que eu mais queria simplificar em primeiro lugar!

@EActivity(R.layout.activity_main)
public class MainActivity extends Activity {

    // Inject the service
    @Bean protected AppService appService;

    // Once everything is loaded…
    @AfterViews public void afterViews() {
        // … request the user and his contacts (returns immediately)
        appService.getUser();
        appService.getContacts();
    }

    /*
        The result of the previous calls will
        come as events through the EventBus.
        We'll probably update the UI, so we
        need to use @UiThread.
    */

    @UiThread public void onEvent(UserFetchedEvent e) {
        ...
    }

    @UiThread public void onEvent(ContactsFetchedEvent e) {
        ...
    }

    // Register the activity in the event bus when it starts
    @Override protected void onStart() {
        super.onStart();
        BUS.register(this);
    }

    // Unregister it when it stops
    @Override protected void onStop() {
        super.onStop();
        BUS.unregister(this);
    }

}

Uma linha para solicitar ao usuário, uma linha para expressar o fato de que receberemos uma resposta para essa solicitação. A mesma coisa para contatos. Soa muito bem!

Passo 5 — Um serviço singleton

Como disse na etapa 4, o serviço que estou usando não é um serviço Android. Na verdade, comecei com um, mas mudei de ideia. A razão é a simplicidade . Os serviços devem ser usados ​​quando você precisa ter algo em execução enquanto nenhuma atividade é exibida ou quando deseja disponibilizar algum código para outros aplicativos. Não era exatamente isso que eu queria. Usar um singleton simples me permite evitar o uso de  ServiceConnectionBinder, etc ...

Há muitas coisas a dizer aqui. Vamos começar com um esquema para mostrar o que acontece quando chamamos  getUser() e  getContacts() da Activity. Então explicarei o código.

Você pode imaginar que cada série é um thread.

Esquema mostrando os benefícios de usar duas séries

O que você vê aqui é o que eu  realmente gosto  neste modelo; a visualização é imediatamente preenchida com dados armazenados em cache, portanto, na maioria das vezes,  o usuário não precisa esperar . Então, quando o resultado atualizado chega do servidor, as informações exibidas são substituídas. A contrapartida disso é que você precisa garantir que a atividade possa receber o mesmo tipo de resposta várias vezes. Tenha isso em mente ao criar a atividade e você ficará bem.

Ok, vamos ver algum código!

// As I said, a simple class, with a singleton scope
@EBean(scope = EBean.Scope.Singleton)
public class AppService {

    // (Explained later)
    public static final String NETWORK = "NETWORK";
    public static final String CACHE = "CACHE";

    // Inject the cache (step 1)
    @Bean protected Cache cache;

    // Inject the rest client (step 2)
    @RestService protected CandyshopApi candyshopApi;

    // This is what the activity calls, it's public
    @Background(serial = CACHE)
    public void getContacts() {

        // Try to load the existing cache
        ContactsFetchedEvent cachedResult =
            cache.get(KEY_CONTACTS, ContactsFetchedEvent.class);

        // If there's something in cache, send the event
        if (cachedResult != null) BUS.post(cachedResult);

        // Then load from server, asynchronously
        getContactsAsync();
    }

    @Background(serial = NETWORK)
    private void getContactsAsync() {

        // Fetch the contacts (network access)
        ContactsWrapper contacts = candyshopApi.fetchContacts();

        // Create the resulting event
        ContactsFetchedEvent event = new ContactsFetchedEvent(contacts);

        // Store the event in cache (replace existing if any)
        cache.put(KEY_CONTACTS, event);

        // Post the event
        BUS.post(event);

    }

}

É muito código para uma única solicitação!  Na verdade, eu explodi para torná-lo mais explicativo, mas é sempre o mesmo padrão para que você possa criar facilmente auxiliares para criar métodos de linha única. Por exemplo  getUser() ficaria assim:

    @Background(serial = CACHE)
    public void getUser() {
        postIfPresent(KEY_USER, UserFetchedEvent.class);
        getUserAsync();
    }

    @Background(serial = NETWORK)
    private void getUserAsync() {
        cacheThenPost(KEY_USER, new UserFetchedEvent(candyshopApi.fetchUser()));
    }

Então e a  serial coisa? Aqui está o que o documento diz:

Por padrão, todos  @Background os métodos anotados são executados em paralelo. É garantido que dois métodos que usam o mesmo  serial sejam executados no mesmo thread, sequencialmente (ou seja, um após o outro).

Executar chamadas de rede uma após a outra pode ter impactos no desempenho, mas é muito mais fácil lidar com coisas do tipo GET-after-POST, que estou pronto para sacrificar um pouco de desempenho. Além disso, você pode ajustar facilmente as séries posteriormente para melhorar o desempenho se notar alguma coisa. Atualmente na Candyshop, uso quatro seriais diferentes.

Concluir

A solução que descrevi aqui é um rascunho, é a ideia básica com a qual comecei há alguns meses. Até hoje, consegui resolver todos os casos específicos que encontrei e estou gostando muito de trabalhar com isso até agora. Há algumas outras coisas incríveis que gostaria de compartilhar sobre esse modelo, como  gerenciamento de errosexpiração de cachesolicitações POST , cancelamento de operações inúteis , mas estou muito grato por você ter lido até agora, então não vou forçar!

Acho que você gosta

Origin blog.csdn.net/skylovesky/article/details/31782961
Recomendado
Clasificación