Post on 10-May-2015
description
DICAS PARA INTERFACES PERFORMÁTICAS
NO SEU APP ANDROID INTEL SOFTWARE DAY 2013
CONTEXTO
INTERAÇÕES COM A UISonho 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...
COMO PERDER FPSs UI Thread pausada na hora da interação
UI Thread está executando alguma operação (lenta) no momento da interação
GARBAGE COLLECTIONPOR QUÊ ELE IMPORTA
GARBAGE COLLECTIONAndroid 2.3+ implementa variante de CMS
Pausas prolongadas e/ou frequentes durante a interação do usuário PRECISAM ser evitadas
FORÇA PAUSAS DA UI THREAD PARA GC
NÃO GERE LIXO DESNECESSÁRIO
AUTO BOXING Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1024, "Android"); map.put(2048, "performance"); map.put(5096, "matters"); List<Double> values = new ArrayList<Double>(); values.add(3.1415);
map.put(1024, "Android");
new Integer(1024);
SPARSE ARRAYS
SparseArray<String> sparseArray = new SparseArray<String>(); sparseArray.put(665, "Android"); sparseArray.put(666, "rocks");
STRINGS String facebookAvatarURL =
"http://graph.facebook.com/" + facebookID + "/picture?type=large";
String numberStr = "" + number;
DON’T
STRINGS String facebookAvatarURL =
"http://graph.facebook.com/" + facebookID + "/picture?type=large";
....
String numberStr = "" + number;
new StringBuilder();
SEMPRE ÓTIMO ??? StringBuilder builder = new StringBuilder(); for (int i = 0; i < count; i++) { builder.append("ANDROID"); } return builder.toString();
FATOSStringBuilder 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...
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
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); } }
FATOSArrayList, 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
BOAS PRÁTICASDIMENSIONE o tamanho das suas listas
EVITE adicionar elementos em uma posição específica da lista
List<FacebookFriend> friends = new ArrayList<FacebookFriend>(500);
friends.add(10,friend);
ABSTRAÇÕES EM EXCESSO ...
REUSAR SEMPREQUE POSSÍVEL
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
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); } } }
Um random por bloco
Novo bloco a cada chamada da engine
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); } } }
Random agora é constante
Reuse um bloco previamente existente !!!
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
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
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);
((TextView) cellView.findViewById(R.id.title)).setText(i.getTitle()); ((TextView) cellView.findViewById(R.id.subtitle)).setText(i.getSubs()); return cellView; } DO
N’T
@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 !
E PARA ESSE TIPO DE LISTA ????
EXECUÇÃO ÓTIMACODIFIQUE PARA A PERFORMANCE
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 !!!
PARCELLABLE E SERIALIZABLE
http://www.developerphil.com/parcelable-vs-serializable/
PARCELLABLE E SERIALIZABLESerializable é muito mais fácil de implementarParcellable é muito mais eficiente na prática
Serializable para um objetoParcellable para coleções de objetos
CACHE DE OPERAÇÕESstatic class ViewHolder {
public TextView friendName; public TextView friendStatus; public ImageView friendImage; }
Cachear a posição das Views na hierarquia de Views da linha !
public abstract class FasterArrayAdapter<T> extends ArrayAdapter<T> {
@Override public View getView(int position, View convertView, ViewGroup parent) {
Object viewHolder = null;
if (convertView == null) {
viewHolder = new ViewHolder(); convertView = mInflater.inflate(layoutResourceForItem(), parent, false); setupHolder(convertView, viewHolder); convertView.setTag(viewHolder);
} else { viewHolder = convertView.getTag(); }
fillHolder(viewHolder, position); return convertView; } }
public abstract class FasterArrayAdapter<T> extends ArrayAdapter<T> {
@Override public View getView(int position, View convertView, ViewGroup parent) {
Object viewHolder = null;
if (convertView == null) { convertView = mInflater.inflate(layoutResourceForItem(), parent, false);
viewHolder = new ViewHolder(); setupHolder(convertView, viewHolder); convertView.setTag(viewHolder);
} else { viewHolder = convertView.getTag(); }
fillHolder(viewHolder, position); return convertView; } }
Recupera o cache
Calcula posições e faz cache
Preenche seu item
LEMBRETES GERAIS
EVITE annotations em tempo de execução
EVITE malabarismos com java.lang.reflect
OTIMIZE o acesso à suas variáveis
http://developer.android.com/training/articles/perf-tips.html
MULTITHREADINGHARD WORK FORA DA UI THREAD
JAVA THREADSUSE CASO TENHA CERTEZA ABSOLUTA DO QUE ESTÁ FAZENDO !!Se o item anterior for cumprido, então priorize a UI Thread
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
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"); } }
PROBLEMAS COM ASYNCTASKComportamento é inconsistente ao longo das versões de API do Android
Difícil para cancelar tarefas já executando em background ...
USANDO ASYNCTASKAssegure-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
INTENT SERVICEService 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)
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);
LAYOUTSVOCÊ JÁ OTIMIZOU O SEU HOJE?
HIERARQUIA DE VIEWS
A Crazy TitleA simple subtitle
Nested Linear Layout
Relative Layout
PROCESSAMENTO DE LAYOUTSMais lento conforme
Profundidade da hierarquia de ViewsQuantidade de Views por hierarquia
Profilling via delay nos métodos onMeasure( ), onLayout( ), onDraw( )
OTIMIZAÇÕES EM LAYOUTSPelo 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 !!!
FLATTENING
GRID LAYOUTRELATIVE LAYOUT
COMPOUND DRAWABLES
LAZY LOADING DE VIEWS ...
<ViewStub android:id="@+id/stub" android:layout_width="fill_parent" android:layout_height="fill_parent" android:inflatedId="@+id/inflatedLayout" android:layout="@layout/lazy_layout" />
View inflated = ((ViewStub) findViewById(R.id.stub)).inflate();
IMAGENSSã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
CONCLUSÕESA HORA DE PRESTAR ATENÇÃO !!!
A EXPERIÊNCIA COM A UI CAI
UM FPS POR VEZ
NÃO GERE LIXO DESNECESSÁRIO
REUSE SEMPRE QUE POSSÍVEL
CODIFIQUE PARA O DESEMPENHO
HARD WORK FORADA UI THREAD
OTIMIZE SEUS LAYOUTS
“DON T̀ AIM CORRECT, AIM FOR AWESOME”
Lucas Rocha, Firefox for Android
ANDROID DEVELOPER
@ubiratanfsoares
gplus.to/ubiratanfsoares
ubiratansoares.com.br/blog