Criação de uma biblioteca no-op Android para ferramentas de depuração

Tempo de leitura: 3 minutes

O que é um ambiente autônomo?

“No op” é a abreviatura de “no operation”.

É uma instrução comum em linguagem assembly e significa uma operação de computação que não tem efeito significativo. Por exemplo, uma função que não contém código:

// Performs an operation
fun setTimestamp(timestamp: String) {
    this.timestamp = timestamp
}

// Does nothing
fun setTimestamp(timestamp: String) {
   // no-op
}

Nesse caso, uma “biblioteca não operacional” é uma biblioteca da qual um aplicativo depende, mas na realidade não tem efeito.

Por que queremos uma biblioteca sem operação?

Durante o desenvolvimento do aplicativo, você pode usar ferramentas de depuração que aumentam a eficiência ao fazer alterações. No entanto, muitas vezes você não quer colocar ferramentas de depuração nas mãos dos usuários finais. Isto é porque:

  • Isso exporia um nível de controle sobre o aplicativo, em produção, que não é seguro ou sensato
  • Isso aumentaria desnecessariamente o tamanho do arquivo APK

Um bom exemplo de uma biblioteca como esta é Stetho. É uma ferramenta de depuração poderosa que não possui uma versão autônoma prontamente disponível.

Então, se quiséssemos, poderíamos incluir a biblioteca acima em compilações ao vivo e simplesmente desativá-la (via BuildConfig.FLAVOUR) ou criar uma classe “DebugApplication”.
No entanto, nenhuma dessas soluções é preferível. O primeiro adiciona lógica e não remove a implementação da biblioteca original. O último remove a implementação da biblioteca original, mas adiciona uma nova classe que estende sua classe de aplicativo normal.

 

Então, como podemos criar um?

  • Em primeiro lugar, adicionamos um novo módulo no Android Studio (versão 3.6.2 para mim) por meio de Arquivo -> Novo -> Novo Módulo
  • Em seguida, adicionamos uma nova biblioteca Android que conterá nossa versão no-op:

“Nome do aplicativo/biblioteca” e “Nome do módulo” podem ser o que você quiser (dentro do razoável, é claro), mas é importante que o campo “Nome do pacote” seja exatamente o mesmo que a biblioteca original que você está usando. Também definimos o idioma e os valores mínimos do SDK para corresponder ao que a biblioteca original usa. Neste caso, é Java e API 18.

 

É importante que o campo “Nome do pacote” seja exatamente igual à biblioteca original que você está usando.

Depois de terminar a caixa de diálogo acima, será exibida uma opção para gerar alguns arquivos. Você pode fazer o que quiser aqui, mas garantimos que a estrutura do arquivo do projeto foi configurada da seguinte maneira:

Aqui, o arquivo Android Manifest é apenas uma única linha:

<manifest package="com.facebook.stetho.stetho" />

E podemos reduzir o arquivo de compilação gerado automaticamente:

apply plugin: 'com.android.library'

android {
    compileSdkVersion <my compile SDK version>
    
    defaultConfig {
        minSdkVersion <my min SDK version>
        targetSdkVersion <my target SDK version>
        versionCode 1
        versionName '1.5.1' <this can be whatever you want, here it matches the version code from the genuine library>
    }
}

 

Em seguida, criamos nossa própria versão do arquivo Stetho.java, com nossa implementação no-op de todos os métodos publicamente acessíveis da superfície da API da biblioteca genuína (neste caso, apenas um!):

package com.facebook.stetho;

import android.content.Context;

public class Stetho {
  
  public static void initializeWithDefaults(Context context) {
    // no op
  }
}

 

Portanto, criamos um arquivo Java em nossa biblioteca personalizada com a mesma estrutura do original, com uma classe com o mesmo nome e o método estático que desejamos, com a mesma assinatura. Agora, se mudássemos para um tipo ao vivo, o IDE nos mostraria que há um erro – não podemos resolver o Stetho.

Portanto, precisamos informar ao Gradle (o Android Studio pode sugerir automaticamente essa alteração) qual versão da biblioteca “Stetho” usar, dependendo do tipo de compilação:

repositories {
    ...
}

android {
    ...
}

dependencies {
    ...
    debugImplementation "com.facebook.stetho.stetho:<version>"
    liveImplementation project(':stetho-no-op')
    ...
}

 

Aqui, debugImplementation informa ao Gradle qual dependência usar nos tipos de depuração. liveImplementation informa ao Gradle qual usar em versões ao vivo, e usando o projeto (‘caminho’) localiza nossa biblioteca autônoma em nosso projeto, por seu caminho. Isso significa que, quando estamos em um tipo de depuração, estamos usando a versão original do Stetho. Em uma versão ao vivo, estamos usando nossa versão no-op.

Depois que o Gradle for sincronizado, você deverá notar uma diferença ao encontrar os usos de initializeWithDefaults – o tipo de depuração apontará para a biblioteca genuína e o sabor ao vivo para o no-op que criamos.

E é isso!