Last active
July 22, 2021 14:20
-
-
Save EduardoSP6/d0882fb3e473963e1dea56a004968612 to your computer and use it in GitHub Desktop.
Migração de aplicativos para Android X
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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