由于开源三方定制系统较多,请大家详细描述场景、机型及解决方案,方便其他朋友参考
[问答]-Android 开发中有哪些兼容性问题?都是怎么解决的? [问答] 你在工作中遇到的最复杂的问题或者 bug 是什么?你是怎么搞定的
场景:使用 MIPush,在华为部分手机上无法推送成功。 机型:[华为 P6,华为 P7] 解决方案:P6 和 P7 是华为的高端机型,不允许推送,防止骚扰用户,无解。
场景:魅族手机 ListView 的 Item 中的 EditText 无法编辑,点击 EditText 弹出软键盘后,软键盘会立即自动隐藏 机型:[魅族 3,魅族 4] 解决方案: 方法一:将 ListView 换成 RecyclerView
方法二:https://github.com/Aspsine/EditTextInListView
场景:HTC M8 从一个 Activity 使用 QQSDK 登陆, 登陆成功后, 返回 Activity 结果 Activity 被销毁了 机型:HTC M8 等某些带有 虚拟 Menu 键盘的手机 解决方案:后来调查发现是这个 Activity 是全屏, 屏蔽了 Menu 键盘的黑条. 但是跳转到 QQ 却把那个 Menu 的黑条显示了出来, 这导致发生了 screenSize 的变化 从而导致我的 Activity 销毁了. 知道了这个原因, 在 manifest 中的 configChanges 添加 screenSize 解决了这个问题.
场景:Android4.4 系统使用了 SystemBarTintManager 库修改透明状态栏后,会导致根布局从屏幕顶端开始布局,而不是从 ActionBar 开始布局 机型:所有 android4.4 机型 解决方案: 方法一:针对 4.4 创建一套额外的布局,即 layout-v19 文件夹,并且在根布局外层再套一层 LinearLayout,并在 LinearLayout 中添加一个属性 android:fitsSystemWindows="true"
方法二:是为 4.4 及以上添加了 paddingTop 去适配,添加 layout 觉得不好适配。
方法三:在 Build.VERSION.SDK_INT <= 18 的版本中,通过 colorDrawable.setAlpha(alpha); 设置 actionbar 背景色透明度的时候,colorDrawable 需要设置 callback。
- finalDrawable.Callback mDrawableCallback =newDrawable.Callback() {@Override
- public void invalidateDrawable(Drawable who) {
- getActionBar().setBackgroundDrawable(who);
- }@Override
- public void scheduleDrawable(Drawable who, Runnable what,longwhen) {
- }@Override
- public void unscheduleDrawable(Drawable who, Runnable what) {
- }
- };
- colorDrawable.setCallback(mDrawableCallback);
场景:Camera 拍摄,用 setPreviewFormat 设置成 YV12,预览会变成绿屏,实际用 getPreviewFormat 显示是支持 YV12 的 机型:魅族 MX3 解决方案:没办法只能设置成 NV21 了
Camera 常见问题: 1)Intent 调用手机内相机程序 如果我们设置了照片的存储路径,那么很可能会遇到一下三种问题:
问题一:onActivityResult 方法中的 data 返回为空(数据表明,93% 的机型的 data 将会是 Null,所以如果我们指定了路径,就不要使用 data 来获取照片,起码在使用前要做空判断)。 问题二:照片无法存储。 如果自定义存储路径是 / mnt/sdcard/lowry/,而手机 SD 卡下在拍照前没有名为 lowry 的文件夹,那么部分手机拍照后图片不会保存,导致我们无法获得照片,大多数手机的相机遇到文件夹不存在的情况都会自己创建出不存在的文件夹,而个别手机却不会创建,
解决的方法就是在指定存储路径前先判断路径中的文件夹是否都存在,不存在先创建再调用相机。
问题三:照片可以存储,但是名字不对。 file:///mnt/sdcard/123 1.jpg,由于 URI 的 fromFile 方法会将路径中的空格用 " " 取代。
其实对于大多数的手机这都不算事,手机在解析存储路径的时候都会将 "" 替换为空格,这样实际上最终的照片名字还是我们当初指定的名字:123 1.jpg,遗憾的是个别手机(如酷派 7260)系统自带的相机没有将" "读成空格,拍照后的照片的名字是 123 1.jpg,我们用路径"file:///mnt/sdcard/123 1.jpg" 能找到照片才怪!
总结:
(1)使用 onActivityResult 中的 intent(data) 前要做空判断。 (2)指定拍照路径时,先检查路径中的文件夹是否都存在,不存在时先创建文件夹再调用 相机拍照。 (3)指定拍照存储路径时,照片的命名中不要包含空格等特殊符号。
通过 Camera 的 open 方法调用手机摄像头 原因:第一次对焦未结束,应用层又发起的第二次对焦,引起对焦失败。 解决方案一:传入 AutoFocusCallback; 解决方案二:延时操作;
解决方案三:异常捕获。
场景:摄像头个数判断错误,当我们使用 Camera.getNumberOfCameras() 方法检测摄像头数量时返回的结果不准确,如果我们尝试打开一个不存在的摄像头肯定会抛出异常,这也提醒我们在开启 Camera 摄像头时需要加异常保护。
问题 Android 自定义 Perference 的时候,系统默认的 Perference 里 Layout 的默认值都被厂商改动了。。。一般设计到统一取值的时候,Google 都用 "?android:attrs。。。。" 的格式,但是 Google 源码在此处用了数值, 中间 title 的 margin 值所有厂商都有变动, 导致自定义的 Perference 和默认的显示不齐
解决 因为 App 的用户机型比较杂,hack 的方法比较不适用,故粘贴 Google 源码,自己重新封装, 自己统一
PopupWindow 中嵌套 EditText,会出现 EditText 长按无法触发 "粘贴" 选项,可以改成 Dialog 嵌套 EditText,包括 DialogFragment。
会调用 Activity 的 onPause 和 onStop 方法. 其他手机会保持在 onResume 状态
场景:在获取系统相机拍照然后保存在本地有时候会保存不上,获取不到地址。 问题原因:通过调试发现当拍完照返回的时候自己设的成员变量值会被回收,估计就是内存不足的原因。重启机器后就好了。 解决方案:无方案。
场景:输入法中的 emoji 适配,Android4.1 之前的系统不支持 emoji 显示
解决方案:所以对于 Android4.1 之前的系统,我采用了 bitmap 来显示 emoji。
问题: (1) 摄像头拍照后图片数据不一定能返回 ; onActivityResult 的 data 为空 (2) 三星的 camera 强制切换到横屏 导致 Activity 重启生命周期 (但是部分机型 配置 android:configChanges 也不能阻止横竖屏切换); (3) APP Activity A 调用系统拍照 –> 拍照 –> 在拍好照片的界面做几次横竖屏转换 –> 返回 APP 界面 Activity A ,A 被销毁。
解决方案:如果 activity 的销毁如果无法避免 那么在 activity 销毁之前调用 onSaveInstanceState 保存图片的路径 当 activity 重新创建的时候 会将 onSaveInstanceState 保存的文件传递给 onCreate() 当中 在 onCreate 当中 检查照片的地址是否存在文件 以此来判定拍照是否成功 Demo 下载地址: http://download.csdn.NET/detail/aaawqqq/7653475
场景:OPPO 手机启动 Service 报 SecurityException 解决方案:try catch 该异常
场景:这个问题主要在部分机型的 4.X 系统上遇见,小图标大小没有按照 24dp 裁剪,而是采用了桌面图标一样的大小 96dp
解决方案:按照标准来,小图标大小为 24dp,大图标为桌面 icon 图标大小 96dp
场景:Flyme 系统对原生 Android 源码做了修改,采用 BigPictureStyle 方式显示大图通知栏的时候,消息与大图重合了,如下图。 解决方案
首先,通过 BigPictureStyle 来实现大图功能肯定是走不通的,因为事实就摆着行不通的嘛。京东的 App 肯定是通过 RemoteViews 来实现的。于是,开始走弯路,尝试通过 RemoteViews 来展示大图。但是谷歌规定,自定义布局展示的通知栏消息最大高度是 64dp。那么,京东的 App 是怎么实现的?在尝试了各种方法以后,最后又是通过投机取巧的方式解决了问题
- private void showBigPictureNotificationWithMZ(Context context) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);Notification.Builderbuilder = new Notification.Builder(context);Notification notification = generateNotification(builder);notification.bigContentView= mRemoteViews;notificationManager.notify(notifyId, notification);}
需要先生成 Notification 的实例,然后手动给 notification.bigContentView 赋值,再 notify,就可以了
场景:当通知来的时候,如果不在通知栏浏览,会在顶部状态栏出现一个向上翻滚动画的通知消息,这条通知消息左边是一个小图标。部分系统这个小图标显示异常,是一个纯灰色的正方形,如下图。 解决方案
首先产生灰色图标的原因就是 5.0 系统引入了材料设计,谷歌强制使用带有 alpha 通道的图标,并且 RGB 的 alpha 值必须是 0(实测不为 0 也是可以的,但系统会忽略所有 RGB 值)。因此,使用 JPG 的图片是不行的,最好的代替方案就是一张背景透明的 PNG 图片。
问题详情
这个问题跟第二个有点类似,在 7.0 系统及以上,有部分应用的小图标是灰色的,大图可以正常显示。碰巧的是,显示异常的小图标,颜色都是灰色的。
解决方案
与小图标显示异常解决方案类似,将小图标替换为透明背景的 PNG 图片。
问题详情
由于系统提供的通知栏消息类型有时候不能满足要求,部分通知栏消息采用自定义 RemoteViews 来实现。采用 RemoteViews,特别是手动生成 Bitmap 然后直接传给一个自定义 Layout,再通过 setContentView 方式设置通知栏消息时,会存在各种各样的坑。
Android 通知栏的背景色有几种情况,白色、暗色、暗色透明和黑色。如果生成的 Bitmap 带背景色,这个背景色就很难选择。如果选择黑色背景,那么在白色通知栏的机型上就很难看。因此不能完全在各个系统上面完美展示出来。如果不带背景色,那么字体颜色也面临同样的困惑。试想,如果在白色的背景上显示白色的文字,用户看到白茫茫一片,是什么感受?
另一方面,大部分厂商对原生的 Android 系统都会有各种各样的改造,通知栏的样式也不例外。如果按照原生的样式来设计,那么在大部分国内厂商的机子上显示都和正常的普通通知栏消息不一样。例如华为 6.0 系统的机子,原生系统的时间线在右上角,华为的在左边,这样会给用户带来错觉。 解决方案
详见 RemoteViews 适配一节。
问题详情
每个应用基本都有自更新的逻辑,App 开机的时候提示用户升级,点击升级按钮后在 Notification 出现一个下载带进度条的通知。应用一般是在开启一个工作线程在后台下载,然后在下载的过程中通过回调更新通知栏中的进度条。我们知道,下载进度的快慢是不可控的,如果每次下载中的回调都去更新通知栏,那么可能几百毫秒、几十毫秒、甚至几毫秒就更新一次通知栏,应用可能就会 ANR,甚至崩溃。
解决方案
控制通知栏更新频率,一般控制在 0.5s 或者 1s 就可以了。在某一个更新时间间隔内下载的进度回调直接丢弃,需要注意的是下载完成的回调,需要实时回调通知栏消息显示下载完成。
问题六:恶心的后台通知和 "守护" 通知 问题详情 但凡存在后台通知或者 "守护" 通知的应用,在 7.0 系统以后都会原形毕露. 解决方案: 无
问题详情
为了提升推送到达,考拉接入了小米推送的 SDK。小米推送分为通知栏消息和透传消息,通知栏消息属于系统级推送,在 MIUI 的机子上可以在进程被杀死的情况下也能收到应用推送。然而有个问题,小米认为应用在前台时,不会回调任何方法;小米认为应用在后台的时候,收到通知栏消息的同时,会回调 onNotificationMessageArrived 方法。这时候就要小心翼翼地处理这条消息了。因为如果你的应用前后台判断逻辑和小米的不一样,那么就有可能小米帮你发了一条通知栏消息,你自己又发了一遍,造成通知栏消息的重复发送 (这个坑考拉踩过 T_T)。另一方面,在 7.0 系统的机子上,主标题和小图标的颜色是可以改变的,目前小米推送 SDK 没有开放这个接口供调用方定制。
解决方案
目前只能解决第一个问题——前后台判断的问题。应用是否在后台可以根据以下代码进行判断。在 Android 5.0 以上,可以通过 ActivityManager.RunningAppProcessInfo 判断,Android 5.0 及以下版本通过 ActivityManager.RunningTaskInfo 判断。经测试,这个方案在 Android 4.4 以上结果是可以完全匹配的。
- public static boolean isAppInBackgroundInternal(Context context) {
- ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
- List.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();if (!ListUtils.isEmpty(runningProcesses)) {
- for (ActivityManager.RunningAppProcessInforunningProcess : runningProcesses) {
- if (runningProcess.importance== ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
- return false;}
- }
- }
- } else {
- List.RunningTaskInfo> task = manager.getRunningTasks(1);if (!ListUtils.isEmpty(task)) {
- ComponentName info = task.get(0).topActivity;if (null != info) {
- return !isKaolaProcess(info.getPackageName());}
- }
- }
- return true;}
RemoteViews 适配 由于系统自带的通知栏消息样式不能完全满足产品们脑洞大开的需求,有时候我们需要自定义布局样式展示通知栏消息。Android 系统可以将自定义布局通过 setContent(7.X 系统推荐使用 setCustomContentView) 设置到 Notification.Builder 中,来实现样式的更变。setContent 方法需要传入一个 RemoteViews 对象,它是一个普通的数据类型,不是 View,作用是供其他进程展示视图。RemoteViews 只支持 4 种基本的布局 ^9:
FrameLayout LinearLayout RelativeLayout GridLayout 这些布局下面只支持几种视图控件:
AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper 只能通过上述组合生成一个 RemoteViews。
自定义布局与视图
除了上面提到的布局与控件,有没有办法自定义布局与视图呢?我们知道,任何一个 View,都可以生成一个 Bitmap 对象,支持的视图控件里有 ImageView,可以通过 ImageView.setBitmapResource() 将自定义视图设置到一个 ImageView 中,然后再随便放到一个布局上,就可以实现通知栏消息的任意布局。理想是美好的,但现实是残酷的。使用这种方式自定义的布局,会存在与原生的通知栏消息样式不一致的可能,包括小图标 / 大图标的大小,字体的大小与颜色,时间的显示方式 (不同版本的时间显示位置和样式都不一样)。下面解决一个最关键,也最致命的问题——字体颜色。如果字体颜色和背景颜色一样,那这条通知栏消息就没法看了,如 RemoteViews 显示异常一节介绍的一样。
解决字体颜色和背景颜色一样的问题有三种解决方案,分别是:
背景色固定不透明,字体颜色与背景色形成反差。(360 和京东的做法) 背景色透明,字体颜色采用系统原生的 notification_style。 背景色透明,通过特殊方式拿到通知栏字体颜色和字体大小。
其中,第一种方案简单,能够兼容所有厂商机型。例如京东固定背景色为黑色,字体为红色。这种方式的唯一缺陷是样式上不能与普通通知栏消息重合,在白色背景的通知栏上极为显眼。第二种方式,通过阅读源码可知,系统的通知栏标题和内容采用的颜色分别是 @android:color/primary_text_dark 和 @android:color/secondary_text_dark,但踩过坑之后发现并非所有的机型默认都是这两个颜色,有可能获取不到值。因此这种方案只能作为参考,不能用于实际环境中。最后详细介绍一下第三种方式。
这种方案有一点投机取巧,是网上寻找代替方案时在简书上找到的,作者是 hackware。思路就是通过 Notification.Builder 生成一条空的 Notification,但不调用 notify() 方法,然后通过这条 Notification 想办法获取里面的布局元素,通过遍历,就能拿到对应的字体和颜色了。具体看代码:
- private static finalString NOTIFICATION_TITLE ="notification_title";public static final intINVALID_COLOR = -1;// 无效颜色
- private static intnotificationTitleColor = INVALID_COLOR;// 获取到的颜色缓存
- /**
- * 获取系统通知栏主标题颜色,根据Activity继承自AppCompatActivity或FragmentActivity采取不同策略。
- *
- * @paramcontext 上下文环境
- * @return系统主标题颜色
- */
- public static int getNotificationColor(Context context) {try{if(notificationTitleColor == INVALID_COLOR) {if(contextinstanceofAppCompatActivity) {
- notificationTitleColor = getNotificationColorCompat(context);
- }else{
- notificationTitleColor = getNotificationColorInternal(context);
- }
- }
- }catch(Exception ignored) {
- }returnnotificationTitleColor;
- }/**
- * 通过一个空的Notification拿到Notification.contentView,通过{@link RemoteViews#apply(Context, ViewGroup)}方法返回通知栏消息根布局实例。
- *
- * @paramcontext 上下文
- * @return系统主标题颜色
- */
- private static int getNotificationColorInternal(Context context) {
- Notification.Builder builder =newNotification.Builder(context);
- builder.setContentTitle(NOTIFICATION_TITLE);
- Notification notification = builder.build();try{
- ViewGroup root = (ViewGroup) notification.contentView.apply(context,newFrameLayout(context));
- TextView titleView = (TextView) root.findViewById(android.R.id.title);if(null== titleView) {
- iteratorView(root,newFilter() {@Override
- public void filter(View view) {if(viewinstanceofTextView) {
- TextView textView = (TextView) view;if(NOTIFICATION_TITLE.equals(textView.getText().toString())) {
- notificationTitleColor = textView.getCurrentTextColor();
- }
- }
- }
- });returnnotificationTitleColor;
- }else{returntitleView.getCurrentTextColor();
- }
- }catch(Exception e) {
- DebugLog.e(e.getMessage());returngetNotificationColorCompat(context);
- }
- }/**
- * 使用getNotificationColorInternal()方法,Activity不能继承自AppCompatActivity(实测5.0以下机型可以,5.0及以上机型不行),
- * 大致的原因是默认通知布局文件中的ImageView(largeIcon和smallIcon)被替换成了AppCompatImageView,
- * 而在5.0及以上系统中,AppCompatImageView的setBackgroundResource(int)未被标记为RemotableViewMethod,导致apply时抛异常。
- *
- * @paramcontext 上下文
- * @return系统主标题颜色
- */
- private static int getNotificationColorCompat(Context context) {try{
- Notification.Builder builder =newNotification.Builder(context);
- Notification notification = builder.build();intlayoutId = notification.contentView.getLayoutId();
- ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(layoutId,null);
- TextView titleView = (TextView) root.findViewById(android.R.id.title);if(null== titleView) {returngetTitleColorIteratorCompat(root);
- }else{returntitleView.getCurrentTextColor();
- }
- }catch(Exception e) {
- }returnINVALID_COLOR;
- }private static void iteratorView(View view, Filter filter) {if(view ==null|| filter ==null) {return;
- }
- filter.filter(view);if(viewinstanceofViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;for(inti =0; i < viewGroup.getChildCount(); i++) {
- View child = viewGroup.getChildAt(i);
- iteratorView(child, filter);
- }
- }
- }private static int getTitleColorIteratorCompat(View view) {if(view ==null) {returnINVALID_COLOR;
- }
- List textViews = getAllTextViews(view);
- intmaxTextSizeIndex = findMaxTextSizeIndex(textViews);if(maxTextSizeIndex != Integer.MIN_VALUE) {returntextViews.get(maxTextSizeIndex).getCurrentTextColor();
- }returnINVALID_COLOR;
- }private static int findMaxTextSizeIndex(List textViews) {
- floatmax = Integer.MIN_VALUE;intmaxIndex = Integer.MIN_VALUE;intindex =0;for(TextView textView : textViews) {if(max < textView.getTextSize()) {// 找到字号最大的字体,默认把它设置为主标题字号大小max = textView.getTextSize();
- maxIndex = index;
- }
- index++;
- }returnmaxIndex;
- }/**
- * 实现遍历View树中的TextView,返回包含TextView的集合。
- *
- * @paramroot 根节点
- * @return包含TextView的集合
- */
- private staticList getAllTextViews(View root) {finalList textViews = newArrayList<>();
- iteratorView(root,newFilter() {@Override
- public void filter(View view) {if(viewinstanceofTextView) {
- textViews.add((TextView) view);
- }
- }
- });returntextViews;
- }private interface Filter{
- voidfilter(View view);
- }
获取系统通知标题颜色,如果能够获取到,那么标题、内容和时间的颜色都设置为标题颜色。 获取不到的情况下,遍历系统通知里的所有文字,取字号最大的那条文字的颜色作为标题、内容和时间的颜色。 以上两个步骤的实现在 getNotificationColor() 方法里。如果还获取不到,那么标题和内容采用 Android 原生系统提供的,其中标题是 @android:color/primary_text_dark,内容是 @android:color/secondary_text_dark。 有一点需要说明的是,以上适配只适合在 Android 7.0 以下系统。Android 7.0 + 修改了 Notification,采用 @android:color/primary_text_dark 和 @android:color/secondary_text_dark 已经获取不到颜色值了,考虑到 7.0 所采用的通知栏主色调是白色,因此目前暂时的解决方案是遇到 7.0 的系统采用黑色字体。面对众多厂商的源码修改,目前测试有 ZUK 的 7.0 系统为暗色背景,暂时的解决方案是根据机型适配。
参考链接:http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/
来源: http://blog.csdn.net/mwq384807683/article/details/72594027