Notification: Reuse drawable in Header if Icon unchanged

Mitigates an issue where a LevelListDrawable would constantly
be reloaded even if unchanged. To avoid this, small icons are
now only reloaded if they no longer point to the same resource.

Note that StatusBarIconView already has this logic.

Change-Id: I6be436e5cef7b7ca91a28edc413b1aaa0f1007d5
Fixes: 30496073
This commit is contained in:
Adrian Roos
2016-08-02 18:30:34 -07:00
parent 88d57db0ad
commit c4337a3569
3 changed files with 181 additions and 2 deletions

View File

@@ -3256,7 +3256,8 @@ public class Notification implements Parcelable
* Resets the notification header to its original state
*/
private void resetNotificationHeader(RemoteViews contentView) {
contentView.setImageViewResource(R.id.icon, 0);
// Small icon doesn't need to be reset, as it's always set. Resetting would prevent
// re-using the drawable when the notification is updated.
contentView.setBoolean(R.id.notification_header, "setExpanded", false);
contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);

View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.internal.widget;
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.widget.ImageView;
import android.widget.RemoteViews;
import libcore.util.Objects;
/**
* An ImageView for displaying an Icon. Avoids reloading the Icon when possible.
*/
@RemoteViews.RemoteView
public class CachingIconView extends ImageView {
private String mLastPackage;
private int mLastResId;
private boolean mInternalSetDrawable;
public CachingIconView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
@RemotableViewMethod(asyncImpl="setImageIconAsync")
public void setImageIcon(@Nullable Icon icon) {
if (!testAndSetCache(icon)) {
mInternalSetDrawable = true;
// This calls back to setImageDrawable, make sure we don't clear the cache there.
super.setImageIcon(icon);
mInternalSetDrawable = false;
}
}
@Override
public Runnable setImageIconAsync(@Nullable Icon icon) {
resetCache();
return super.setImageIconAsync(icon);
}
@Override
@RemotableViewMethod(asyncImpl="setImageResourceAsync")
public void setImageResource(@DrawableRes int resId) {
if (!testAndSetCache(resId)) {
mInternalSetDrawable = true;
// This calls back to setImageDrawable, make sure we don't clear the cache there.
super.setImageResource(resId);
mInternalSetDrawable = false;
}
}
@Override
public Runnable setImageResourceAsync(@DrawableRes int resId) {
resetCache();
return super.setImageResourceAsync(resId);
}
@Override
@RemotableViewMethod(asyncImpl="setImageURIAsync")
public void setImageURI(@Nullable Uri uri) {
resetCache();
super.setImageURI(uri);
}
@Override
public Runnable setImageURIAsync(@Nullable Uri uri) {
resetCache();
return super.setImageURIAsync(uri);
}
@Override
public void setImageDrawable(@Nullable Drawable drawable) {
if (!mInternalSetDrawable) {
// Only clear the cache if we were externally called.
resetCache();
}
super.setImageDrawable(drawable);
}
@Override
@RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
resetCache();
super.setImageBitmap(bm);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
resetCache();
}
/**
* @return true if the currently set image is the same as {@param icon}
*/
private synchronized boolean testAndSetCache(Icon icon) {
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
String iconPackage = normalizeIconPackage(icon);
boolean isCached = mLastResId != 0
&& icon.getResId() == mLastResId
&& Objects.equal(iconPackage, mLastPackage);
mLastPackage = iconPackage;
mLastResId = icon.getResId();
return isCached;
} else {
resetCache();
return false;
}
}
/**
* @return true if the currently set image is the same as {@param resId}
*/
private synchronized boolean testAndSetCache(int resId) {
boolean isCached;
if (resId == 0 || mLastResId == 0) {
isCached = false;
} else {
isCached = resId == mLastResId && null == mLastPackage;
}
mLastPackage = null;
mLastResId = resId;
return isCached;
}
/**
* Returns the normalized package name of {@param icon}.
* @return null if icon is null or if the icons package is null, empty or matches the current
* context. Otherwise returns the icon's package context.
*/
private String normalizeIconPackage(Icon icon) {
if (icon == null) {
return null;
}
String pkg = icon.getResPackage();
if (TextUtils.isEmpty(pkg)) {
return null;
}
if (pkg.equals(mContext.getPackageName())) {
return null;
}
return pkg;
}
private synchronized void resetCache() {
mLastResId = 0;
mLastPackage = null;
}
}

View File

@@ -26,7 +26,7 @@
android:paddingBottom="16dp"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="16dp">
<ImageView
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="18dp"
android:layout_height="18dp"