Skip to content

Instantly share code, notes, and snippets.

@EduardoSP6
Last active July 22, 2021 14:20
Show Gist options
  • Save EduardoSP6/d0882fb3e473963e1dea56a004968612 to your computer and use it in GitHub Desktop.
Save EduardoSP6/d0882fb3e473963e1dea56a004968612 to your computer and use it in GitHub Desktop.
Migração de aplicativos para Android X
Migrar para o AndroidX
O AndroidX substitui as APIs da Biblioteca de Suporte original por pacotes no namespace androidx. Apenas o pacote e os nomes
de artefatos Maven foram modificados. Os nomes de classes, métodos e campos permanecem os mesmos.
Observação: recomendamos que você trabalhe em uma ramificação separada ao fazer a migração. Tente também evitar a refatoração
do código durante esse processo.
Pré-requisitos:
Antes de fazer a migração, atualize seu app. Recomendamos que você atualize o projeto para usar a versão final da Biblioteca
de Suporte: versão 28.0.0. Isso porque os artefatos do AndroidX na versão 1.0.0 são equivalentes binários aos artefatos da
Biblioteca de Suporte 28.0.0.
- Atualizar versão do gradle para mínimo 4.6 no arquivo gradle-wrapper.properties.
- No arquivo build.gradle (Project) - atualizar versão das dependências do gradle para mínimo 3.2.0.
Migrar um projeto existente usando o Android Studio:
Com o Android Studio 3.2 e versões mais recentes, é possível migrar um projeto já existente para o AndroidX selecionando
Refactor > Migrate to AndroidX na barra de menus.
O comando "refactor" usa duas sinalizações. Por padrão, elas são definidas como true no arquivo gradle.properties:
android.useAndroidX=true
O plug-in do Android usa a biblioteca adequada do AndroidX em vez de uma Biblioteca de Suporte.
android.enableJetifier=true
Ele também migra automaticamente as bibliotecas de terceiros já existentes para o AndroidX reescrevendo os binários delas.
Observação: a migração integrada do Android Studio pode não processar tudo. Dependendo da sua configuração da compilação,
pode ser necessário atualizar manualmente os scripts de criação e os mapeamentos Proguard. Por exemplo, se você mantém sua
configuração de dependências em um arquivo de compilação diferente, use os arquivos de mapeamento mencionados abaixo para
revisar e atualizar suas dependências com os pacotes correspondentes do AndroidX.
Link: https://developer.android.com/jetpack/androidx/migrate?hl=pt-br#:~:text=Com%20o%20Android%20Studio%203.2,como%20true%20no%20arquivo%20gradle.
Após migração:
0- Adicionar dependência para androidx.appcompat.app.AppCompatActivity;
1- Trocar o namespace dos imports das classes do projeto;
2- Se o projeto tem função de obter a localização do aparelho, então será necessário inserir a permissão
ACCESS_BACKGROUND_LOCATION (somente para Android X+);
3- Se obtem IMEI do aparelho, essa feature não é mais possível no Android X ou superior;
4- Se o app utiliza Environment.getExternalStorageDirectory() para retornar o path para armazenamento de arquivos, o método
getExternalStorageDirectory() foi depreciado. Se faz necessário substituir por Context.getExternalFilesDir();
5- Se o app utiliza Context.getResources().getDrawable(), substituir por ContextCompat.getDrawable(ctx, R.drawable.res_id);
6- Se o app utiliza context.getResources().getColor(), substituir por ContextCompat.getColor(ctx, R.color.res_id);
7- LinearLayout.setBackgroundDrawable() foi depreciado. Substituir por LinearLayout.setBackgroundResource();
8- RecyclerViews:
Se tivermos um RecyclerView com match_parent como altura / largura , devemos adicionar setHasFixedSize(true) uma vez que o
tamanho do RecyclerViews não altera a inserção ou exclusão de itens nele.
setHasFixedSize deve ser falsa, se temos um RecyclerView com wrap_content a altura / largura , pois cada elemento inserido
pelo adaptador pode mudar o tamanho do Recycler dependendo dos itens inseridos / excluídos, por isso, o tamanho da Recycler
será diferente cada vez que adicionar / excluir Itens.
Se o largura e altura = match_parent, então podemos usar setHasFixedSize(true).
Se altura = wrap_content, então usamos setHasFixedSize(false).
9- Melhorias de segurança (Extras):
9.1- Inserir na tag application do Android manifest o atributo: android:taskAffinity="" para evitar Hijacking Task.
9.2- Não defina a sinalização FLAG_ACTIVITY_NEW_TASK em intents de inicialização de atividade ou use com FLAG_ACTIVITY_CLEAR_TASK.
9.3- Defina explicitamente o android:usesCleartextTraffic="true" na tag application e defina um Android Network Security
Config (https://developer.android.com/training/articles/security-config). Este atributo indica se o aplicativo pretende usar
tráfego de rede de texto não criptografado, como HTTP de texto não criptografado.
9.4- Defina explicitamente o atributo android:hasFragileUserData na tag application com o valor apropriado. Este atributo
especifica quando o usuário desinstala um aplicativo, se deve ou não mostrar ao usuário um prompt para manter os dados do
aplicativo. O valor padrão é falso.
9.5- Resolvendo vulnerabilidade da versão do OpenSSL. As versões antigas do OpenSSL apresentam vulnerabilidades na segurança
que foram corrigidas na versão 1.1.1g.
Para solucionar devemos atualizar o Realm DB para 10.4.0 conforme link: https://github.com/realm/realm-java/blob/master/CHANGELOG.md
Comando para verificar qual versão seu APK possui:
(unzip -p app-release.apk | strings | grep "OpenSSL")
Se der erro ao compilar executar os comandos:
chmod +x gradlew
./gradlew clean
10- Adicionar compatibilidade com Java 8 adicionando no arquivo build.gradle (:app), abaixo de buildTypes:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
11- A função getFragmentManager() foi depreciada, substituir por getActivity().getSupportFragmentManager()
12- Substituir o componente Switch por SwitchCompact ou SwitchMaterial;
13- AsyncTask foi depreciada. Implementar o Retrofit para realizar requisições com o servidor;
14- Handler.Callback() foi depreciada, substituir por Handler(Looper.getMainLooper()) ;
15- Interface LoaderCallbacks foi depreciada. Remover da LoginActivity pois só lida com autocomplete do campo username;
16- O metodo onActivityResult() foi depreciado. Utilizar a nova Result API para lidar com isso. Exemplo:
Fragment MyFragment() extends Fragment {
Button titlePhoto;
ImageView imageView;
ActivityResultLauncher<Intent> activityResultLauncher;
@Override
public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// registra callback de captura da foto
activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
try {
Bundle bundle = result.getData().getExtras();
Bitmap thumbnail = (Bitmap) bundle.get("data"); // retona o thumbnail da foto
// Cria o diretorio do arquivo
File dir = new File(SysHelper.getAppDirectory(getContext()) + "steps/" + mStepUuid + "/photos/cancel/");
if (!dir.exists()) {
dir.mkdirs();
}
// Nome da imagem
String imgName = "IMG_" + System.currentTimeMillis() + ".jpg";
String path = dir.getAbsolutePath() + "/" + imgName;
// Adiciona a marca d' agua na foto
Bitmap bitmap = SysHelper.setImageWaterMark(thumbnail);
// Salva a imagem fisicamente
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(new File(path)));
// Corrige a orientacao da imagem
Bitmap outputImg = SysHelper.rotateImage(bitmap, path);
// Salva a imagem corrigida
outputImg.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(new File(path)));
mPhoto = path; // armazena o caminho do arquivo da foto
// Atribui a imagem ao ImageView
if (imageView != null) {
imageView.setImageBitmap(outputImg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Button take foto
btnTakePhoto = view.findViewById(R.id.btn_take);
btnTakePhoto.setOnClickListener(view12 -> {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
activityResultLauncher.launch(intent);
});
// ImageView
imageView = view.findViewById(R.id.img_photo_reason);
}
}
Observações: O metodo acima retorna somente o thumbnail da foto capturada, caso queira salvar a foto em tamanho e qualidade reais, é necessário criar um FileProvider e criar um arquivo físico para salvar a imagem e retornar o Uri da mesma.
Referências:
https://www.youtube.com/watch?v=qO3FFuBrT2E
https://developer.android.com/training/camera/photobasics?hl=pt-br
https://wajahatkarim.com/2020/05/activity-results-api-onactivityresult/
17- Requisição de permissões. O modo atual não funciona no Android 11 - API 30. Então devemos criar um callback.
Adicionar essas linhas no build.gradle:
implementation 'androidx.activity:activity:1.2.3'
implementation 'androidx.fragment:fragment:1.3.5'
Exemplo (MainActivity):
final private int PERMISSION_CODE = 100;
private ActivityResultLauncher<String[]> multiplePermissionActivityResultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// callback da requisicao de permissoes
multiplePermissionActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
if (isGranted.containsValue(false)) {
Log.d("PERMISSIONS", "At least one of the permissions was not granted, launching again...");
runtimePermissions();
} else {
initiateServices();
redirectUser();
}
});
// exige as permissoes e inicia o servico de localizacao
runtimePermissions();
}
/** Retorna array de permissoes **/
private ArrayList<String> getPermissionsList() {
ArrayList<String> permisssionsList = new ArrayList<>();
permisssionsList.add(Manifest.permission.ACCESS_FINE_LOCATION);
permisssionsList.add(Manifest.permission.CAMERA);
permisssionsList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
permisssionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
permisssionsList.add(Manifest.permission.VIBRATE);
permisssionsList.add(Manifest.permission.RECORD_AUDIO);
permisssionsList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS);
permisssionsList.add(Manifest.permission.READ_PHONE_STATE);
// Permissoes para (Android X somente)
if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.Q) {
// Permissao de acesso a localizacao em segundo plano
permisssionsList.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
return permisssionsList;
}
/** Requisita todas as permissoes dos componentes externos utilizados pelo app **/
private void runtimePermissions() {
ArrayList<String> permisssionsList = getPermissionsList();
// converte o arraylist para array de strings
String[] permissionsArray = new String[permisssionsList.size()];
permissionsArray = permisssionsList.toArray(permissionsArray);
// Android 10 ou inferior
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.Q) {
if(!hasPermissions(this, permisssionsList)) {
ActivityCompat.requestPermissions(this, permissionsArray, PERMISSION_CODE);
} else {
// redireciona o usuario para a respectiva pagina
initiateServices();
redirectUser();
}
} else {
// Android 11 ou superior
multiplePermissionActivityResultLauncher.launch(permissionsArray);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == PERMISSION_CODE) {
Log.d("DEV" , "VALIDANDO PERMISSAO");
if(grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.d("DEV" , "PERMISSAO LIBERADA");
initiateServices();
redirectUser();
} else {
Log.d("DEV" , "PERMISSAO NEGADA");
runtimePermissions();
}
}
}
/** Verifica se o App tem as permissoes declaradas no manifest **/
private boolean hasPermissions(Context context, ArrayList<String> permissions) {
if (context != null && permissions != null) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
Observações: A permissão Manifest.permission.ACCESS_BACKGROUND_LOCATION deve ser solicitada separadamente no Android 11, ao solicitar junto com outras o Android irá ignorar todas as outras. Para isso solicite essa permissão em outro momento no app ou diga ao usuário para habilitá-la manualmente.
Acesse este link paa saber detalhes sobre a solicitação de acesso a localização em segundo plano: https://developer.android.com/training/location/permissions#request-background-location
Referências:
https://stackoverflow.com/questions/66475027/activityresultlauncher-with-requestmultiplepermissions-contract-doesnt-show-per
https://wajahatkarim.com/2018/11/multiple-runtime-permissions-in-android-without-any-third-party-libraries/
https://developer.android.com/training/permissions/requesting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment