¿Cómo podemos optimizar (cpu & ram) esta animación de salpicadura personalizada de android?

He desarrollado una animación personalizada para mi actividad de Splash Screen:

=> Aquí está una animación que muestra lo que está sucediendo:

Introduzca aquí la descripción de la imagen

Por supuesto mi aplicación real:

  • Es con diferentes imágenes (fullhd)
  • Es un poco más lento comparar con el GIF: 3s para 60 pantallas intermedias.

Mi diseñador me proporcionó 60 archivos png.

=> Un ejemplo para ilustrar:

Introduzca aquí la descripción de la imagen

Mi objetivo es:

  1. Comenzar desde un logotipo central (aquí con SO) con una imagen inferior (Apple)
  2. Ejecutar una animación morphing
  3. Terminar en la pantalla como la página principal de la aplicación

Para ejecutar esto, tengo un diseño Multilayer para la SpashScreenActivity con:

  • BackGround (invisible): el diseño de la homePage (MainActivity)
  • MiddleGround: con el ImageView de Apple que es reemplazado por el Droid y el bottomBar que crecen
  • FrontGround: el logotipo en un ImageView con su lema en un TextView

Aquí está el código xml para el diseño de SpashScreen:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="bottom"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" android:orientation="vertical"> <FrameLayout android:id="@+id/fl_logo_top_marge_hidden" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:visibility="invisible" android:background="@color/colorPrimary" /> <include android:visibility="invisible" android:id="@+id/l_logo_activate_hidden" layout="@layout/part_logo_activate" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:visibility="invisible" android:id="@+id/fl_logo_bottom_marge_hidden" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@color/colorPrimary" /> <fr.millezimsolutions.app.splashanimation.SquareAspectWidthBasedImageView android:visibility="invisible" android:id="@+id/iv_home_hidden" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/p_log_android" /> <FrameLayout android:visibility="invisible" android:id="@+id/fl_bar_hidden" android:layout_width="match_parent" android:layout_height="@dimen/start_degustation_bar_height" android:background="@color/colorAccent" android:gravity="bottom" /> </LinearLayout> <LinearLayout android:id="@+id/fl_middle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentBottom="true" android:background="@color/colorPrimary" android:gravity="bottom" android:orientation="vertical"> <fr.millezimsolutions.app.splashanimation.FitXCropTopImageView android:id="@+id/iv_slogan" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorTransparent" android:scaleType="fitStart" android:src="@drawable/p_log_apple" /> <FrameLayout android:id="@+id/fl_logo_bottom_bar" android:layout_width="match_parent" android:layout_height="0dp" android:background="@color/colorAccent" android:gravity="bottom" /> </LinearLayout> <LinearLayout android:id="@+id/fl_front" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <FrameLayout android:id="@+id/fl_logo_top_layout" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="40" android:background="@color/colorPrimary" /> <FrameLayout android:id="@+id/fl_logo_top_marge" android:layout_width="match_parent" android:layout_height="5dp" android:background="@color/colorTransparent" /> <include android:id="@+id/l_logo_activate" layout="@layout/part_logo_activate" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/fl_logo_bottom_marge" android:layout_width="match_parent" android:layout_height="5dp" android:background="@color/colorTransparent" /> <FrameLayout android:id="@+id/fl_logo_bottom_layout" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="40" android:background="@color/colorTransparent" /> </LinearLayout> </RelativeLayout> 

2 El código xml para la parte superior del diseño (vía include)

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorTransparent" android:gravity="bottom" android:orientation="vertical"> <FrameLayout android:id="@+id/fl_home_marginTop" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <ImageView android:id="@+id/iv_millezimuLogo" android:layout_width="wrap_content" android:layout_height="64dp" android:layout_gravity="center_horizontal" android:layout_marginLeft="@dimen/marge" android:layout_marginRight="@dimen/marge" android:src="@drawable/p_log_so" /> <TextView android:id="@+id/tv_slogan" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginBottom="@dimen/marge_small" android:layout_marginTop="@dimen/marge_small_border" android:gravity="center" android:hint="" android:text="Bonjour" android:textColor="@color/colorAccent" android:textSize="20sp" /> <ImageView android:id="@+id/iv_sponsorLogo" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_gravity="center_horizontal" android:layout_marginLeft="@dimen/marge" android:layout_marginRight="@dimen/marge" android:src="@drawable/p_log_so" android:visibility="gone" /> <TextView android:id="@+id/tv_sponsorLogo" android:layout_width="match_parent" android:layout_height="50dp" android:layout_gravity="center_horizontal" android:layout_marginLeft="@dimen/marge" android:layout_marginRight="@dimen/marge" android:gravity="center" android:textColor="@color/colorAccent" android:textSize="20sp" android:visibility="gone" /> <FrameLayout android:id="@+id/fl_home_marginBottom" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout> 

3 Aquí está el código de la Actividad.

  import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; public class SplashScreenActivity extends AppCompatActivity { // Splash screen timer private static int SPLASH_TIME_OUT = 3000; private int finalTopMarge, finalBottomMarge, topMargeDec, bottomMargeInc; long currentTimeStamp; private int[] mSplashAnimFrames = {R.drawable.p_wave_spashscreen_00, R.drawable.p_wave_spashscreen_01, R.drawable.p_wave_spashscreen_02, R.drawable.p_wave_spashscreen_03, R.drawable.p_wave_spashscreen_04, R.drawable.p_wave_spashscreen_05, R.drawable.p_wave_spashscreen_06, R.drawable.p_wave_spashscreen_07, R.drawable.p_wave_spashscreen_08, R.drawable.p_wave_spashscreen_09, R.drawable.p_wave_spashscreen_10, R.drawable.p_wave_spashscreen_11, R.drawable.p_wave_spashscreen_12, R.drawable.p_wave_spashscreen_13, R.drawable.p_wave_spashscreen_14, R.drawable.p_wave_spashscreen_15, R.drawable.p_wave_spashscreen_16, R.drawable.p_wave_spashscreen_17, R.drawable.p_wave_spashscreen_18, R.drawable.p_wave_spashscreen_19, R.drawable.p_wave_spashscreen_20, R.drawable.p_wave_spashscreen_21, R.drawable.p_wave_spashscreen_22, R.drawable.p_wave_spashscreen_23, R.drawable.p_wave_spashscreen_24, R.drawable.p_wave_spashscreen_25, R.drawable.p_wave_spashscreen_26, R.drawable.p_wave_spashscreen_27, R.drawable.p_wave_spashscreen_28, R.drawable.p_wave_spashscreen_29, R.drawable.p_wave_spashscreen_30, R.drawable.p_wave_spashscreen_31, R.drawable.p_wave_spashscreen_32, R.drawable.p_wave_spashscreen_33, R.drawable.p_wave_spashscreen_34, R.drawable.p_wave_spashscreen_35, R.drawable.p_wave_spashscreen_36, R.drawable.p_wave_spashscreen_37, R.drawable.p_wave_spashscreen_38, R.drawable.p_wave_spashscreen_39, R.drawable.p_wave_spashscreen_40, R.drawable.p_wave_spashscreen_41, R.drawable.p_wave_spashscreen_42, R.drawable.p_wave_spashscreen_43, R.drawable.p_wave_spashscreen_44, R.drawable.p_wave_spashscreen_45, R.drawable.p_wave_spashscreen_46, R.drawable.p_wave_spashscreen_47, R.drawable.p_wave_spashscreen_48, R.drawable.p_wave_spashscreen_49, R.drawable.p_wave_spashscreen_50, R.drawable.p_wave_spashscreen_51, R.drawable.p_wave_spashscreen_52, R.drawable.p_wave_spashscreen_53, R.drawable.p_wave_spashscreen_54, R.drawable.p_wave_spashscreen_55, R.drawable.p_wave_spashscreen_56, R.drawable.p_wave_spashscreen_57, R.drawable.p_wave_spashscreen_58, R.drawable.p_wave_spashscreen_59}; private final int C_STOP = 120, C_MOVE = 40, C_BAR = 80; private int bottomBarRatio; private ImageView finalImageView; private int targetWidth, targetHeight; private Rect mImageViewRect; private Paint paint; private Bitmap original; private Bitmap result; private boolean setupOk = false; private ImageView mImageView; private Bitmap mask; private FrameLayout ltm; private FrameLayout lbm; private FrameLayout lbb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash_screen); // Indique que l'ecran est full Screen getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); ImageManager.create(this); } @Override protected void onResume() { super.onResume(); int delay = SPLASH_TIME_OUT / C_STOP; bottomBarRatio = getResources().getDimensionPixelSize(R.dimen.bar_nav_height) / (C_STOP - C_BAR); runCycle(0, delay); } private void logStamp() { long oldTimeStamp = currentTimeStamp; currentTimeStamp = System.currentTimeMillis(); long delay = currentTimeStamp - oldTimeStamp; Log.v("TIMESTAMP", String.valueOf(delay)); } public void runCycle(final int cycle, final int delay) { if (BuildConfig.DEBUG) logStamp(); Handler cyclic = new Handler(); cyclic.postDelayed(new Runnable() { @Override public void run() { if (cycle >= C_STOP) { closeActivity(); } else { runCycle(cycle + 1, delay); if (cycle >= C_MOVE) { // Copy des hauteurs pour les marges initFinalLogoMargeHeight(); // Decroissance du poid de layout superieur MoveUpLogo(); // bouger la bar if (cycle >= C_BAR) { updateBottomBar(cycle - C_BAR); } findViewById(R.id.fl_front).requestLayout(); } if (setupFinalView()) { if ((cycle % 2) == 0) updateImageViewLight(cycle / 2); } } } }, delay); } private boolean setupFinalView() { if (!setupOk) { finalImageView = (ImageView) findViewById(R.id.iv_home_hidden); targetWidth = finalImageView.getWidth(); targetHeight = finalImageView.getHeight(); mImageViewRect = new Rect(0, 0, finalImageView.getWidth(), finalImageView.getHeight()); mImageView = (ImageView) findViewById(R.id.iv_slogan); mImageView.setBackgroundResource(R.drawable.p_log_apple); paint = new Paint(Paint.ANTI_ALIAS_FLAG); ltm = (FrameLayout) findViewById(R.id.fl_logo_top_marge); lbm = (FrameLayout) findViewById(R.id.fl_logo_bottom_marge); lbb = ((FrameLayout) findViewById(R.id.fl_logo_bottom_bar)); if (targetWidth > 0 && targetHeight > 0) { original = ImageManager.decodeSampledBitmapFromResource(getResources(), R.drawable.p_log_android, targetWidth, targetHeight); result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_4444); setupOk = true; } } return setupOk; } private void MoveUpLogo() { ViewGroup.LayoutParams ltmp = ltm.getLayoutParams(); ltmp.height -= topMargeDec; ViewGroup.LayoutParams lbmp = lbm.getLayoutParams(); lbmp.height += bottomMargeInc; } private void initFinalLogoMargeHeight() { if (finalBottomMarge == 0) { finalTopMarge = findViewById(R.id.fl_logo_top_marge_hidden).getHeight(); topMargeDec = (findViewById(R.id.fl_logo_top_marge).getHeight() - finalTopMarge) / C_BAR; finalBottomMarge = findViewById(R.id.fl_logo_bottom_marge_hidden).getHeight() + findViewById(R.id.fl_bar_hidden).getHeight() + findViewById(R.id.iv_home_hidden).getHeight(); bottomMargeInc = (finalBottomMarge - findViewById(R.id.fl_logo_bottom_marge).getHeight()) / C_BAR; } } private void updateBottomBar(int cycle) { LinearLayout.LayoutParams lbbp = (LinearLayout.LayoutParams) lbb.getLayoutParams(); lbbp.height = cycle * bottomBarRatio; lbb.setLayoutParams(lbbp); } private void closeActivity() { overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); Intent i = new Intent(SplashScreenActivity.this, MainActivity.class); startActivity(i); finish(); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); } private int getNext(int index) { if (index < (mSplashAnimFrames.length - 1)) index++; else index = mSplashAnimFrames.length - 1; return mSplashAnimFrames[index]; } public void updateImageViewLight(int index) { mask = ImageManager.decodeSampledBitmapFromResource(getResources(), getNext(index), targetWidth, targetHeight); Canvas mCanvas = new Canvas(result); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mCanvas.drawBitmap(original, null, mImageViewRect, null); mCanvas.drawBitmap(mask, null, mImageViewRect, paint); paint.setXfermode(null); mImageView.setImageBitmap(result); } } 

4 Y el código del ImageManager para la comprensión (yo uso UIL)

 public class ImageManager { private static Context context; public static ImageLoader getImageLoader() { return ImageLoader.getInstance(); } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.outWidth = reqWidth; options.outHeight = reqHeight; options.inJustDecodeBounds = true; options.inPreferredConfig = Bitmap.Config.RGB_565; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return getResourceImageForCanvas(resId, new ImageSize(reqWidth, reqHeight)); } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } public static Bitmap getResourceImageForCanvas(int bitmapResourceId, ImageSize targetImageSize) { DisplayImageOptions options = new DisplayImageOptions.Builder().bitmapConfig(Bitmap.Config.RGB_565).build(); return getImageLoader().loadImageSync("drawable://" + bitmapResourceId, targetImageSize, options); // } public static void create(Context context) { try { ImageManager.context = context; initImageLoader(); } catch (IOException e) { e.printStackTrace(); } } private static void initImageLoader() throws IOException { // Create global configuration and initialize ImageLoader with this // configuration BitmapFactory.Options opt = new BitmapFactory.Options(); // opt.inScaled = false; opt.inSampleSize = 1; opt.inDither = true; opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPreferQualityOverSpeed = false; DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()./* cacheInMemory(true). */cacheOnDisk(true).decodingOptions(opt).imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) .bitmapConfig(Bitmap.Config.RGB_565).build(); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).defaultDisplayImageOptions(defaultOptions).memoryCacheSizePercentage(13).writeDebugLogs().build(); ImageLoader.getInstance().init(config); } } 

ESTADO:

El método updateImageViewLight puede ayudar a otros a tratar este tipo de comportamiento (PortedDuff …) que no fue fácil de encontrar.

La animación funciona bien en un dispositivo potente, pero a menudo se retrasa si el dispositivo o la aplicación hace algo más.

He intentado ejecutar este cálculo en Async Task pero era menos potente que en el mainThread

PREGUNTAS:

Estoy buscando cualquier consejo educado sobre mi implementación, que puede ayudar a mejorar:

  • Consumo de memoria
  • uso de CPU

Pero también :

  • Fuga potencial
  • Legibilidad del código

Aún no responde completamente, pero es un intento, que necesita ser continuado y comentado.

Primera optimización:

  • Cree una vista personalizada (aquí la extensión ImageView ) para la onda, que se actualizará en el método onDraw
  • Define miembro const como miembros de clase static final
  • Como más posible, evite la creación de instancias de objetos en onDraw ( Canvas y Paint ), así como la actualización innecesaria de objetos

Aquí está el código del CustomView que extiende ImageView

  public class WaveFillingImageView extends ImageView { private final static int[] mSplashAnimFrames = {R.drawable.p_wave_spashscreen_00, R.drawable.p_wave_spashscreen_01, R.drawable.p_wave_spashscreen_02, R.drawable.p_wave_spashscreen_03, R.drawable.p_wave_spashscreen_04, R.drawable.p_wave_spashscreen_05, R.drawable.p_wave_spashscreen_06, R.drawable.p_wave_spashscreen_07, R.drawable.p_wave_spashscreen_08, R.drawable.p_wave_spashscreen_09, R.drawable.p_wave_spashscreen_10, R.drawable.p_wave_spashscreen_11, R.drawable.p_wave_spashscreen_12, R.drawable.p_wave_spashscreen_13, R.drawable.p_wave_spashscreen_14, R.drawable.p_wave_spashscreen_15, R.drawable.p_wave_spashscreen_16, R.drawable.p_wave_spashscreen_17, R.drawable.p_wave_spashscreen_18, R.drawable.p_wave_spashscreen_19, R.drawable.p_wave_spashscreen_20, R.drawable.p_wave_spashscreen_21, R.drawable.p_wave_spashscreen_22, R.drawable.p_wave_spashscreen_23, R.drawable.p_wave_spashscreen_24, R.drawable.p_wave_spashscreen_25, R.drawable.p_wave_spashscreen_26, R.drawable.p_wave_spashscreen_27, R.drawable.p_wave_spashscreen_28, R.drawable.p_wave_spashscreen_29, R.drawable.p_wave_spashscreen_30, R.drawable.p_wave_spashscreen_31, R.drawable.p_wave_spashscreen_32, R.drawable.p_wave_spashscreen_33, R.drawable.p_wave_spashscreen_34, R.drawable.p_wave_spashscreen_35, R.drawable.p_wave_spashscreen_36, R.drawable.p_wave_spashscreen_37, R.drawable.p_wave_spashscreen_38, R.drawable.p_wave_spashscreen_39, R.drawable.p_wave_spashscreen_40, R.drawable.p_wave_spashscreen_41, R.drawable.p_wave_spashscreen_42, R.drawable.p_wave_spashscreen_43, R.drawable.p_wave_spashscreen_44, R.drawable.p_wave_spashscreen_45, R.drawable.p_wave_spashscreen_46, R.drawable.p_wave_spashscreen_47, R.drawable.p_wave_spashscreen_48, R.drawable.p_wave_spashscreen_49, R.drawable.p_wave_spashscreen_50, R.drawable.p_wave_spashscreen_51, R.drawable.p_wave_spashscreen_52, R.drawable.p_wave_spashscreen_53, R.drawable.p_wave_spashscreen_54, R.drawable.p_wave_spashscreen_55, R.drawable.p_wave_spashscreen_56, R.drawable.p_wave_spashscreen_57, R.drawable.p_wave_spashscreen_58, R.drawable.p_wave_spashscreen_59}; private Paint paint; private long nextDrawTimeStamp; private boolean init = false, isStarted = false; private Bitmap original, result; private Rect mImageViewRect; private int index = 0; private LogAndStat las; private Canvas mCanvas; private final int timeTick = 50; public WaveFillingImageView(Context context) { super(context); init(context); } public WaveFillingImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public WaveFillingImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } @DebugLog private void init(Context context) { paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); las = new LogAndStat("View", mSplashAnimFrames.length); } public void start() { isStarted = true; nextDrawTimeStamp = System.currentTimeMillis(); invalidate(); } @DebugLog @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Before Layout if (getWidth() == 0 || getHeight() == 0) return; // Init variables if (!init) { las.logStamp("init onDraw"); original = ImageManager.decodeSampledBitmapFromResourcewithUIL(getResources(), R.drawable.p_log_android, getWidth(), getHeight()); result = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444); mCanvas = new Canvas(result); mImageViewRect = new Rect(0, 0, getWidth(), getHeight()); init = true; las.logStamp("init onDraw"); } // If tick reached for refresh if (System.currentTimeMillis() >= nextDrawTimeStamp) { nextDrawTimeStamp += timeTick; las.logStamp(index); Bitmap mask = ImageManager.decodeSampledBitmapFromResourcewithUIL(getResources(), getResourceForNextCycle(index), mImageViewRect.width(), mImageViewRect.height()); mCanvas.drawBitmap(original, null, mImageViewRect, null); mCanvas.drawBitmap(mask, null, mImageViewRect, paint); canvas.drawBitmap(result, 0, 0, null); index++; } if (isStarted) // Invalidate during animation to call again on Draw if (index < mSplashAnimFrames.length) { las.logStamp("invalidate"); invalidate(); } else { las.logStats(); } } @DebugLog private int getResourceForNextCycle(int index) { if (index < (mSplashAnimFrames.length - 1)) index++; else index = mSplashAnimFrames.length - 1; return mSplashAnimFrames[index]; } @DebugLog @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); } } 

Resultados:

  • La animación se ahoga, y parece que toma menos recursos. Sin embargo, está completamente sin sincronizar con la animación de diseño. Necesidad de hacer eso. ¿Cuales son las mejores prácticas?
  • Éste es claramente el paso init del método onDraw que toma tiempo en el primer ciclo
  • Trabajando en lienzo temporal para finalmente dibujar el lienzo en el lienzo de la vista principal mejoró mucho
  • Optimización de SQLite para aplicaciones de Android
  • ¿Deshacer / rehacer rápidamente con el patrón del memento / comando?
  • Bucle eficiente a través de la lista de Java
  • Optimización: Acceso a campos y métodos
  • ¿View.setVisibility (View.VISIBLE) obliga a la vista a volver a dibujar incluso si ya es visible?
  • Optimización del compilador de código Java en Android
  • Creación de perfiles y optimización de un juego Android
  • ¿Canvas.getClipBounds asigna un objeto Rect?
  • ¿Cómo puedo activar adecuadamente ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS?
  • Evitar que Proguard elimine elementos estirables específicos
  • Picasso "Cambiar tamaño y centerCrop" o ImageView "centerCrop"?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.