android问题:notification中notify频繁调用界面会很卡,
1、直接new Notification()这种方式已经过时,因此自己也没有去细究这种方式,直接使用的是new NotificationCompat.Builder(context).build()(这个在support v4包中,下面的内容全是以这种方式来实现的)
2、自定义布局的兼容
使用NotificationCampat来做自定义布局我们可以这样做:
RemoteViews rvMain = new RemoteViews(context.getPackageName(), R.layout.notification_layout);
//TODO rvMain...
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContent(rvMain);
// TOOD ...
在4.0以上,这样是没问题,但是在2.3的时候,你会发现这样根本无效,因此我们需要换一种方式:
RemoteViews rvMain = new RemoteViews(context.getPackageName(), R.layout.notification_layout);
//TODO rmMain...
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContent(rvMain);
// TOOD ...
Notification notification = builder.build();
if(Build.VERSION.SDK_INT <= 10){
notification.contentView = rvMain;
}
3、通知栏上的操作事件
先说说我们可以处理的通知事件:
setContentIntent():用户点击通知时触发
setFullScreenIntent()://TODO 这个在通知显示的时候会被调用
setDeleteIntent():用户清除通知时触发,可以是点击清除按钮,也可以是左右滑动删除(当然了,前提是高版本)
2.3及以下是无法处理自定义布局中的操作事件的,这样我们就不要去考虑增加自定义按钮了。
二、可能会遇到的问题
1、Ticker图标显示只有中间一部分
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notice_small, 0)
我们可以通过这样的方式来设置Ticker时显示的图标。在我们测试的时候小米和sony的机器上出现过,解决办法是把之前的大图标(我们最初是72×72)替换成现在的32×32的小图标,并把它从之前的xxhdpi移到hdpi文件夹中。
2、Ticker不显示:直接显示通知的图标,中间的Ticker过程没有显示
这个没有找到是什么原因,直接把setFullScreenIntent(mPIFullScreen, false)这行注销即可
3、点击通知,通知列表不消失
这个只在华为的一个机器上遇到过,解决办法是在点击事件之后发一个关闭的广播:
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
参考:http://stackoverflow.com/questions/18261969/clicking-android-notification-actions-does-not-close-notification-drawer
4、出现各种乱
这里说的混乱是指通知没有显示出来、多条通知点击触发的事件相同、多条通知的Intent相同等等。这个时候你可能需要去检查通知的ID和PendingIntent的requestCode。
5、各种机型的适配
这个是相当混乱的,比如大多数是黑底白字,但是部分华为机型是灰底黑字;有些不支持设置自定义布局中的字体颜色;有些会屏蔽自定义布局的背景颜色和透明度,有些甚至你只能自定义通知栏的部分布局(左面的图标是改不了的):
曾经为了追求自定义布局的左侧图标和默认通知的左侧图标宽度相同,大小相仿,做了很多适配,比如各种dimens,但是仅仅是适配,还是不能支持所有机型,遂放弃了。到目前为止没找到一个完美的解决方案,市面上很多优秀的应用做的也不是非常好,对比下左侧图标宽度你就知道。最近在墨迹天气中发现这个功能:
这算不算对混乱的一种妥协呢?
三、自定义的一些扩展
从开始处理通知中的操作事件,到实现一个比现有通知更高的通知,我们的欲望在不断的增加。虽然美好的东西总是来得晚一些,但是目前来看,这些想法我们都可以实现了。
4.1中默认的三种big style可以满足我们很多时候的需求,比如:
从左到右依次应该为BigTextStyle、BigPictureStyle、InboxStyle,这个就不多说了,照着demo就能弄出来,值得注意的是,默认展开BigStyle是用两手指头平行下滑,折叠是上滑。下面是百度音乐播放器的通知效果,在此简单实现下,效果如图:
知道怎么做了,其实是很容易实现的,但关键的是不知道怎么做。首先看到这个效果,自己去尝试了各种方法,比如指定视图的高度,在不同的机器(主要是高版本的)机器上运行,发现都不行,google一下,没有结果。怎么办?反编译吧,在资源文件中发现了layout-v16文件夹下有个布局,这个布局就是我们上图的布局,在一大堆java文件中找到这样一行代码:
notification.bigContentView = *****;
怎么样?明白了吧~~就是关键句,查看api,这个属性需要16及以上才支持,因此我们在实现的时候可以这样写:
if(Build.VERSION.SDK_INT >= 16){
notification.bigContentView = rvMain;
}
将我们自己的RemoteView付给bigContentView。真正实现的时候你可能会发现,这个高度不是我们任意指定的,是的,却是如此,他的高度是100dp。这样我们就可以通过自定义RemoteView来实现各种绚丽的通知了。
四、通知NotificationManager的源码
本想在分析下NotificationManager,但是发现他的代码很少,只要完成自己的Notification然后调用它的方法就可以。具体的实现就是底层的事情了,又到软肋的
不过简单浏览还是发现了一点点——和Toast的关联,他们都使用了INotificationManager:
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
Toast的show():
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
NotificatioManager的notify():
/**
* Post a notification to be shown in the status bar. If a notification with
* the same tag and id has already been posted by your application and has not yet been
* canceled, it will be replaced by the updated information.
*
* @param tag A string identifier for this notification. May be {@code null}.
* @param id An identifier for this notification. The pair (tag, id) must be unique
* within your application.
* @param notification A {@link Notification} object describing what to
* show the user. Must not be null.
*/
public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
notification, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
依然很卡不能本质解决问题
要限制界面的刷新逻辑,不要每次都刷新,应该是判断当进度变化的时候才更新界面,给个赞吧,代码如下:
DownloadUtils.download(apkUrl, new FileDownCallBack(CACHE_DIR, CACHE_FILE) {
@Override
public void onError(Call call, Exception e, int id) {
manager.cancel(0);
notifyBuilder.setContentText("下载失败")
// Removes the progress bar
.setOngoing(false)
.setProgress(0, 0, false);
manager.notify(1, notifyBuilder.build());
map_download_task.clear();
/***stop service*****/
stopSelf();
}
@Override
public void onResponse(File response, int id) {
manager.cancel(0);
notifyBuilder.setContentText("下载完成,点击安装更新")
// Removes the progress bar
.setOngoing(false)
.setProgress(0, 0, false);
Uri uri = Uri.fromFile(response);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
notifyBuilder.setContentIntent(pendingIntent);
manager.notify(1, notifyBuilder.build());
/*****安装APK******/
installApk(response);
//stopService(updateIntent);
map_download_task.clear();
/***stop service*****/
stopSelf();
}
@Override
public void inProgress(float progress, long total, int id) {
int currProgress = (int) (100 * progress);
//防止界面卡死
if (preProgress < currProgress) {
notifyBuilder.setProgress(100, currProgress, false)
.setContentText(StringUtils.append("下载", currProgress, "%"));
// Displays the progress bar for the first time.
manager.notify(0, notifyBuilder.build());
}
preProgress = currProgress;
}
});
我也遇到了你的问题,按照官方文档的做法:https://developer.android.com/guide/topics/ui/notifiers/notifications.html
应当把频繁更新的操作放在子线程里面(sdk文档原版代码):
...
mNotifyManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("Picture Download")
.setContentText("Download in progress")
.setSmallIcon(R.drawable.ic_notification);
// Start a lengthy operation in a background thread
new Thread(
new Runnable() {
@Override
public void run() {
int incr;
// Do the "lengthy" operation 20 times
for (incr = 0; incr <= 100; incr+=5) {
// Sets the progress indicator to a max value, the
// current completion percentage, and "determinate"
// state
mBuilder.setProgress(100, incr, false);
// Displays the progress bar for the first time.
mNotifyManager.notify(0, mBuilder.build());
// Sleeps the thread, simulating an operation
// that takes time
try {
// Sleep for 5 seconds
Thread.sleep(5*1000);
} catch (InterruptedException e) {
Log.d(TAG, "sleep failure");
}
}
// When the loop is finished, updates the notification
mBuilder.setContentText("Download complete")
// Removes the progress bar
.setProgress(0,0,false);
mNotifyManager.notify(ID, mBuilder.build());
}
}
// Starts the thread by calling the run() method in its Runnable
).start();