Este documento fornece dicas para criar interfaces de usuário performáticas no Android, enfatizando a importância de evitar perdas de quadros por segundo durante a interação, reutilizar objetos sempre que possível e executar trabalhos pesados fora da thread principal de interface do usuário.
4. INTERAÇÕES COM A UI
Sonho de consumo são 60 FPS...
A cada 16ms, um FPS certamente se perde
Quanto mais FPSs perdidos, mais a
experiência do usuário com a UI degrada...
5. COMO PERDER FPSs
UI Thread pausada na hora da interação
UI Thread está executando alguma operação
(lenta) no momento da interação
7. GARBAGE COLLECTION
Android 2.3+ implementa variante de CMS
FORÇA PAUSAS DA UI THREAD PARA GC
Pausas prolongadas e/ou frequentes durante
a interação do usuário PRECISAM ser evitadas
15. SEMPRE ÓTIMO ???
StringBuilder builder = new StringBuilder();
for (int i = 0; i < count; i++) {
builder.append("ANDROID");
}
return builder.toString();
16. FATOS
StringBuilder trabalha com um array de
caracteres de tamanho pré-fixado...
Estourar o limite significa criar um novo array de
caracteres e concatenar no já existente...
17. BOAS PRÁTICAS
final String androidRocks = "android" + "rocks";
// Constantes serão otimizadas pelo compilador
// e concatenadas em tempo de compilação
final String facebookAvatarURL =
GRAPH_BASE_URL.concat(facebookID);
// String.concat() melhor para uma variável String
StringBuilder builder = new StringBuilder(200);
// Garantir que StringBuilder aloca caracteres suficientes
// evita a criação de novos arrays de caracteres
18. ARRAY LISTS
@Override
public void onCompleted(List<GraphUser> users, Response response) {
List<FacebookFriend> friends = new ArrayList<FacebookFriend>();
if (users != null) {
for (GraphUser user : users) {
FacebookFriend friend = new FacebookFriend(user);
friends.add(friend)
}
mAdapter = new FriendsAdapter(this, friends);
mFriendsList.setAdapter(mAdapter);
}
}
19. FATOS
ArrayList, HashMaps, TreeMaps trabalham sobre
Object[], uma estrutura imutável
Se o tamanho pré-definido da Collection estoura,
arrays subjacentes serão substituídos por novas
instâncias
20. BOAS PRÁTICAS
DIMENSIONE o tamanho das suas listas
List<FacebookFriend> friends = new ArrayList<FacebookFriend>(500);
EVITE adicionar elementos em uma posição
específica da lista
friends.add(10,friend);
23. OBJECT POOL
Reusar instâncias de objetos ao invés
de criar e destruir com (muita!)
frequência
http://en.wikipedia.org/wiki/Object_pool_pattern
24. Um random por bloco
public class BlocksEngine extends CCLayer {
public void blocksEngine(float dt) {
if (new Random().nextInt(300) == 0) {
getDelegate().createBlock(
new Block(Assets.block).generate(), 1, 1);
}
}
}
Novo bloco a cada
chamada da engine
25. Random agora é constante
public static final Random sRANDOM = new Random();
public class BlocksEngine extends CCLayer {
}
public void blocksEngine(float dt) {
if (sRANDOM.nextInt(300) == 0) {
final Block b = BlocksPool.acquire();
// configurar seu bloco
getDelegate().createBlock(b);
}
}
Reuse um bloco previamente existente !!!
26. private static final int MSG_DESIRED_EVENT = 0xb0b0;
public void sendMessage(Handler handler, Object eventInfo) {
final Message message = new Message();
message.what = MSG_DESIRED_EVENT;
message.obj = eventInfo;
handler.sendMessage(message);
}
Uma mensagem nova para cada
evento de interesse
27. private static final int MSG_DESIRED_EVENT = 0xb0b0;
public void sendMessage(Handler handler, Object eventInfo) {
final Message message = Message.obtain();
message.what = MSG_DESIRED_EVENT;
message.obj = eventInfo;
handler.sendMessage(message);
}
Obtém uma mensagem de um
pool, ou cria uma nova
28. ADAPTERS
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View cellView =
mInflater.inflate(R.layout.list_item, parent, false);
final MovieInfo i = getItem(position);
T
’
N
O
D
((TextView) cellView.findViewById(R.id.title)).setText(i.getTitle());
((TextView) cellView.findViewById(R.id.subtitle)).setText(i.getSubs());
return cellView;
}
29. @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, parent, false);
}
final MovieInfo i = getItem(position);
((TextView) convertView.findViewById(R.id.title))
.setText(i.getTitle());
((TextView)convertView.findViewById(R.id.subtitle))
.setText(i.getSubs());
return convertView;
}
Reusa uma View já
criada se possível !
32. FOR LOOP
List<FacebookFriend> friends = new ArrayList<FacebookFriend>();
if (users != null) {
for (GraphUser user : users) {
FacebookFriend friend = new FacebookFriend(user);
friends.add(friend)
}
}
Otimizada pelo compilador !!!
34. PARCELLABLE E SERIALIZABLE
Serializable é muito mais fácil de implementar
Parcellable é muito mais eficiente na prática
Serializable para um objeto
Parcellable para coleções de objetos
35. CACHE DE OPERAÇÕES
static class
public
public
public
}
ViewHolder {
TextView friendName;
TextView friendStatus;
ImageView friendImage;
Cachear a posição das Views na
hierarquia de Views da linha !
37. public abstract class FasterArrayAdapter<T> extends ArrayAdapter<T> {
@Override public View getView(int position, View convertView, ViewGroup parent) {
Object viewHolder = null;
Calcula posições e faz cache
if (convertView == null) {
convertView = mInflater.inflate(layoutResourceForItem(), parent, false);
viewHolder = new ViewHolder();
setupHolder(convertView, viewHolder);
convertView.setTag(viewHolder);
} else {
viewHolder = convertView.getTag();
}
Recupera o cache
fillHolder(viewHolder, position);
return convertView;
}
}
Preenche seu item
38. LEMBRETES GERAIS
OTIMIZE o acesso à suas variáveis
EVITE malabarismos com java.lang.reflect
EVITE annotations em tempo de execução
http://developer.android.com/training/articles/perf-tips.html
41. JAVA THREADS
USE CASO TENHA CERTEZA
ABSOLUTA DO QUE ESTÁ FAZENDO !!
Se o item anterior for cumprido, então priorize
a UI Thread
42. private static final int MSG_DONE = 0xcafe;
private Handler mHandler = new Handler(Looper.getMainLooper());
private void workOnBigFile(final String filePath) {
new Thread(new Runnable() {
@Override
public void run() {
Process.setThreadPriority(
Process.THREAD_PRIORITY_BACKGROUND);
File f = new File(filePath);
Data d = DataUtils.extractFrom(f);
Message.obtain(mHandler, MSG_DONE, d).sendToTarget();
}
}).start();
}
Prioridade mais baixa para sua Thread
43. ASYNCTASK
private class DownloadFilesTask extends AsyncTask<URL, Void , Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
}
}
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
}
return totalSize;
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
44. PROBLEMAS COM ASYNCTASK
Comportamento é inconsistente ao longo
das versões de API do Android
Difícil para cancelar tarefas já executando
em background ...
45. USANDO ASYNCTASK
Assegure-se de que tarefas serão
executadas em paralelo (backporting API14+)
Recomendada para tarefas curtas (segundos)
Cuidados ao declarar como inner class ou
ao associar callbacks após a execução
46. INTENT SERVICE
Service com uma Thread Worker
MUITO BOM para executar tarefas únicas
Finaliza sozinho após o trabalho executado
Callback mais burocrático após tarefa
executada (BroadcastReceiver)
47. public class HardWorkIntentService extends IntentService {
public HardWorkIntentService() {
super(“HardWorkIntentService”);
}
@Override
protected void onHandleIntent(Intent intent) {
// EXECUTE O SEU TRABALHO PESADO AQUI !!!!
}
}
Intent toHardWork = new Intent(this, HardWorkIntentService.class);
// Coloque seus parâmetros como extras!
startService(toHardWork);
50. PROCESSAMENTO DE LAYOUTS
Mais lento conforme
Profundidade da hierarquia de Views
Quantidade de Views por hierarquia
Profilling via delay nos métodos
onMeasure( ), onLayout( ), onDraw( )
51. OTIMIZAÇÕES EM LAYOUTS
Pelo menos dois pontos básicos
Conversão de declarações em XML em hierarquias
Recuperação de itens dentro de uma hierarquia
NA PRÁTICA
Precisamos de hierarquias
mais leves e planas !!!
54. IMAGENS
São redimensionados em tempo de execução
SEMPRE FORNEÇA TODOS OS CONJUNTOS DE IMAGENS
Para imagens obtidas via IO, procure
cachear os Bitmaps já processados