118、adb 连接设备出现unable to connect devices 问题
通过网络adb连接设备:
adb connect [设备的ip地址]
adb disconnect 或者adb disconnect [设备的ip地址]
未root的手机,不能连接,提示 unable to connect devices
解决方法:
先usb连接上,输入adb tcpip 5555
再输入 adb connect 192.168.1.7:5555就可以连接上
119、activity的生命周期回调函数:
onCreate()
onStart():用户可见
onResume():在前台,用户可见
onPause():用户可见
onStop()
-----onRestart()
onDestory()
打开应用时先后执行了onCreate()->onStart()->onResume三个方法.
按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法.
按HOME键,onPause()->onStop()
再次启动ActivityDemo应用程序时,则先后分别执行了onRestart()->onStart()->onResume()三个方法
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
120、一张图片在android中如果不压缩,实际占用内存计算
例如一张1440*2448(351K)大小的图片,加载到内存中,不压缩
1440*2448(个像素)*4(RGBA,每个像素有4个颜色值)*8(每个颜色值8bit) = 112803840 bit = 13.45MB(放大40倍左右)
121、今天分析了下activity的启动流程源码分析,大概总结下:
在我们学习AIDL的时候我们activity调用service里面的方法是通过Binder的(就是Service里面有个内部类实现了IBinder接口,在onBind方法中会把这个对象返回给activity里面
我们在activity里面就可以使用这个Binder类的应用来调用service里面的方法了)。
网上摘录:
在Actvity启动过程中,其实是应用进程与SystemServer进程相互配合启动Activity的过程,其中应用进程主要用于执行具体的Activity的启动过程,回调生命周期方法等操作,
而SystemServer进程则主要是调用其中的各种服务,将Activity保存在栈中,协调各种系统资源等操作。
注意细节:
a.
问题:
通过这里的代码我们可以发现,其实我们在Activity中调用startActivity的内部也是调用的startActivityForResult的。
那么为什么调用startActivityForResult可以在Activity中回调onActivityResult而调用startActivity则不可以呢
答案:
可以发现其主要的区别是调用startActivity内部调用startActivityForResult传递的传输requestCode值为-1,
也就是说我们在Activity调用startActivityForResult的时候传递的requestCode值为-1的话,那么onActivityResult是不起作用的。
实际上,经测试requestCode的值小于0的时候都是不起作用的,所以当我们调用startActivityForResult的时候需要注意这一点。
b.
在查看execStartActivity方法之前,我们需要对mInstrumentation对象有一个了解 什么是Instrumentation
Instrumentation是android系统中启动Activity的一个实际操作类,也就是说Activity在应用进程端的启动实际上就是Instrumentation执行的,那么为什么说是在应用进程端的启动呢
实际上acitivty的启动分为应用进程端的启动和SystemServer服务进程端的启动的,多个应用进程相互配合最终完成了Activity在系统中的启动的,
而在应用进程端的启动实际的操作类就是Intrumentation来执行的,可能还是有点绕口,没关系,随着我们慢慢的解析大家就会对Instrumentation的认识逐渐加深的。
可以发现execStartActivity方法传递的几个参数:
this,为启动Activity的对象;
contextThread,为Binder对象,是主进程的context对象;
token,也是一个Binder对象,指向了服务端一个ActivityRecord对象;
target,为启动的Activity;
intent,启动的Intent对象;
requestCode,请求码;
options,参数;
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
}
c.
Binder接口 –> ActivityManagerNative/ActivityManagerProxy –> ActivityManagerService;
这样,ActivityManagerNative与ActivityManagerProxy相当于一个Binder的客户端而ActivityManagerService相当于Binder的服务端,
这样当ActivityManagerNative调用接口方法的时候底层通过Binder driver就会将请求数据与请求传递给server端,并在server端执行具体的接口逻辑。
需要注意的是Binder机制是单向的,是异步的,也就是说只能通过client端向server端传递数据与请求而不同等待服务端的返回,也无法返回.
那如果SystemServer进程想向应用进程传递数据怎么办
这时候就需要重新定义一个Binder请求以SystemServer为client端,以应用进程为server端,这样就是实现了两个进程之间的双向通讯。
d.
这里通过我们刚刚的分析,ActivityManagerNative.getDefault()方法会返回一个ActivityManagerProxy对象,那么我们看一下ActivityManagerProxy对象的startActivity方法:
这里就涉及到了具体的Binder数据传输机制了,我们不做过多的分析,知道通过数据传输之后就会调用SystemServer进程的ActivityManagerService的startActivity就好了。
以上其实都是发生在应用进程中,下面开始调用的ActivityManagerService的执行时发生在SystemServer进程。
e.
Activity生命周期中的第一个生命周期方法终于被我们找到了。。。。也就是说我们在启动一个Activity的时候最先被执行的是栈顶的Activity的onPause方法。
(一般我们可能认为是新的activity的onCreate方法,通过源码分析可以知道不是的)
记住这点吧,面试的时候经常会问到类似的问题。
f.
Activity的启动流程一般是通过调用startActivity或者是startActivityForResult来开始的
startActivity内部也是通过调用startActivityForResult来启动Activity,只不过传递的requestCode小于0
Activity的启动流程涉及到多个进程之间的通讯这里主要是ActivityThread与ActivityManagerService之间的通讯
ActivityThread向ActivityManagerService传递进程间消息通过ActivityManagerNative,ActivityManagerService向ActivityThread进程间传递消息通过IApplicationThread。
ActivityManagerService接收到应用进程创建Activity的请求之后会执行初始化操作,解析启动模式,保存请求信息等一系列操作。
ActivityManagerService保存完请求信息之后会将当前系统栈顶的Activity执行onPause操作,并且IApplication进程间通讯告诉应用程序继承执行当前栈顶的Activity的onPause方法;
ActivityThread接收到SystemServer的消息之后会统一交个自身定义的Handler对象处理分发;
ActivityThread执行完栈顶的Activity的onPause方法之后会通过ActivityManagerNative执行进程间通讯告诉ActivityManagerService,栈顶Actiity已经执行完成onPause方法,继续执行后续操作;
ActivityManagerService会继续执行启动Activity的逻辑,这时候会判断需要启动的Activity所属的应用进程是否已经启动,若没有启动则首先会启动这个Activity的应用程序进程;
ActivityManagerService会通过socket与Zygote继承通讯,并告知Zygote进程fork出一个新的应用程序进程,然后执行ActivityThread的mani方法;
在ActivityThead.main方法中执行初始化操作,初始化主线程异步消息,然后通知ActivityManagerService执行进程初始化操作;
ActivityManagerService会在执行初始化操作的同时检测当前进程是否有需要创建的Activity对象,若有的话,则执行创建操作;
ActivityManagerService将执行创建Activity的通知告知ActivityThread,然后通过反射机制创建出Activity对象,并执行Activity的onCreate方法,onStart方法,onResume方法;
(源码看是通过类加载器来加载的,也是反射的一种)
ActivityThread执行完成onResume方法之后告知ActivityManagerService onResume执行完成,开始执行栈顶Activity的onStop方法;
ActivityManagerService开始执行栈顶的onStop方法并告知ActivityThread;
ActivityThread执行真正的onStop方法;
自己总结:
通过源码分析可以看到:
startActivity(客户端) --- ActivityManagerNative.getDefault() --- 进入到ActivityManagerService里面(服务端)。
【ActivityManagerNative是一个抽象类继承Binder,具体的实现类是ActivityManager】所以这两个进程是通过ActivityManagerNative这个Binder通信的。
服务端 --- app.thread.scheduleLauncherActivity(IApplicationThread) --- ApplicationThread(是ActivityThread的一个内部类)(客户端)
【app.thread是一个IApplicationThread,也是一个Binder接口,具体是实现类是ApplicationThread】所以最终调用到了ActivityThread里面的方法了,又回到客户端了
最终就是通过类加载器,加载Activity类,然后调用onCreate方法启动Activity
所以2个进程间通信是通过Binder实现的。具体Binder底层是怎么通信的,还需要继续研究。
122、iperf3.0编译
sudo -s
source /etc/profile
export PATH=/home/hw/CTS/cts/android-ndk-linux/AndroidToolChain/bin:$PATH
make distclean
./configure --host=arm-linux CC=arm-linux-androideabi-gcc CXX=arm-linux-androideabi-g++ CFLAGS=-static CXXFLAGS=-static
tempdir = "/data/data/com.example.test";
test->outfile = stdout;
make
123、android使用su命令
String cmd = "ls -l /data/log/LogService/0/";
process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
os.writeBytes("ls -l /data/log/LogService/0/\n");//一定要加\n分割
os.writeBytes("exit\n");
os.flush();
//process = Runtime.getRuntime().exec(cmd);
buf = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
int status = process.waitFor();//0正常,1无权限,2找不到文件
Log.d("gaoqiang", "status:" + status);
String str = "";
final StringBuffer sb = new StringBuffer();
while ((str = buf.readLine()) != null) {
Log.d("gaoqiang", str+"\n");
sb.append(str);
}
Toast.makeText(this, sb.toString(), 1).show();
124、非root的手机,将下载的命令push到手机上执行。
有时候我们需要在app中执行一个命令,例如iperf,我们不想重新写个demo,只是想验证下命令是不是能正常执行。
这个时候我们可以在 /data/local/tmp目录下执行
步骤:
1.push到/data/local/tmp/下(例如push iperf命令到这个目录下)
2.chmod 777 iperf
3./data/local/tmp/iperf -c 192.168.1.5
备注:其它的命令也一样,例如下载busybox也可以执行
125、android traceroute功能实现
在windows下面的命令【tracert】
C:\Users\CHINA\Desktop>tracert www.tencent.com
通过最多 30 个跃点跟踪
到 ssd.tcdn.qq.com [119.147.227.25] 的路由:
1 <1 毫秒 <1 毫秒 <1 毫秒 192.168.1.1
2 1 ms 1 ms <1 毫秒 192.168.0.200
3 3 ms 5 ms 4 ms 100.64.0.1
4 3 ms 3 ms 3 ms 253.185.37.59.broad.dg.gd.dynamic.163data.com.cn [59.37.185.2
5 * * * 请求超时。
6 * * * 请求超时。
7 * * * 请求超时。
8 * * * 请求超时。
9 13 ms 13 ms 13 ms 119.147.227.25
跟踪完成。
在linux下面的命令【traceroute】,但是android里面是没有这个命令的,使用busybox很多需要有root权限。
现在有2种实现方案:
1、使用交叉编译好的traceroute命令,这个暂时没有找到好的方法。
2、使用ping去实现。【github有很多这种案例,可以参考】
ping -c 1 -W 10 -t 1 www.baidu.com (获取每个节点的ip)
解析出ip地址,查询ip的位置
ping -c 1 - W 10 192.168.1.1 (解析出ping的时延,可以ping多次,window上的tracert命令是ping了3次)
MTU的探测方法
windows系统:
查看网卡的mtu的值
netsh interface ipv4 show interfaces
修改本地的mtu值
netsh interface ipv4 set subinterface "本地连接 2" mtu=1500 store=persistent
测试最大MTU值方法
ping -n 1 -f -l 1472 www.baidu.com
-f不允许私自拆分数据包
-l发送指定大小的数据包
android 系统 :
查看手机mtu值设置
cat /sys/class/net/wlan0/mtu
ifconfig命令也可以查看
ip ad |grep wlan0 也可以查看
【ping -c 1 -M do -s 1373 www.baidu.com】nova plus手机验证结果
1|shell@HWMLA:/sys/class/net/wlan0 $ ping -c 1 -M do -s 1373 www.baidu.com
ping -c 1 -M do -s 1373 www.baidu.com
PING www.a.shifen.com (163.177.151.109) 1373(1401) bytes of data.
ping: local error: Message too long, mtu=1400
--- www.a.shifen.com ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms
【ping -c 1 -M do -s 1373 www.baidu.com】p8max手机验证结果
shell@HWP8max:/ $ ping -c 1 -M do -s 1373 www.baidu.com
ping -c 1 -M do -s 1373 www.baidu.com
PING www.a.shifen.com (163.177.151.109) 1373(1401) bytes of data.
From 192.168.3.3: icmp_seq=1 Frag needed and DF set (mtu = 1400)
--- www.a.shifen.com ping statistics ---
0 packets transmitted, 0 received, +1 errors
只有设置1372才能通过,加上28的包头大小刚好1400。所以测试不了网关的mtu最大值
设置手机的mtu值(需要root手机)
ip link set dev wlan0 mtu 1500
126、在调试已安装好的应用方法(手机需要root)
有时候安装了app,在启动的时候想打断点调试,如果等点击app启动再在as中启动断点会很快就消失了,断点位置已经运行过了。
这个时候我们可以使用命令来启动指定的activity【am start -D -n com.hw.smarthome/com.hw.smarthome.about.AgreementActivity】
输入命令的时候页面会弹框提示连接debugger,这个时候在as中把调试器关联上就可以运行断点了
127、对文本指定的字符修改颜色和大小,加粗等的方法
SpannableString SpannableStringBody = new SpannableString(this.getString(R.string.app_policy_msg_new));
SpannableStringBody.setSpan(new StyleSpan(Typeface.BOLD), 29, 84, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
cView.setText(SpannableStringBody);
可以使用SpannableString,可以设置样式。
128、退出app方法:
App.systemExit();
自杀进程
Process.killProcess(Process.myPid());
129、调试本地mysql数据库
启动服务:【net start mysql57】启动这个服务名
130、将一键提单的代码放到linux上使用gradlew编译出现的问题:
a、访问不到gradle里面配置的仓库地址
解决方法:配置代理服务器
b、提示没有licence的错误 “Warning: License for package Android Support Repository not accepted.”
原因:这个是由于在uploadLog模块中使用了和linux电脑中sdk不一样的support-v4包,导致需要license。
解决方法:将版本修改为25.1.1和智能家居app一致,再次编译OK
131、使用codedex扫描操作说明:
a、编译服务器的地址
100.100.151.218
jenkins
123456
直接samba访问\\100.100.151.218
工程地址:/cloud/wifiapp/OneClickSubmission
一些工具地址:/cloud/tools(jdk 、 sdk)
jenkins地址:http://10.22.142.35:8080/jenkins/job/Codedex_wifiapp/5/tqereports/
b、linux一些基本的vi命令
vi
操作命令
定位到某一行
vi build.gradle +100 【直接打开光标停留在100行的位置】
使用【i】进入编辑模式,使用【esc】退出编辑模式,使用【:wq】保存退出
c、遇到的一些问题:
1、开始的时候遇到不能get那个mcloud插件的uri地址
解决方法:配置了代理就OK了
2、遇到一个下面的报错
> You have not accepted the license agreements of the following SDK components:
[Android Support Repository].
原因:这个问题由于使用的support-v4的版本在那台linux服务器没有,需要license。
解决方法:将uploadLog这个模块中的v4版本配置为和智能家居保持一致
compile 'com.android.support:support-v4:25.1.1'
再次编译没有报这个错误。
3、又遇到一个不能下载到mcloud的一个lark-runtime的配置文件
Can not resolve com.hw.hae.mcloud.bundle....
一直提示:Recieved status code 502 from server:badgateway
手动去服务器访问是能访问到这个文件的。
解决的方法:
直接配置固定的版本号,不去加载服务器的配置文件。
compile 'com.hw.hae.mcloud.bundle.android:lark-runtime:3.2.7'
再次编译OK
4、后面又遇到一个ExecutionException:java.lang.OutOfMemoryError:GC overhead limit exceeded的错误
这个大概的意思就是编译内存不够。
解决方法:
修改了下工程目录下的 gradle.properties这个文件的配置
org.gradle.jvmargs=-Xmx2048m
注意:编译工程的时候注意把模块中的一些不需要的本地配置文件删除掉,不然编译也会出问题。
132、使用jarsigner工具对已有的apk签名
jarsigner -verbose -keystore keystore文件路径 -signedjar 签名后生成的apk路径 待签名的apk路径 别名
上面只支持v1的签名,遇到一个 [INSTALL_PARSE_FAILED_NO_CERTIFICATES]问题。
使用apksigner.bat签名
cd 到目录 C:\Users\gq.CHINA\AppData\Local\Android\sdk\build-tools\25.0.1
apksigner sign --ks 密钥库名 --ks-key-alias 密钥别名 xxx.apk 【直接覆盖了原来的apk,直接安装就OK】
查看签名信息:
【apksigner verify -v --print-certs xxx.apk】
查看CERT.RSA文件的内容:
可以使用jre里面的keytool工具查看【keytool -printcert -file CERT.RSA】
133、X工程现网替换成sdk 27 遇到的一些问题:
a.将工程选择为sdk 27后,出现 Unsupported class version number [52.0] (maximum 51.0, Java 1.7)
网上提示proguard版本较低,需要更新proguard版本(当前的版本为4.7)
解决方法:从网上下载一个新版的proguard版本(版本号6.0.3)
下载完成替换【D:\android-sdk-windows-4.3\tools\proguard】这个目录,重新编译导出,没有报上面的那个错误
b.遇到的一个新问题
替换新的proguard后,没有遇到之前那个问题。但是遇到一个新的问题
eclipse弹框提示:proguard-android.txt does not exist or is not a regular file
可能是proguard配置不对
解决方法:
将工程下的project.properties配置修改下
之前的:proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
修改后的:proguard.config=proguard-project.txt
重新编译导出发现是OK的,使用反编译工具查看,混淆是有效的。但是后面clean后试了又不行
最后将从老版本的proguard目录下面将proguard-android.txt 复制到新版本目录下,编译混淆OK
c.使用sdk 27 有时候clean工程的时候,再导出apk,eclipse会弹框提示【Failed to export application】
界面上没有任何的提示,这个时候我们去看下log
log目录在【D:\Workspace\.metadata\.log】中
可以看到log打印的错误信息
Output:
D:\Workspace\SVN\xmos_33234\bin\AndroidManifest.xml: error: Unable to open file for read: No such file or directory
解决方法:
需要先用android6.0等待bin目录先自动生成AndroidManifest.xml,再切换到android8.0编译
备注:由于官方不推荐使用eclipse,所以使用新的sdk会出现一些兼容性问题,暂时只能先用android6.0自动生成后在切到8.0编译。
但是有个问题就是直接run是不行的,所以如果调试代码还需要使用android6.0。
134、后台接口打包
cd到工程的根目录【D:\0_xmos_server\xmos_Server\project\common-api】
运行命令【mvn clean package】
成功后会在target里面生成jar包
135、android源码查看方法
使用Source Insight 工具查看源码,下载3.5的版本,网上有比较多的破解码。
下载安装完成后 Project --- New Project 创建一个源码工程,将你的源码地址链接过来
设置代码窗口的文字大小方法:Options --- File Type Options --- Screen Font 既可以修改字体的大小了
使用的快捷技巧:
a.在Project Files里面可以搜到你需要查看的java类。快捷键(ctrl+o)
b.在Symbol Name里面可以搜索到这个类里面所有的方法和属性,快捷键(alt+l)
c.跳转到上一个搜索的位置,点击界面上向左的箭头,快捷键(alt+,)
d.跳转到下一个搜索的位置,点击界面上向右的箭头,快捷键(alt+.)
136、保存mpchart图片到本地sdcard
看了api里面有个saveToPath的方法,一直使用返回false,后面查阅源码需要这样使用
boolean res = chart_send_package.saveToPath("mpchart", "/test/"); //ture 成功 , false 失败
注意:生成文件的路径【/sdcard/test/mpchart.png】,test目录需要存在,第二个参数貌似前面必须加 /
137、android studio调试服务,自动断开
需要在手机开发者选项中把 “显示所有应用无响应ANR” 勾选上
138、部分android9.0的手机点击home退到后台,再次点击图标重新走welcome的问题
在MainActivity的onCreate方法中加入
if (!isTaskRoot()) {
finish();
return;
}
139、在android9.0上不显示通知的问题
设置的target sdk=27
String title = "一键提单工具测评通知";
String content = "日志上传成功";
String id = "channel_id_01";
String name="channel_id_01_name";
Context context = AppContext.getInstance().getApplication();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
Toast.makeText(context, mChannel.toString(), Toast.LENGTH_SHORT).show();
notificationManager.createNotificationChannel(mChannel);
notification = new Notification.Builder(context)
.setChannelId(id)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher).build();
} else {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher)
.setOngoing(true);
notification = notificationBuilder.build();
}
notificationManager.notify(1008, notification);
140、调试服务端的接口注意事项:
a.启动服务配置好端口号
【application-prod.properties】文件中配置
server.port=6931
b.配置数据库地址,用户名和密码
【application-prod.properties】
【mybatis-generator.xml】
2个文件都需要配置
c.配置访问的协议类型是http还是https
【application-prod.properties】
注释最后面https的就变成了http了,默认发布的时候使用https
d.本机调试的地址
https://localhost:6931/api/swagger-ui.html
141、android8.0以上,应用内升级遇到的坑
a.找不到对应apk下载路径,这个是在android7.0以上就会出现的问题
使用FileProvider就可以解决
b.下载完成后,不会进入app安装页面
这个是由于android8.0上修改导致的,需要用户手动授予安装的权限
完整的适配代码如下:
protected void installAPK(File file) {
if (!file.exists())
return;
Intent intent = new Intent(Intent.ACTION_VIEW);
//start gaoqiang 2018-12-17 modify,match for api 27
Uri uri = null;
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(this, CONTANT.FILE_PROVIDER, file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//如果是android8.0判断是否有安装权限 gaoqiang 2019-01-21
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
LogUtils.d(TAG,"no install permission,go setting ,please grant permission");
//Toast.makeText(mContext,"请授予安装权限",Toast.LENGTH_SHORT).show();
startInstallPermissionSettingActivity();
return;
}
}
} else {
uri = Uri.fromFile(file);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
// 在服务中开启activity必须设置flag,后面解释
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
LogUtils.d(TAG,"startActivity");
mContext.startActivity(intent);
}
//使用onActivityResult不行,因为跳转的页面是一个singleTask所以这个回调会立即执行,我们只有在onResume周期函数中做业务处理
@Override
protected void onResume() {
super.onResume();
if(!hasInstallPermission){
//检查安装权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls();
if(hasInstallPermission){
//如果授予权限,则启动安装
installApp(downloadPath);
}
}
}
}
特别注意:
a.intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 我们一定要用addFlags不要用setFlags。不然覆盖掉flag可能会导致出现“解析包时错误”
b.一定要在xml配置权限
142、android悬浮框权限判断
public static boolean checkAlertWindowPermission(Context context){
LogUtils.d("gaoqiang", "Build.VERSION.SDK_INT=" +Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
LogUtils.d("gaoqiang", "<19 默认有悬浮框权限");
return true;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {//<23
try {
Class cls = Class.forName("android.content.Context");
Field declaredField = cls.getDeclaredField("APP_OPS_SERVICE");
declaredField.setAccessible(true);
Object obj = declaredField.get(cls);
if (!(obj instanceof String)) {
return false;
}
String str2 = (String) obj;
obj = cls.getMethod("getSystemService", String.class).invoke(context, str2);
cls = Class.forName("android.app.AppOpsManager");
Field declaredField2 = cls.getDeclaredField("MODE_ALLOWED");
declaredField2.setAccessible(true);
Method checkOp = cls.getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class);
int result = (Integer) checkOp.invoke(obj, 24, Binder.getCallingUid(), context.getPackageName());
return result == declaredField2.getInt(cls);
} catch (Exception e) {
return false;
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//>26 android8.0
AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
if (appOpsMgr == null)
return false;
int mode = appOpsMgr.checkOpNoThrow("android:system_alert_window", android.os.Process.myUid(), context
.getPackageName());
LogUtils.d("gaoqiang", "mode = " + mode);
// return mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_IGNORED;
return mode == AppOpsManager.MODE_ALLOWED;
} else {
boolean p = Settings.canDrawOverlays(context);
LogUtils.d(“gaoqiang”, "canDrawOverlays = " + p);
return p;
}
}
}
使用
private DisplayMetrics dm ;
private WindowManager wmFloatWindow;
private WindowManager.LayoutParams paramsFloatWindow;
private static boolean isShowFloatWindow = true;
private void createFloatWindow(){
LogUtils.d(TAG, "createFloatWindow");
if(!PermissionUtils.checkAlertWindowPermission(this)){
LogUtils.d(TAG, "没有悬浮框权限,弹框授权");
Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
showErrorDialogPosition(this,"需要悬浮权限,快去设置再来吧!",Settings.ACTION_MANAGE_OVERLAY_PERMISSION,packageUri);
}else{
dm = getResources().getDisplayMetrics();//2017-11-13 gaoqiang
if(null == tv_float_window){
wmFloatWindow = (WindowManager) getApplicationContext().getSystemService(WINDOW_SERVICE);
paramsFloatWindow = new WindowManager.LayoutParams();
//paramsFloatWindow.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
paramsFloatWindow.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else{
paramsFloatWindow.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// 不设置这个弹出框的透明遮罩显示为黑色
paramsFloatWindow.format = PixelFormat.TRANSLUCENT;
paramsFloatWindow.gravity= Gravity.LEFT|Gravity.TOP;
// paramsFloatWindow.x = dm.widthPixels/3;
paramsFloatWindow.x = 0;
paramsFloatWindow.y = dm.heightPixels/3;
// paramsFloatWindow.width = WindowManager.LayoutParams.WRAP_CONTENT;
paramsFloatWindow.width = (dm.widthPixels/3)*2;
paramsFloatWindow.height = WindowManager.LayoutParams.WRAP_CONTENT;
paramsFloatWindow.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
paramsFloatWindow.alpha=(float) 1.0;
tv_float_window = new TextView(this);
String str = "Wi-Fi名称:NA\n";
str += "路由器MAC:NA\n";
str += "信道:NA\n";
str += "信号强度:NA\n";
str += "连接速率:NA\n";
str += "路由器IP:NA";
tv_float_window.setText(str);
tv_float_window.setTextSize(13);
tv_float_window.setBackgroundResource(R.drawable.background_float_window_style);
tv_float_window.setTextColor(0xffffffff);
setTouchListener();
wmFloatWindow.addView(tv_float_window, paramsFloatWindow);
LogUtils.d(TAG, "有悬浮框权限,显示弹框");
}else{
tv_float_window.setVisibility(View.VISIBLE);
}
}
}
143、通过包名打开一个应用
例如今日头条
String appName = "com.ss.android.article.lite";
PackageManager manager = getPackageManager();
Intent launchIntentForPackage = manager.getLaunchIntentForPackage(appName);
startActivity(launchIntentForPackage);
144、定义一个圆角矩形的弹框
a.定义一个dialog继承AlertDialog
public class PixelDialog extends AlertDialog{
protected PixelDialog(Context context) {
super(context, R.style.PixelDialogStyle);
}
}
b.PixelDialogStyle这个属性中
<style name="PixelDialogStyle" parent="@android:style/Theme.Dialog">
<item name="android:background">#00000000</item>//这个属性一定要设置成透明的,不然会出现直角
<item name="android:windowBackground">@drawable/background_pixel_dialog</item>//这个设置貌似作用不大
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
</style>
c.在dialog的xml文件中定义圆角和背景
android:background="@drawable/background_pixel_dialog"
background_pixel_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="8dip" />//这个就是显示在弹框的圆角,很重要
<solid android:color="#60000000" />//这个就是弹框的背景颜色,上面的那个已经设置成透明的了
</shape>
145、在蓝区遇到请求welink的mcloud地址出现502错误的问题:
a.不要接路由器组小网。
b.使用openproxy.hw.com的代理。
会出现jcenter中的jar不能下载问题,切换成proxy.hw.com代理即可
记住需要配置C:\Users\gq\.gradle\gradle.properties里面的密码
c.一些gradle配置
as版本:3.3.2
项目build.gradle:
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.hw.hae.mcloud.tools.build:hae-tools-public:1.1.6'
gradle-wrapper.properties:
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
app的gradle:
将compile修改为implementation
uploadLog的gradle:
//implementation fileTree(include: ['*.jar'], dir: 'libs')必须要注释,
compileOnly files('libs/fastjson-1.2.37.jar')只编译不打包,因为主app里面有这个jar
146、后台工程移植需要注意的问题
a.eclipse直接拷贝过来,里面eclipse.ini
-vm
C:/Program Files (x86)/Java/jdk1.7.0_51/bin(需要配置当前的java环境变量的路径,注意不能是1.8的,因为这个eclipse是32位版本,1.8的不兼容)
可以直接安装工具包中的jdk-7u51-windows-i586.exe 这个是1.7版本的
b.配置maven的环境变量
D:\xmos_Server\apache-maven-3.2.1-bin\bin(这个需要看你拷贝的路径)
c.cd到你的工程目录
执行【mvn clean package】注意需要断开iaccess
d.打开eclipse,import工程
import -- Maven -- Existing Maven Projects
特别注意:需要配置Maven的配置
在eclipse的设置中找到Maven 的User Settings :D:\xmos_Server\apache-maven-3.2.1-bin\conf\settings.xml
这个settings.xml文件配置
<localRepository>D:/xmos_Server/apache-maven-3.2.1-bin/repo</localRepository>配置的是本地的仓库
再次右击工程,运行mvn命令【mvn clean】 【mvn package】,将GBK编码改为UTF-8,工程OK,2未报错。
将工程导入idea遇到的问题:
open... --- pom.xml
1、idea配置maven:
Maven home derectory:D:/xmos_Server/apache-maven-3.2.1-bin
User settings file:D:\xmos_Server\apache-maven-3.2.1-bin\conf\settings.xml (overwrite勾选上)里面配置同上
Local responsitory:D:\xmos_Server\apache-maven-3.2.1-bin\repo(overwrite勾选上)
2、遇到很多的依赖下载不下来,“Cannot resolve plugin org.apache.maven”
在设置中搜索maven ,选择Remote Jar Responsitories,在Remote Jar Responsitories增加2个网址:(参考网上解决方法https://www.cnblogs.com/phpdragon/p/7216626.html)
http://maven.aliyun.com/nexus/content/repositories/central/
http://maven.aliyun.com/nexus/content/groups/public/
3、发现还是不行,使用everything搜索不能下载的jar(可能是之前下载没有成功)
把搜索出来的.lastUpdated文件删除,例如:xml-maven-plugin-1.0.pom.lastUpdated
再次Reimport all Maven Projects
147、android反编译AndroidManifest.xml文件
a、先下载【AXMLPrinter2.jar】文件,下载路径:https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/android4me/AXMLPrinter2.jar
b、将jar放到 C:\Users\gq\AppData\Local\Android\Sdk\tools 中
c、解压apk中的AndroidManifest.xml文件,放到tools目录中
d、输入命令【java -jar AXMLPrinter2.jar AndroidManifest.xml>AndroidManifest.txt】就可以了
实测OK
148、上传文件到服务器中
参考网址:https://www.cnblogs.com/woolhc/p/6123975.html
方式1:
客户端编写:
/**
* server param is string , file name
* @author gaoqiang
* @param urlStr em:https://192.168.3.12:6931/api/xmos/uploadDetailFile
* @param folderPath em:/sdcard/xmos/xmos_game/
* @param fileName em:test.txt
* @return status
*/
public static String uploadFile(String urlStr, String folderPath, String fileName){
urlStr = urlStr + "?fileName=" + fileName;
LogUtils.d(TAG, "start upload file to server , urlStr = " + urlStr);
HttpsUtil.initHttpsUrlConnection();
DataOutputStream ds = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
try {
URL url = new URL(urlStr);
URLConnection urlConnection = url.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
httpURLConnection.setDoInput(true);
httpURLConnection.setDoOutput(true);
httpURLConnection.setUseCaches(false);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
httpURLConnection.setRequestProperty("Charset", "GBK");
// 设置请求内容类型
httpURLConnection.setRequestProperty("Content-Type", "text/xml");
String filePath = folderPath + fileName;
// 设置DataOutputStream
ds = new DataOutputStream(httpURLConnection.getOutputStream());
FileInputStream fStream = new FileInputStream(filePath);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = fStream.read(buffer)) != -1) {
ds.write(buffer, 0, length);
}
ds.flush();
LogUtils.d(TAG,"upload file response code = " + httpURLConnection.getResponseCode());
if (httpURLConnection.getResponseCode() == 200) {
fStream.close();
LogUtils.d(TAG, "detail record file upload success ");
return "success";
}else{
return "fail";
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.e(TAG, "error=" + e.getMessage());
} finally {
if (ds != null) {
try {
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
服务端编写:
@RequestMapping(value = "/uploadDetailFile")
@ApiOperation(notes="上传详细测试记录文件",value="上传详细测试记录文件",httpMethod="POST")
@ApiResponses({@ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对") })
public Object uploadDetailFile(HttpServletRequest request) {
String msg = "ok";
List<Map<String, Object>> list =new ArrayList<>();
FileOutputStream fos = null;
BufferedOutputStream bos = null;
InputStream is = null;
HashMap<String, Object> map = new HashMap<>();
String dirName = "";
String filePath = gameLogSavePath;
List<String> accTypes = Arrays.asList(".txt",".zip");
try {
// SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
// String dateFolder = df.format(new Date());
// String folderPath = "E:\\xmos\\game\\" + dateFolder + "\\";
String fileName = request.getParameter("fileName") + ".txt";
//获取文件后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
if(accTypes.contains(suffixName)){
System.out.println(suffixName+"后缀合法");
}else{
System.out.println(suffixName+"后缀不合法");
map.put("success", false);
map.put("message", "上传文件类型有误");
return map;
}
//提取YYYY-MM-DD
String dirName_pattern = "\\d{4}_\\d{2}_\\d{2}";
Pattern r1 = Pattern.compile(dirName_pattern);
Matcher m1 = r1.matcher(fileName);
if (m1.find()){
dirName = m1.group(0);
}
//获取文件后缀名
//String suffixName = fileName.substring(fileName.lastIndexOf("."));
//重新生成文件名
//fileName = UUID.randomUUID()+suffixName;
//保存到static文件夹里
String folderPath = filePath + dirName + "/";
File f = new File(folderPath);
if(!f.exists()){
f.mkdirs();
}
File file = new File(folderPath+fileName);
if(!file.exists()){
file.createNewFile();
}
fos = new FileOutputStream(folderPath+fileName);
bos = new BufferedOutputStream(fos);
is = request.getInputStream();
int len = -1;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
bos.write(buf, 0, len);
}
bos.flush();
System.out.println("detail file upload ok");
map.put("success", true);
map.put("message", "success to upload");
} catch (Exception e) {
msg = e.getCause().toString();
System.err.println(msg);
map.put("success", false);
map.put("message", msg);
}finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// return binder.toJson(Common.generateResJson(list, msg));
return map;
}
方式2:
客户端编写:
/**
* server param is file object
* @author gaoqiang
* @param urlStr em:https://192.168.3.12:6931/api/xmos/uploadDetailFile
* @param folderPath em:/sdcard/xmos/xmos_game/
* @param fileName em:sheet_game_025GSJ188Y002465_2019_09_23_10_37_45.txt
* @return status
*/
public static String uploadMultipartFile(String urlStr, String folderPath, String fileName){
LogUtils.d(TAG, "start upload file to server , urlStr = " + urlStr);
HttpsUtil.initHttpsUrlConnection();
DataOutputStream ds = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
try {
URL url = new URL(urlStr);
URLConnection urlConnection = url.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
httpURLConnection.setDoInput(true);
httpURLConnection.setDoOutput(true);
httpURLConnection.setUseCaches(false);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Connection", "Keep-Alive");
httpURLConnection.setRequestProperty("Charset", "GBK");
// 设置请求内容类型
httpURLConnection.setRequestProperty("Content-Type", "text/xml");
String filePath = folderPath + fileName;
LogUtils.d(TAG,"filePath = " + filePath);
// add file special head
String end = "\r\n";
String twoHyphens = "--";
String boundary = "---------------------------823928434";
httpURLConnection.setRequestProperty("Content-Type", "multipart/form-data;file="+ fileName+";boundary=" + boundary);
httpURLConnection.setRequestProperty("fileName",fileName);
// 设置DataOutputStream
ds = new DataOutputStream(httpURLConnection.getOutputStream());
FileInputStream fStream = new FileInputStream(filePath);
//write file special head
ds.writeBytes(twoHyphens + boundary + end);
ds.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"" + end);
ds.writeBytes(end);
//write file content
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = fStream.read(buffer)) != -1) {
ds.write(buffer, 0, length);
}
//write file special head
ds.writeBytes(end);
ds.writeBytes(twoHyphens + boundary + twoHyphens + end);
ds.flush();
LogUtils.d(TAG,"upload file response code = " + httpURLConnection.getResponseCode());
if (httpURLConnection.getResponseCode() == 200) {
fStream.close();
LogUtils.d(TAG, "file upload success ");
return "success";
}else{
return "fail";
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.e(TAG, "error=" + e.getMessage());
} finally {
if (ds != null) {
try {
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
服务端编写:
/*
APK端上传接口-上传游戏测试日志文件
*/
@RequestMapping(value = "/uploadDetailFile", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> uploadDetailFile(MultipartFile file) {
HashMap<String, Object> map = new HashMap<>();
String dirName = "";
String filePath = gameLogSavePath;
List<String> accTypes = Arrays.asList(".txt",".zip");
try {
//获取文件名
String fileName = file.getOriginalFilename();
//获取文件后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
if(accTypes.contains(suffixName)){
System.out.println(suffixName+"后缀合法");
}else{
System.out.println(suffixName+"后缀不合法");
map.put("success", false);
map.put("message", "上传文件类型有误");
return map;
}
//提取YYYY-MM-DD
String dirName_pattern = "\\d{4}_\\d{2}_\\d{2}";
Pattern r1 = Pattern.compile(dirName_pattern);
Matcher m1 = r1.matcher(fileName);
if (m1.find()){
dirName = m1.group(0);
}
//获取文件后缀名
//String suffixName = fileName.substring(fileName.lastIndexOf("."));
//重新生成文件名
//fileName = UUID.randomUUID()+suffixName;
//保存到static文件夹里
filePath = filePath + dirName + "/";
System.out.println(">>>>>>>>>>>>>>>>>>>>" + filePath);
File f = new File(filePath+fileName);
File fileParent = f.getParentFile();
if(!fileParent.exists()){
fileParent.mkdirs();
}
file.transferTo(f);
map.put("success", true);
map.put("message", "success to upload");
} catch (Exception e) {
e.printStackTrace();
String msg = e.getCause().toString();
map.put("success", false);
map.put("message", msg);
}
return map;
}
149、遇到一个Runtime 出现ANR的问题
部分手机会出现这种问题
有问题的写法:
...
String cmd = "getprop";
process = Runtime.getRuntime().exec(cmd);
LogUtils.d(TAG, "start run command");
int status = process.waitFor();//这里可能会阻塞,需要将这个代码放到读取流完成(网上查询的资料说runtime流中的数据没有读取走的话,buffer满了会一直阻塞在这里)
LogUtils.d(TAG, "status=" + status);
...
可能由于getprop的内容太多,导致buffer满了,
解决方法:
new Thread(){
@Override
public void run() {
LogUtils.d(TAG,"Thread 2");
Process process = null;
BufferedReader buf = null;
try {
String cmd = "getprop";
process = Runtime.getRuntime().exec(cmd);
LogUtils.d(TAG, "start run command2" );
buf = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
String str = "";
while ((str = buf.readLine()) != null) {
if (str.contains("ro.boot.odm.conn.schiptype") || str.contains("ro.connectivity.sub_chiptype")) {
LogUtils.d(TAG, "str2=" + str);
}
}
int status = process.waitFor();
LogUtils.d(TAG, "status2=" + status);
} catch (RuntimeException e) {
LogUtils.d(TAG, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
process.destroy();
buf.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
150、采集不到游戏时延数据日志导出方法:
adb shell "touch /sdcard/Android/data/com.tencent.tmgp.sgame/files/vmpdebug.log"
adb shell "touch /sdcard/Android/data/com.tencent.tmgp.sgame/files/vmpdebug"
pause
然后重启游戏,把/sdcard/Android/data/com.tencent.tmgp.sgame/files/vmpdebug.log这个导出来发下
151、更新悬浮框位置方法(兼容平板):
ll_float_window.setOnTouchListener(new View.OnTouchListener() {
int startX, startY;
int currentX, currentY;
private boolean mScrolling = true;
private boolean touchEvent;
int gap = ViewConfiguration.get(MainActivity.this).getScaledTouchSlop();
private int x,y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
x = (int) event.getRawX();
y = (int) event.getRawY();
LogUtils.d(TAG,"updateWindowPosition , phoneOrientation = " + phoneOrientation + " || systemTop = " +systemTop + " || systemBottom = " + systemBottom
+ " || systemLeft = " + systemLeft + " || systemRight = " + systemRight + " || startX = " + startX + " || startY =" + startY
+ " || x = " + x + " || y =" + y);
mScrolling = false;//记录是否滚动了
touchEvent = false;
break;
case MotionEvent.ACTION_MOVE:
LogUtils.d(TAG,"MotionEvent.ACTION_MOVE");
int currentX = (int) event.getRawX();
int currentY = (int) event.getRawY();
int movedX = currentX - x;
int movedY = currentY - y;
x = currentX;
y = currentY;
paramsFloatWindow.x = paramsFloatWindow.x + movedX;
paramsFloatWindow.y = paramsFloatWindow.y + movedY;
if (!mScrolling) {//如果正在滑动就不进行判断
if (Math.abs(startX - event.getX()) >= gap || Math.abs(startY - event.getY()) >= gap) {
mScrolling = true;
} else {
mScrolling = false;
}
}
// TODO: 2019/10/17 需要打开
if (mScrolling) {
// wmFloatWindow.updateViewLayout(view, paramsFloatWindow);
}
wmFloatWindow.updateViewLayout(view, paramsFloatWindow);
break;
case MotionEvent.ACTION_UP:
if (mScrolling) {
touchEvent = true;//滑动事件拦截
}
mScrolling = false;
break;
}
return touchEvent;
}
});
152、动态广播注册,双向权限保护:
2个app:接收端game app ; 发送端xmos app
a、只校验发送者权限(只要发送者有这个权限就能正常接收到广播):
广播接收端:
代码:
//注册广播
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION_KILL_HICOM);
registerReceiver(myReceiver, filter,"com.xmos.game.SEND_BROADCAST_PERMISSION",null);//这个权限是要求发送者必须要有的权限
xml:
AndroidManifest.xml 里面不用配置权限
广播发送端:
xml:
<!--广播发送端在AndroidManifest中申明的权限,在xml申请,发送广播都是具有这个发送权限的-->
<uses-permission android:name="com.xmos.game.SEND_BROADCAST_PERMISSION" />
<permission
android:name="com.xmos.game.SEND_BROADCAST_PERMISSION"
android:protectionLevel="signatureOrSystem" ><!--注意需要设置这个权限的level,实际测试不设置接收不到,设置normal和signature都行-->
</permission>
代码:
Intent intentReadDatabaseEnd = new Intent();
intentReadDatabaseEnd.setAction(BROADCAST_ACTION_KILL_HICOM);
sendBroadcast(intentReadDatabaseEnd);
LogUtils.d(TAG,"发送一条具有发送权限的广播");
b、只校验接收者权限(只要接收端具有这个权限就能正常接收,否则接收不到)
广播接收端:
代码:
//注册广播
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION_KILL_HICOM);
registerReceiver(myReceiver, filter);//不要求发送端有发送的权限
xml:
<!--xml中配置接收权限-->
<uses-permission android:name="com.xmos.game.RECEIVED_BROADCAST_PERMISSION" />
<permission
android:name="com.xmos.game.RECEIVED_BROADCAST_PERMISSION"
android:protectionLevel="signatureOrSystem" ><!--注意需要设置这个权限的level,实际测试不设置接收不到,设置normal和signature都行-->
</permission>
广播发送端:
xml:
AndroidManifest.xml 里面不用配置权限
代码:
Intent intentReadDatabaseEnd = new Intent();
intentReadDatabaseEnd.setAction(BROADCAST_ACTION_KILL_HICOM);
sendBroadcast(intentReadDatabaseEnd,"com.xmos.game.RECEIVED_BROADCAST_PERMISSION");//接收者必须要有的权限
LogUtils.d(TAG,"发送一条具有接收权限的广播");
c、双向权限保护:
广播接收端:
代码:
//注册广播
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION_KILL_HICOM);
registerReceiver(myReceiver, filter,"com.xmos.game.SEND_BROADCAST_PERMISSION",null);//这个权限是要求发送者必须要有的权限
xml:
<!--xml中配置接收权限-->
<uses-permission android:name="com.xmos.game.RECEIVED_BROADCAST_PERMISSION" />
<permission
android:name="com.xmos.game.RECEIVED_BROADCAST_PERMISSION"
android:protectionLevel="signatureOrSystem" ><!--注意需要设置这个权限的level-->
</permission>
广播发送端:
xml:
<!--广播发送端在AndroidManifest中申明的权限,在xml申请,发送广播都是具有这个发送权限的-->
<uses-permission android:name="com.xmos.game.SEND_BROADCAST_PERMISSION" />
<permission
android:name="com.xmos.game.SEND_BROADCAST_PERMISSION"
android:protectionLevel="signatureOrSystem" ><!--注意需要设置这个权限的level,-->
</permission>
代码:
Intent intentReadDatabaseEnd = new Intent();
intentReadDatabaseEnd.setAction(BROADCAST_ACTION_KILL_HICOM);
sendBroadcast(intentReadDatabaseEnd,"com.xmos.game.RECEIVED_BROADCAST_PERMISSION");//接收者必须要有的权限
LogUtils.d(TAG,"发送一条具有双向权限的广播");
实际测试OK,能正常发送接收。
有没有发现,上面设置其实是有问题的,我们设置了 signatureOrSystem权限,但是2个app签名不一样同样也能接收到广播。其实搞反了。
例如我们在接收端要求发送端具有registerReceiver(myReceiver, filter,"com.xmos.game.SEND_BROADCAST_PERMISSION",null);,但是我们权限定义是在发送端,所以没有达到校验的效果。
在同一个手机中,一个自定义权限只能定义在一个app里面,接收端定义了权限,发送端就定义不了!!!
我们接收端 registerReceiver(myReceiver, filter,"com.xmos.game.SEND_BROADCAST_PERMISSION",null);的xml中设置 SEND_BROADCAST_PERMISSION 权限,发送端只是申请(这是正常逻辑),只有设置android:protectionLevel="normal"或者
设置android:protectionLevel="signatureOrSystem"签名一样才能达到效果。
总结:
uses-permission是申请权限;
permission是自己定义权限;
android组件中的permission指明调用这个组件需要的权限。
153、混淆AIDL导致注册失败:
解决方法:
#不能混淆aidl不然会注册失败
-keep public interface android.emcom.IListenDataCallback
154、多语言设置
在资源文件res中
values
strings.xml
values-en
strings.xml
参考的博客://https://blog.csdn.net/wangsf1112/article/details/89852385
切换的代码:
public void eventLanguage(View v) {
String currentLanguage = getCurrLanguage(this);
LogUtils.d(TAG,"current language = " + currentLanguage);
Locale targetLocale = Locale.ENGLISH;
if(currentLanguage.equals(targetLocale.getLanguage())){
targetLocale = Locale.CHINESE;
}
LogUtils.d(TAG,"target language = " + targetLocale.getLanguage());
Resources res = getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.setLocale(targetLocale);
res.updateConfiguration(conf, dm);
LogUtils.d(TAG,"set language = " + getCurrLanguage(this));
//createConfigurationContext(conf);
//recreate();
/** restart activity */
finish();
startActivity(new Intent(this,MainActivity.class));
}
public String getCurrLanguage(Context context) {
Locale locale = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = context.getResources().getConfiguration().getLocales().get(0);
} else {
locale = context.getResources().getConfiguration().locale;
}
return locale.getLanguage();
}
155、后台插入数据并且获取返回值方法(mysql语句):
mapper.xml写法:
<!-- 登录记录 -->
<insert id="uploadAccount" parameterType="java.util.HashMap">
<selectKey resultType="java.lang.Integer" keyProperty="id" order="AFTER" >
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_xmos_login_account(
login_time,
userinfo_phone_os_version,
userinfo_phone_model,
userinfo_phone_brand,
userinfo_phone_mac,
w3_account,
email,
employee_number,
user_cn,
user_name_en,
user_name_zh,
login_type,
phone_sn,
phone_imei,
app_version,
reserved_field_1,
reserved_field_2,
reserved_field_3,
reserved_field_4,
reserved_field_5)
select
#{login_time},
#{userinfo_phone_os_version},
#{userinfo_phone_model},
#{userinfo_phone_brand},
#{userinfo_phone_mac},
#{w3_account},
#{email},
#{employee_number},
#{user_cn},
#{user_name_en},
#{user_name_zh},
#{login_type},
#{phone_sn},
#{phone_imei},
#{app_version},
#{reserved_field_1},
#{reserved_field_2},
#{reserved_field_3},
#{reserved_field_4},
#{reserved_field_5}
where
(
select count(*) from t_xmos_login_account
where
w3_account = #{w3_account}
and phone_sn = #{phone_sn}
and phone_imei = #{phone_imei}
) = 0;
</insert>
增加 selectKey 标签,SELECT LAST_INSERT_ID()返回插入记录的条数(没有插入返回0,插入一条记录返回1)
接口如下:
@RequestMapping(value = "/uploadAccount")
@ApiOperation(notes="上传登录账号记录",value="上传登录账号记录",httpMethod="POST")
@ApiResponses({@ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对") })
public String uploadAccount(HttpServletRequest request) {
String msg = "ok";
List<Map<String, Object>> list =new ArrayList<>();
try {
System.out.println("client string = " + request.getParameter("loginInfo"));
uploadAccount uploadBean = binder.fromJson(request.getParameter("loginInfo"), uploadAccount.class);
HashMap<String, Object> insertMap = uploadBean.getInsertMap(uploadBean);
int res = xmosTestServer.uploadAccount(insertMap);
HashMap<String, Object> map = new HashMap<>();
if(res >0){
map.put("res","success");
System.out.println("insert w3 account success.");
}else{
map.put("res","exist");
System.out.println("w3 account is already existed.");
}
list.add(map);
} catch (Exception e) {
System.out.println(e.getCause().toString());
}
return binder.toJson(Common.generateResJson(list, msg));
}
客户端请求返回的结果:
{"total":1,"result":[{"res":"exist"}],"status":"200","msg":"ok"}
156、X工程合入日志上传uploadLog模块遇到的问题及解决方法:
错误:
ERROR: Manifest merger failed with multiple errors, see logs
解决:
当我们项目Manefest的某些属性和第三方库中Manefest的属性有冲突会报上面的错误。
比如第三方库中也定义了icon、allowBackup等属性,且会与你的项目不同,则发生冲突,编译就会报错。
解决办法就是,uploadLog模块中的AndroidManifest保持和app中一致就OK了。
错误:
D:\Workspace_AS\IotWifiTester\app\src\main\AndroidManifest.xml Error:
uses-sdk:minSdkVersion 17 cannot be smaller than version 21 declared in library [:uploadLog] D:\Workspace_AS\IotWifiTester\uploadLog\build\intermediates\library_manifest\debug\AndroidManifest.xml as the library might be usin
g APIs not available in 17
Suggestion: use a compatible library with a minSdk of at most 17,
or increase this project's minSdk version to at least 21,
or use tools:overrideLibrary="com.hw.automation" to force usage (may lead to runtime failures)
解决:
将模块中的minSdkVersion 21 修改为17
157、访问xmos.consumer.hw.com异常
原因:
属于HSTS的范畴,被浏览器重定向了,而我们服务是http,当然访问不了。
解决:
chrome://net-internals/#hsts,选型Delete domain security policies中删除该域
填写“consumer.hw.com”删除
158、服务接口使用url拼接,代理网关需要特殊配置,接口免登录也需要不同的配置
接口写法,例如:(直接将taskid拼接的)
@RequestMapping(value="/complete/taskid/{taskid}", method=RequestMethod.POST)
public ResultBean<String> complete(@PathVariable("taskid")String taskId, @RequestBody JSONObject jsonParam ) {
HashMap<String, Object> map = new HashMap<>();
map.put("msg",jsonParam.get("msg"));
map.put("approve_content",jsonParam.get("approve_content"));
try{
workflowService.completeTask(taskId,map);
ResultBean<String> resultBean = new ResultBean<String>(ResultBean.SUCCESS, "completeTask", "success");
return resultBean;
}catch(Exception e) {
ResultBean<String> resultBean = new ResultBean<String>(ResultBean.FAILED, e.getMessage(), "FAILED");
return resultBean;
}
}
代理网关网关配置:(需要使用$替换,如果不使用这种方式配置,会返回404错误码)
com.hw.xmos http://100.100.100.100:9000/api/workflow/complete/taskid/${taskid} sz-inside public /workflow/complete/taskid HTTP/HTTPS POST 当前 永久 com.hw.xmos 审批接口
接口免登录配置:(否则会出现401错误码)
"*workflow/complete/taskid*," +
app端的调用方式(切记需要使用 ?taskid=123456 这种,代理网关会默认将${taskid}替换为123456 )
String 代理网关Service = getString(R.string.PERMISSION_APPLY_COMPLETE_代理网关);
res = 代理网关HttpUtils.doPostTo代理网关BodyJson(代理网关Service + "?taskid=" + taskid, JsonUtils.toJson(requestBean));
LogUtils.d(TAG,"complete 完成请求结果 代理网关 res = " + res);
159、hw JDK 替换
hw JDK下载路径:https://test.hw.com/cmcversion/index/releaseView?deltaId=19545500
版本号:
C:\Users\gq\Desktop>java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-hw_JDK_V100R001C00SPC180B003-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
以前环境变量配置(win10):
JAVA_HOME:D:\Program Files (x86)\Java\jdk1.8.0_31
CLASSPATH:%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\jre\lib\rt.jar;%JAVA_HOME%\lib\comm.jar
Path:
%CLASSPATH%
%JAVA_HOME%\bin
Android Studio工程替换:
打开Project Structure --- SDK Location --- JDK location,去掉“Use embedded JDK(recommended)”.选择hw JDK的安装路径即可
【例如:C:\Program Files\hw\jdk1.8.0_242】
160、xml中有webview会导致多语言切换失效
现象:语言切换成engilish,第一次启动app,进入有webview的activity中,显示的还是中文。而且退出的时候,进入其它页面也变成了中文(webview会将语言切换成手机默认的语言)
解决方法:在myapplication的oncreate回调中加入如下代码:
new WebView(this).destroy();
在LoginActivity的onCreate方法中调用setLanguage()方法。
private void setLanguage(){
LogUtils.d(TAG,"setLanguage");
/** 获取当前手机语言 */
String currentLanguage = LanguageUtils.getCurrLanguage(this);
LogUtils.d(TAG,"current language = " + currentLanguage);
/** 读取持久化语言设置 */
SaveReadSettingXML ssx = new SaveReadSettingXML(this, CONTANT.SettingXml);
String languageXML = ssx.read(CONTANT.app_language, "");
LogUtils.d(TAG,"languageXML = " + languageXML);
if(!languageXML.equals("") && !currentLanguage.equals(languageXML) ){
/** 切换语言 */
Locale targetLocale = Locale.CHINESE;
if(languageXML.equals("en")){
targetLocale = Locale.ENGLISH;
}
LanguageUtils.changeLanguage(this,targetLocale);
LogUtils.d(TAG,"切换语言 = " + targetLocale.getLanguage());
}else{
LogUtils.d(TAG,"xml语言为空 or 和当前语言一致,不需要切换语言。");
}
}
实际测试验证OK
161、遇到一个“Didn’t find class “android.support.v4.content.FileProvider” on path:”问题
现象:使用gradlew assembleDebug编译版本,安装会出现上面的问题。
查阅资料,这个和multiDexEnabled true分包有关系。参考:https://blog.csdn.net/fox_wei_hlz/article/details/78732907
我当前最小的minSdkVersion设置是17.
目前解决方法2个:
1、将minSdkVersion设置成21,测试OK
2、debug版本编译也使用混淆,测试OK
备注:如果设置成17,参考网上的解决方法并不生效。(在application中重写 attachBaseContext方法)