android代码检查
‘壹’ 一行代码搞定 Android 复杂列表埋点曝光
一个好的产品离不开数据分析,在手机 APP 中,数据分析极致化需要细致到某个时刻列表曝光的了哪几个 Item。
2022 年了,基本上目前 Android 上可以滑动的复杂列表都是 RecyclerView 或者其扩展,这里分享一个封装的思路。
如果非要细化细节:
各种方案核心都差不多,最关键的就是通过 LayoutManager 获取屏幕内第一个可见和最后一个可见 item position,上报其区间内的 Item。这里简称这个逻辑为 检查上报逻辑 。
但是触发时机有所不同,通常如下方案一和二所述,当然除了方案一和方案二外,还有一些别的方案,比如监听 RecyclerView 的布局树变化触发 检查上报逻辑 等方案。
可以发现方案二相比方案一更有利于减少各种回调的注册和周期的控制,下文会在方案二的基础上,阐述用法和相关实现思路。
仓库地址: RecyclerViewExposure
这里会主要说明一些主要逻辑,需要完整的逻辑可以 fork 仓库 查看
思路来自于 lifecycle 的设计,这里主要是想让 Activity/Fragment 提供可见和不可见的状态变化给外部订阅
对 List Item 的收集处理是 RecyclerViewExposure 最核心的收集数据逻辑,这里针对在 Activity 的使用作为例子。上文已经讲述如何做一个 PageLifeCycleHolder 为其他组件提供页面可见状态,下文将直接使用。
‘贰’ android性能测试工具有哪些
大概有如下几个工具:
android针对上面这些会影响到应用性能的情况提供了一些列的工具:
1 布局复杂度:
hierarchyviewer:检测布局复杂度,各视图的布局耗时情况:
Android开发者模式—GPU过渡绘制:
2 耗电量:Android开发者模式中的电量统计;
3 内存:
应用运行时内存使用情况查看:Android Studio—Memory/CPU/GPU;
内存泄露检测工具:DDMS—MAT;
4 网络:Android Studio—NetWork;
5 程序执行效率:
静态代码检查工具:Android studio—Analyze—Inspect Code.../Code cleanup... ,用于检测代码中潜在的问题、存在效率问题的代码段并提供改善方案;
DDMS—TraceView,用于查找程序运行时具体耗时在哪;
StrictMode:用于查找程序运行时具体耗时在哪,需要集成到代码中;
Andorid开发者模式—GPU呈现模式分析。
6 程序稳定性:monkey,通过monkey对程序在提交测试前做自测,可以检测出明显的导致程序不稳定的问题,执行monkey只需要一行命令,提交测试前跑一次可以避免应用刚提交就被打回的问题。
说明:
上面提到的这些工具可以进Android开发者官网性能工具介绍查看每个工具的介绍和使用说明;
Android开发者选项中有很多测试应用性能的工具,对应用性能的检测非常有帮助,具体可以查看:All about your phone's developer options和15个必知的Android开发者选项对Android开发者选项中每一项的介绍;
针对Android应用性能的优化,Google官方提供了一系列的性能优化视频教程,对应用性能优化具有非常好的指导作用,具体可以查看:优酷Google Developers或者Android Performance Patterns。
二 第三方性能优化工具介绍
除了android官方提供的一系列性能检测工具,还有很多优秀的第三方性能检测工具使用起来更方便,比如对内存泄露的检测,使用leakcanry比MAT更人性化,能够快速查到具体是哪存在内存泄露。
leakcanary:square/leakcanary · GitHub,通过集成到程序中的方式,在程序运行时检测应用中存在的内存泄露,并在页面中显示,在应用中集成leancanry后,程序运行时会存在卡顿的情况,这个是正常的,因为leancanry就是通过gc操作来检测内存泄露的,gc会知道应用卡顿,说明文档:LeakCanary 中文使用说明、LeakCanary: 让内存泄露无所遁形。
GT:GT Home,GT是腾讯开发的一款APP的随身调测平台,利用GT,可以对CPU、内存、流量、点亮、帧率/流畅度进行测试,还可以查看开发日志、crash日志、抓取网络数据包、APP内部参数调试、真机代码耗时统计等等,需要说明的是,应用需要集成GT的sdk后,GT这个apk才能在应用运行时对各个性能进行检测。
‘叁’ Android-android中如何通过代码检测是否有root权限
最直接有效的方式就是执行su命令,su就是切换到root用户,如果su命令可以执行,限则表示root成功。
具体测试方式:
1.安装进入adb目录(SDK中自带adb)。
2.adb shell 进入shell模式。
3.su 切换到root用户。
4.切换到root用户后会显示一个#号。
5.或直接在android 版本的 shell (附件)中执行命令。
‘肆’ 怎么用代码判断android手机是否开启了ROOT权限
android手机开启了root权限,主要是根据root之后,获取了手机的最高权限,底层linux系统就会生成一个以su结尾的文件,su代表super超级权限,如下代码:
/**
* 判断当前手机是否有ROOT权限
* @return
*/
public boolean isRoot(){
boolean bool = false;
try{
if ((!new File("/system/bin/su").exists()) && (!new File("/system/xbin/su").exists())){
bool = false;
} else {
bool = true;
}
Log.d(TAG, "bool = " + bool);
} catch (Exception e) {
}
return bool;
}
android底层是使用linux进行编译和一些驱动、网络管理的,所以可以根据linux的权限特性来判断是否root,权限的管理在linux里面很多,包括读写、删除文件的权限,也有关于访问网络的权限,这些权限都需要开通才能有。
‘伍’ Android检测是否有网络的代码
public class CheckNetwork {
private NetworkStateReceiver netStateReceiver;
private Context mContext;
private long servicetimes = 0;
private Utility myUtility = null;
private int runInternerTime = 0;
private HandlerNetwork mHandlerNetwork = new HandlerNetwork();
private static CheckNetwork mNetworkState = null;
private Map<String, Handler> mHandlerOuterMap = new HashMap<String, Handler>();
public static CheckNetwork getNetworkState(Context context) {
if (mNetworkState == null) {
mNetworkState = new CheckNetwork(context);
}
return mNetworkState;
}
private CheckNetwork(Context context) {
mContext = context;
myUtility = new Utility(mContext);
registNetworkStatusReceiver();
}
public void registNetworkStatusReceiver() {
netStateReceiver = new NetworkStateReceiver(mHandlerNetwork);
IntentFilter filter = new IntentFilter(
"android.net.conn.CONNECTIVITY_CHANGE");
filter.addAction("android.intent.action.MY_NET_BOOTCHECK");
mContext.registerReceiver(netStateReceiver, filter);
Intent intent = new Intent("android.intent.action.MY_NET_BOOTCHECK");
mContext.sendBroadcast(intent);
}
/**
* 增加一个网络状态消息接受者
*
* @param key
* @param handler
*/
public void addHandler(String key, Handler handler) {
mHandlerOuterMap.put(key, handler);
if (!key.equals("IntegrateActHandlerKey")) {
// 当整合页添加handle时,网络状态还没准备好,所以不通过handle对外发通知
sendNetworkMessage(mHandlerOuterMap);
}
}
/**
* 向接受者集合中的接受者发送网络状态的消息
*
* @param map
*/
public static void sendNetworkMessage(Map<String, Handler> map) {
Set<String> key = map.keySet();
for (Object element : key) {
String s = (String) element;
Message msg = Message.obtain();
msg.what = GlobalParams.Handle_Msg.NetWorkStatus.ordinal();
map.get(s).sendMessage(msg);
}
}
private class HandlerNetwork extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == GlobalParams.Handle_Msg.ConnectSuccess.ordinal()) {
if (System.currentTimeMillis() - servicetimes > 3000) {// 防止短时间内接收多个广播,执行多次
runInternerTime = 0;
servicetimes = System.currentTimeMillis();
new CheckServerThread().start();
}
} else if (msg.what == GlobalParams.Handle_Msg.ConnectFail
.ordinal()) {
if (System.currentTimeMillis() - servicetimes > 2000) {// 防止短时间内接收多个广播,执行多次
servicetimes = System.currentTimeMillis();
runInternerTime++;
if (runInternerTime > 3) {
// Intent intent = new Intent(context,
// IntegrateActivity.class);
// context.startActivity(intent);
GlobalParams.NetworkStatus = 0;
sendNetworkMessage(mHandlerOuterMap);
} else {
Log.e("RUN", "本地连接不上尝试第" + runInternerTime + "次");
Message msgrun = Message.obtain();
msgrun.what = GlobalParams.Handle_Msg.InternerFail
.ordinal();
mHandlerNetwork.sendMessageDelayed(msgrun, 3000);
}
}
} else if (msg.what == GlobalParams.Handle_Msg.InternerFail
.ordinal()) {
//检测棒子与路由器间的连接不足3次时,继续检测
Intent intent = new Intent(
"android.intent.action.MY_NET_BOOTCHECK");
mContext.sendBroadcast(intent);
}else if(msg.what==GlobalParams.Handle_Msg.NetWorkStatusIn.ordinal()){
if(msg.arg1==0){
GlobalParams.NetworkStatus = 2;
sendNetworkMessage(mHandlerOuterMap);
}else{
GlobalParams.NetworkStatus = 1;
sendNetworkMessage(mHandlerOuterMap);
}
}
}
}
private class CheckServerThread extends Thread{
@Override
public void run() {
File f = new File(Environment.getExternalStorageDirectory().getPath()
+ "/CudgelLauncher/Images");
if (!f.exists())
f.mkdirs();
File ftemp = new File(Environment.getExternalStorageDirectory()
.getPath() + "/CudgelLauncher/Temp");
if (!ftemp.exists())
ftemp.mkdirs();
// 等待SD卡准备完成
while (true) {
try {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 因为旧的配置文件都不适合用于新版,所以要把旧的都清除再重新加载
myUtility.delete(new File("/data/data/"
+ GlobalParams.PROCESSPACKNAME
+ "/databases/cudgellauncher"));
myUtility.delete(new File("/system/media/cudgel"));
myUtility.delete(new File("/mnt/sdcard/CudgelLauncher"));
break;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
XmlParser parser = new XmlParser();
File fileConfig = new File("/mnt/sdcard/CudgelLauncher/config.xml");
File file = new File("/mnt/sdcard/CudgelLauncher/ConfigInterface.xml");
while (true) {
try {
Map<String, String> mapConfig = parser
.getServiceAsmx(fileConfig);
Map<String, String> mapInterface = parser.getServiceAsmx(file);
if (!fileConfig.exists() || mapConfig == null
|| !mapConfig.containsKey("area") || !file.exists()
|| mapInterface == null
|| !mapInterface.containsKey("servicenamespace")) {
// 第一次运运没配置文件时或防止配置文件被损坏,重新复制
myUtility.saveConfigFile(Environment
.getExternalStorageDirectory().getPath()
+ "/CudgelLauncher", "Config.xml", R.raw.config,
mContext);
myUtility.saveConfigFile(Environment
.getExternalStorageDirectory().getPath()
+ "/CudgelLauncher", "ConfigInterface.xml",
R.raw.configinterface, mContext);
} else {
GlobalParams.AreaID = mapConfig.get("area");
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//开始检测通不通公网
PingAddrUtils pingAddrUtils=new PingAddrUtils(mHandlerNetwork,GlobalParams.Handle_Msg.NetWorkStatusIn.ordinal());
pingAddrUtils.startPingAddr();
super.run();
}
}
}
ublic class PingAddrUtils {
private final static byte[] writeLock = new byte[0];
private String[] urls = new String[] { "tiipot.chinadhia.com",
"www.youku.com", "v.qq.com", "www.letv.com", "www..com",
"www.pptv.com", "tv.sohu.com" };
private String[] names = new String[] { "云联", "优酷", "腾讯", "乐视", "网络",
"pptv", "搜狐" };
private boolean[] isConected = { false, false, false, false, false, false,
false };
private Handler mHandler;
private int msgWhat;
private Timer timer;
/**
* mHandler用于接收返回成功连接或失败的消息
*
* @param mHandler
* @param mContext
*/
public PingAddrUtils(Handler mHandler, int msgWhat) {
this.mHandler = mHandler;
this.msgWhat = msgWhat;
}
/**
* 开始ping地址,返回值通过消息形式发送到构造方法传入的Handler,msg.What也是用构造方法传入的msgWhat
* 参数说明arg1:0失败;1通了;obj:用于生成二维码的字符串(String)
*
* @param msgWhat
*/
public void startPingAddr() {
// 开始检测时把状态都置为false以防重复检测时出现错误结果
for (int i = 0; i < isConected.length; i++) {
isConected[i] = false;
}
for (int i = 0; i < urls.length; i++) {
new IpAddrThread(i).start();
}
timer = new Timer();
timer.schele(new TimerTask() {
@Override
public void run() {
if (!isAllConected()) {// 全通线程中已处理了,所以这里只考虑没有全通的情况
boolean isAnyOneConected = false;
for (int i = 0; i < isConected.length; i++) {
isAnyOneConected = isAnyOneConected || isConected[i];
}
if (isAnyOneConected) {
sendMessage(1);// 有某个地址连接成功了。
} else {
sendMessage(0);// 全部地址连接失败。
}
}
}
}, 9000);// 9秒后
}
/***
* 判断是否所有地址都一ping通
*
* @return
*/
private boolean isAllConected() {
boolean isAllConected = true;
for (int j = 0; j < isConected.length; j++) {
// 所有都为TRUE isAllConected才为TRUE
isAllConected = isAllConected && isConected[j];
}
return isAllConected;
}
class IpAddrThread extends Thread {
int IpNum;
public IpAddrThread(int IpNum) {
this.IpNum = IpNum;
}
@Override
public void run() {
try {
Process p = Runtime.getRuntime().exec(
"ping -c 3 -w 5 " + urls[IpNum]);
int status = p.waitFor();
if (status == 0) {
// pass
// mPingIpAddrResult = "连接正常";
setThisIpTrue(IpNum);
} else {
// Fail:Host unreachable
// mPingIpAddrResult = "连接不可达到";
}
} catch (UnknownHostException e) {
// Fail: Unknown Host
// mPingIpAddrResult = "出现未知连接";
} catch (IOException e) {
// Fail: IOException
// mPingIpAddrResult = "连接出现IO异常";
} catch (InterruptedException e) {
// Fail: InterruptedException
// mPingIpAddrResult = "连接出现中断异常";
}
}
}
/**
* 生成用于产生二维码的字符串。01~10~21~31~41~50~61,有7组结果,用符号~分隔,每组结果都有两位数字,第一位数字代表是哪个服务器(
* 0是XX,1是优酷,2是 腾讯, 3是乐视, 4是网络,5是pptv,6是搜狐),第二位数字代码ping的结果(0表示不通,1表示已通)
*
* @return
*/
private String getQRCodeString() {
String oRCodeString = "";
for (int i = 0; i < isConected.length; i++) {
int state = 0;
if (isConected[i]) {
state = 1;
}
oRCodeString = oRCodeString + i + state;
if (i < isConected.length - 1) {
oRCodeString = oRCodeString + "~";
}
}
writeFileSdcard(oRCodeString);
return oRCodeString;
}
/**
* 写入SD卡
*
* @param filePath
* @param message
*/
private void writeFileSdcard(String message) {
try {
File file = new File("/mnt/sdcard/netstatus.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
file.createNewFile();
}
// FileOutputStream fout = openFileOutput(fileName, MODE_PRIVATE);
FileOutputStream fout = new FileOutputStream(file);
byte[] bytes = message.getBytes();
fout.write(bytes);
fout.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* 定义消息并发送
*
* @param state
*/
private void sendMessage(int state) {
Message msg = Message.obtain();
msg.what = msgWhat;
msg.arg1 = state;// 全通
msg.obj = getQRCodeString();
mHandler.sendMessage(msg);
}
/**
* 把该ipNum代表的地址的通达状况置为True并判断是否所有都已联通,如果都已连通则发送成功连接的消息
*
* @param IpNum
*/
private void setThisIpTrue(int IpNum) {
synchronized (writeLock) {
isConected[IpNum] = true;// 把本地址的标记置为true;
if (isAllConected()) {
sendMessage(1);
timer.cancel();
timer = null;
}
}
}
}