在需要隐藏虚拟键Navigation Bar的Activity的onCreate方法中添加如下代码:
第一种:始终隐藏navigation,即使点击屏幕也不会出现:
WindowManager.LayoutParams params = getWindow().getAttributes();
params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE;
getWindow().setAttributes(params);
第二种:初始化时隐藏,点击屏幕后Navigation重新出现:
WindowManager.LayoutParams params =getWindow().getAttributes();
params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
getWindow().setAttributes(params);

一、概述

在项目中,测试发现在一些华为手机的屏幕适配上出现了问题,主要是因为华为Mate等一些系列的手机有一个虚拟按键的设计.当这些虚拟按键由用户手势滑出,或默认显示的话,就会遮挡我们本身的应用布局.比如欢迎界面过后是四个Fragment,那么底部的四个tab就会被虚拟的导航栏遮住,非常难看.

img

当然,欢迎页的图片适配也同样会出现问题.

Google后得出第一个问题的解决方案.第二个图片的问题则用自己摸索的方式解决,当然也非常简单.

二、布局由于虚拟按键导致导航栏顶上去的解决方法

在我们的项目中加载Fragment的MainActivity,以及其他一般的Activity继承的BaseActivity中的onCreate方法中添加如下代码:

if (AndroidWorkaround.checkDeviceHasNavigationBar(this)) {

AndroidWorkaround.assistActivity(findViewById(android.R.id.content));

}

其中AndroidWorkaround使我们为了解决该问题而封装的类,也可以看作是一个特定的工具类:

/**

* 解决底部屏幕按键适配

* Created by Mercury on 2016/10/25.

*/

public class AndroidWorkaround {

public static void assistActivity(View content) {

​ new AndroidWorkaround(content);

}

private View mChildOfContent;

private int usableHeightPrevious;

private ViewGroup.LayoutParams frameLayoutParams;

private AndroidWorkaround(View content) {

​ mChildOfContent = content;

​ mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

​ public void onGlobalLayout() {

​ possiblyResizeChildOfContent();

​ }

​ });

​ frameLayoutParams = mChildOfContent.getLayoutParams();

}

private void possiblyResizeChildOfContent() {

​ int usableHeightNow = computeUsableHeight();

​ if (usableHeightNow != usableHeightPrevious) {

​ frameLayoutParams.height = usableHeightNow;

​ mChildOfContent.requestLayout();

​ usableHeightPrevious = usableHeightNow;

​ }

}

private int computeUsableHeight() {

​ Rect r = new Rect();

​ mChildOfContent.getWindowVisibleDisplayFrame(r);

​ return (r.bottom);

}

public static boolean checkDeviceHasNavigationBar(Context context) {

​ boolean hasNavigationBar = false;

​ Resources rs = context.getResources();

​ int id = rs.getIdentifier(“config_showNavigationBar”, “bool”, “android”);

​ if (id > 0) {

​ hasNavigationBar = rs.getBoolean(id);

​ }

​ try {

​ Class systemPropertiesClass = Class.forName(“android.os.SystemProperties”);

​ Method m = systemPropertiesClass.getMethod(“get”, String.class);

​ String navBarOverride = (String) m.invoke(systemPropertiesClass, “qemu.hw.mainkeys”);

​ if (“1”.equals(navBarOverride)) {

​ hasNavigationBar = false;

​ } else if (“0”.equals(navBarOverride)) {

​ hasNavigationBar = true;

​ }

​ } catch (Exception e) {

​ }

​ return hasNavigationBar;

}

}

重新测试,发现无论是否弹出虚拟按键,都不会再次遮挡tab按钮。

三、原理

上面的代码需要在setContentView后面执行。其最初的解决方案是stackoverflow上有人为了适配软键盘在全屏下的布局问题。

开始先判断该设备上是否存在导航栏。为什么用findViewById(android.R.id.content)呢?因为android.R.id.content这个id代表的就是所在页面的根布局,而并不需要特别指定一个id给该布局。可以通过调用系统API返回的结果,也可以通过判断该手机是否为华为手机,操作系统属于哪种类型来来判断。

一旦确定该设备存在导航栏,将对该布局进行重新测量。首先mChildOfContent得到其视图树,对全局高度实现监听。

OnGlobalLayoutListener 是ViewTreeObserver的内部类,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到,这是一个注册监听视图树的观察者(observer),在视图树的全局事件改变时得到通知。ViewTreeObserver不能直接实例化,而是通过getViewTreeObserver()获得。

接着得到视图目前可用的总高度,将其赋值给mChildOfContent的布局高度。调用requestLayout,让mChildOfContent要求自己的parent view对自己重新设置位置。

四、全屏图片的适配

解决了布局的问题,再来看欢迎页启动时候全屏图片的适配问题。发现该方法对于图片不适用。如下图,当虚拟按键弹出时,图片照样被遮挡了底部的一小部分。

img

如果隐藏虚拟按键,图片大小恢复正常

img

仔细想想,对于一个ImageView直接占据一个layout的情况,是没有必要再去写一些代码进行适配的。到布局里一看,发现ImageView的属性 android:scaleType=”centerCrop”

将其改为 android:scaleType=”fitXY”就可以解决了。这样图片可能高度会随着虚拟键的弹出而压缩,但是很好的适配了布局高度的变化而不会被遮挡。

转自:http://blog.csdn.net/wzhseu/article/details/58117424

1
2
3
4
5
6
<ImageView

android:src="@mipmap/icon_my_top_bg"
android:layout_width="match_parent"
android:scaleType="fitXY"
android:layout_height="match_parent"/>

如果是像上面那样设置时,记得加上android:scaleType=”fitXY”,不然高清手机或者宽屏手机上无法铺满

Google表示,为保证用户数据和设备的安全,针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。

因此在Android P 使用HttpUrlConnection进行http请求会出现以下异常:

1
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: Monaco, Menlo, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px !important; background: rgb(238, 238, 238); border: 0px; overflow: auto; border-radius: 2px;">W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted</pre>

使用OKHttp请求则出现:

1
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: Monaco, Menlo, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px !important; background: rgb(238, 238, 238); border: 0px; overflow: auto; border-radius: 2px;">java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy</pre>

在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。

有人认为 Android P 上所有的 App 都需要使用 TLS 加密会降低上网体验,事实上这是一种误解,至于 App 对于少数旧服务器的连接如果非要使用明码传输,开发者需要更改 App 的网络安全配置以允许此类连接。

有以下三种解决方案

  • APP改用https请求
  • targetSdkVersion 降到27以下
  • 在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求
    1
    2
    3
    4
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: Monaco, Menlo, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px !important; background: rgb(238, 238, 238); border: 0px; overflow: auto; border-radius: 2px;"><?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
    <base-config cleartextTrafficPermitted="true" />
    </network-security-config></pre>
    然后在APP的AndroidManifest.xml文件下的application标签增加以下属性
    1
    2
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: Monaco, Menlo, Consolas, &quot;Courier New&quot;, monospace; font-size: 12px !important; background: rgb(238, 238, 238); border: 0px; overflow: auto; border-radius: 2px;"><application ...
    android:networkSecurityConfig="@xml/network_security_config" ... /></pre>
    参考资料:
    https://android-developers.googleblog.com/2018/04/protecting-users-with-tls-by-default-in.html 
    https://android-developers.googleblog.com/2018/04/dns-over-tls-support-in-android-p.html

#####代码执行获取密钥散列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
int i = 0;
PackageInfo info = getPackageManager().getPackageInfo( getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : info.signatures) {
i++;
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
String KeyHash = Base64.encodeToString(md.digest(), Base64.DEFAULT);
//KeyHash 就是你要的,不用改任何代码 复制粘贴 ;
Log.e("tyl","KeyHash="+KeyHash);
}
}
catch (PackageManager.NameNotFoundException e) {

}
catch (NoSuchAlgorithmException e) {

}

####Android 设备在黑屏的分钟后可能会发生多种情况:
应用程序切后台后进程资源被系统回收,导致不能持续定位。
####解决办法:
####长连接定时唤醒cpu(解决黑屏定位、黑屏断网问题)

  1. 对于原生Android系统可采用google给出的提升后台应用进程优先级的解决方案来解决,可参考google Android 开发者官网。

  2. 对于国内厂商提供的Android系统需要联系到对应的厂商进行系统底层应用白名单授权,才可以保证App进程在后台处于活跃状态。

CPU会处于休眠状态(不同厂商生产的设备CPU休眠时间不尽相同)(包含AP[Application Processor,ARM架构的处理器,用于支撑Android系统运行]和BP[Baseband Processor,运行实时操作系统,通讯协议栈等])。一旦当CPU处于休眠状态,设备将无法正常链接网络,APP的定位请求也将无法正常发送。
解决办法:

  • 通过创建Timer来保持CPU唤醒状态:
    Android 的 Timer 类可以用来计划需要执行的任务。但 Timer 的问题是比较消耗手机电量(实现是用 WakeLock 让 CPU 保持唤醒状态);另外一点是:部分厂商将WakeLock也设置了休眠时间,就是说 Timer 很可能和CPU一起处于休眠状态。Timer 类只能解决一小部分问题。
  • 通过AlarmManager保持CPU处于唤醒状态:
    AlarmManager 是 Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。用 AlarmManager 来定时执行任务,CPU 可以正常的休眠,需要运行定位时醒来即可。但部分厂商为了使设备更加省电,将AlarmManager也做出了修改,例如5s一次的响应更改为50s或者是几分钟,有些干脆在CPU休眠后彻底停掉了。
  • 通过心跳长链接保持client端CPU处于唤醒状态:(推荐)
    最佳唤醒CPU的方法是通过server端与client端的长链接通信。例如每次长链接保持5分钟时间,每30s通信一次,这样可以有效确保CPU处于唤醒状态。

####开启前台server(进程保活)
思路:模仿一个音乐类软件,开启一个前台server并循环播放无声音乐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.guoshikeji.xiaoxiangDriver.services;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;

import com.guoshikeji.xiaoxiangDriver.MainActivity;
import com.guoshikeji.xiaoxiangDriver.R;

import static android.app.Notification.PRIORITY_MAX;
/**
* Created by tyl
* 2019/11/12/012
* Describe:
*/
public class BackGroundService extends Service {
Notification notification;
private Context mContext;
private static Thread uploadGpsThread;
private MediaPlayer bgmediaPlayer;
private boolean isrun = true;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mContext = this;
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//1.通知栏占用,不清楚的看官网或者音乐类APP的效果
notification = new Notification.Builder(mContext)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setTicker(getResources().getString(R.string.app_name))
.setContentTitle(getResources().getString(R.string.app_name))
.setContentText("正在后台运行")
.setOngoing(true)
.setPriority(PRIORITY_MAX)
.setContentIntent(pendingIntent)
.setAutoCancel(false)
.build();
/*使用startForeground,如果id为0,那么notification将不会显示*/
startForeground(2479, buildNotification());
////2.开启线程(或者需要定时操作的事情)
//if(uploadGpsThread == null){
// uploadGpsThread = new Thread(new Runnable() {
// @Override
// public void run() {
// //这里用死循环就是模拟一直执行的操作
// while (isrun){
//
// //你需要执行的任务
// //doSomething();
//
// try {
// Thread.sleep(10000L);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// });
//}
//3.最关键的神来之笔,也是最投机的动作,没办法要骗过CPU
//这就是播放音乐类APP不被杀的做法,自己找个无声MP3放进来循环播放
//slient自己百度找一个无声的mp3即可
if(bgmediaPlayer == null){
bgmediaPlayer = MediaPlayer.create(this,R.raw.slient);
bgmediaPlayer.setLooping(true);
bgmediaPlayer.start();
}
return START_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}

@Override
public void onDestroy() {
isrun = false;
stopForeground(true);
bgmediaPlayer.release();
stopSelf();
super.onDestroy();
}

private NotificationManager notificationManager;
private boolean isCreateChannel = false;
@SuppressLint("NewApi")
private Notification buildNotification() {
Notification.Builder builder = null;
Notification notification = null;

if (android.os.Build.VERSION.SDK_INT >= 26) {
if (null == notificationManager) {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
String channelId = getPackageName();
if (!isCreateChannel) {
NotificationChannel notificationChannel = new NotificationChannel(channelId,
"BackgroundLocation", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.enableLights(false);//是否在桌面icon右上角展示小圆点
notificationChannel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知
notificationManager.createNotificationChannel(notificationChannel);
isCreateChannel = true;
}
builder = new Notification.Builder(getApplicationContext(), channelId);
} else {
builder = new Notification.Builder(getApplicationContext());
}
builder.setSmallIcon(R.mipmap.icon_notifacation_log)
.setColor(getResources().getColor(R.color.main_color))
.setContentTitle(getResources().getString(R.string.app_name))
.setContentText("正在后台运行")
.setWhen(System.currentTimeMillis());
if (android.os.Build.VERSION.SDK_INT >= 16) {
notification = builder.build();
} else {
return builder.getNotification();
}
return notification;
}
}

清单文件注册:

1
2
3
4
<service
android:name=".services.BackGroundService"
android:enabled="true"
android:exported="true"/>

启动server:

1
2
Intent forgroundService = new Intent(this,BackGroundService.class);
startService(forgroundService);

####出现错误的效果图:
出现错误的效果图
####解决方法:

1
2
3
datePickerDialog.setTitle("");
datePickerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
datePickerDialog.show();

在项目中,想使RecyclerView慢慢的平缓滑动指定位置,于是使用:

1
RecyclerView.smoothScrollToPosition(int);

发现效果并不理想,滑动过程很突兀,很快就滑动到了指定位置,并没有像函数名那样smooth(流畅的,平滑的),也就是说smoothScrollToPosition没有滑动效果?
这里接不说底层原理了,网上一大把!

#####解决办法 重写LinearLayoutManager自定义滑动速度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;

/**
* Created by tyl
* 2018/12/19/019
* Describe:解决 RecyclerView调用smoothScrollToPosition时没有起到平滑的作用
* 设置LinerLayoutManager:recyclerview.setLayoutManager(new SmoothScrollLayoutManager(this));
*recyclerview.smoothScrollToPosition(i);
*/

public class SmoothScrollLayoutManager extends LinearLayoutManager {

public SmoothScrollLayoutManager(Context context) {
super(context);
}

@Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state, final int position) {

LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
// 返回:滑过1px时经历的时间(ms)。
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 150f / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}

AndroidManifest属性设置:

键盘直接覆盖,不让屏幕上移:
<activity android:name=”.activity.HomeActivity”

Android:windowSoftInputMode=”adjustPan|stateHidden”/>

这样会让屏幕整体上移:

Android:windowSoftInputMode=”stateVisible|adjustResize”

这样键盘就会覆盖屏幕:
Android:windowSoftInputMode=”adjustPan”

方法一:
在项目的AndroidManifest.xml文件中界面对应的里加入android:windowSoftInputMode=”adjustPan”这样键盘就会覆盖屏幕。
通过实践发现方法一并没有起作用,查询资料得知我的布局是RelativeLayout,底部菜单用了android:layout_alignParentBottom=”true”,因此方法一不起作用,故采用方法二。

方法二:
在代码setContentView()之前加入:
getWindow().setSoftInputMode
(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN|
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

亲测有效,另外还有一种通用的方法:

方法三:
把顶级的layout替换成ScrollView,或者说在顶级的Layout上面再加一层ScrollView的封装。这样就会把软键盘和输入框一起滚动了,软键盘会一直处于底部。