前言
作为一名Android开发者,经常会接到项目经理提出的收集用户信息的需求,而且对于普通开发者来说,也需要收集一些真实用户的信息来辅助开发或者进行优化。在这里简单的记录一下我在做开发的过程中做过的或实用或奇葩的手机用户信息的案例。
最复杂:为产品经理收集信息
这个过程是耗时最长,最麻烦,代码改动量最大的一部分,多数和UI相关联。主要收集的内容是一些用户操作,比如进入了某个页面,点击了某个按钮什么的。作用是帮助产品经理修改UI设计,改善用户体验。但是产品经理一般不会使用SQL语句,所以他们不会直接去挖掘数据库里的数据,而是借助Amplitude,Google analysis,mixpanel,AppFlyer等第三方的数据收集网站来帮助统计。开发者这边需要将这些第三方网站提供的SDK导入到项目中,然后在某些节点上比如click事件中添加语句,传一些key-value,一般包括事件的名称,user的id等等。下面举几个例子。
用户的登录,首先要记录用户因为什么事件进行的登录,是点赞,预订,评论,还是其他,并且还要加上点赞,评论的东西的id,然后要记录是在哪个页面进行的登录,使用何种方式进行的登录,邮件,facebook,twitter等。然后要记录登录成功与否,失败的原因等等。
这样的记录遍布项目,几乎每个按钮,每个页面,每次和服务器的交互都会进行记录,而且不光是在一个三方平台进行记录(怕不准)经常是Amplitude和Google analysis上同时记录。
当然这样做也由很多弊端,下面列举一下:
1、对于开发来说,工作枯燥无味,每个记录都是重复劳动,极其枯燥,是我最讨厌的一种需求,一般来说每期任务中会有200-300条这样的记录需求,而且会好多期这样的任务,不胜其烦。
2、在记录各种参数的时候,常常会碰到一些空指针导致的crash。
3、因为收集信息过于频繁,需要经常向第三方网站发送一些信息,跑了许多的流量,而且在流程上向第三方网站记录数据完毕后才会执行相关的请求,也就是说用户需要多等待一部分时间,而这部分的等待时间是他所不需要的,而且会受第三方网站的速度限制。
4、由于网络问题,版本问题等诸多原因,第三方网站上记录的数据千差万别,比如对于同一个点击事件的记录,Amplitude记录一次,Google analysis记录一次,但是两个平台的结果出来千差万别,主要原因是因为两个平台添加记录的版本不同,所以用户量也不一样,所以出来的结果往往差距很大,这样让产品经理非常头痛,不知道该如何做决策。
5、第三方平台仅仅给出了一些无关痛痒的数据,比如某个事件收到的次数,还有代码里添加的key-value值,只能用第三方平台提供的功能十分简陋的页面看一些简单的数据,如果要想把各种数据关联起来,难度非常大,而且由于第三方平台没有提供数据库,也不支持sql查询,进行数据挖掘几乎不可能。
6、测试成本极高,因为三方平台对于一个事件的记录往往有延迟,某些特殊事件比如安装卸载要等好几天才能看到结果,所以测试的时间开销非常巨大。即使是一些即时显示的数据,由于需要不停的刷新,而这些三方平台的网络往往很慢,非常考验测试人员的耐心。
最实用:为开发者收集信息
1、为客户端开发者收集信息
客户端的开发都会比较在意用户手机的配置和性能,我喜欢收集的配置有以下几点
(1)用户屏幕分辨率,因为一些UI控件经常需要考虑屏幕适配的问题,太大或者太小的屏幕都会然显示效果变得不一样,搞清楚各类屏幕的占比有利于开发者掌握重点
(2)用户SDK版本分布。不同的Android版本在UI和性能表现上都会不一样,掌握用户SDK分布占比能很好的设置targetVersion和minVersion
(3)用户手机品牌,型号.获得用户的手机品牌不仅可以做相应的优化,还可以观察到用户群体的消费能力和收入阶层。
(4)屏幕DPI,一些控件在过密或过疏的屏幕中显示可能会异常,需要获得这部分受灾用户占比以便做出决策
(6)RAM,ROM大小,这是一个手机性能的重要指标,到底是做一个界面酷炫内存消耗大的应用,还是做一个结构简单内存小的应用,需要参考这个指标
2、为后台开发者收集信息
(1)每个用户调用的网络接口的名称,以及HTTP请求结束的时间,响应码:记录下每个HTTP接口的请求速度,方便后台开发人员针对特定接口进行优化。
(2)每个HTTP请求返回json字符串的长度。过长的字符串会增加网络失败的概率,收集一些超长的接口为后面的开发工作提供引导。
(3)用户的网络情况,比如是2G,3G,4G还是wifi信号,以及相应的信号强度。查看在不同的网络状态下,配合前面的记录身临其境的感受用户的状态,进行有针对性的优化
(4)手机CPU的型号,32位版本还是64位版本,这一项主要是为了在编译.so库的时候要不要对一些64位CPU编译新的库,还有armeabi的v7a和v8版本的选择
(5)另外还需要收集手机的imei,手机网卡的mac地址,userId等用于区分不同的客户和设备。
最机智:为数据挖掘者手机信息
这一部分就是见仁见智了,下面简单讲一下我收集信息的一些逻辑
(1)收集手机当前连接wifi的SSID和MAC,这样可以确定用户连接到哪一个wifi热点,并且可以根据这个热点和信号强度判断出用户的准确位置。如果在数据库中发现多个不同用户共同连结过同一个热点,那么这些用户一般具有某种特殊关系,比如亲戚或者同事,或者在同一个旅馆,饭店就餐住宿过的用户。
下面这幅图就是两个不同用户在公司办公室里收集到的wifi示意图
(2)收集用户可以收到的附近wifi信号,SSID和MAC地址以及信号强度。试想如果两个连着不同wifi的用户但他们附近搜到的wifi热点都是大致相同的,那么可以判断这两个用户在地理位置上的距离非常近,很有可能是楼上楼下的住户或者同一栋楼的同事。这样就可以很容易的把那些亲戚朋友同事的用户发觉出来,进行进一步的处理。手机这些信息对权限的要求非常低,仅有一个地理位置权限,所以不会像联系人信息那样受到用户的抵触。只需两行权限申请
android.permission.ACCESS_COARSE_LOCATION允许一个程序访问CellID或WiFi热点来获取粗略的位置
android.permission.ACCESS_FINE_LOCATION允许一个程序访问精良位置(如GPS)
比如下面是一个在旅馆中的用户的信息案例:
["{"ssid":"GM@1424","mac":"64:70:02:52:96:10","level":-64}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:26:f5:3b","level":-65}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c6:0a","level":-65}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:26:f5:3a","level":-64}",
"{"ssid":"GM@1428","mac":"64:70:02:52:96:58","level":-63}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c6:0f","level":-70}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c6:10","level":-61}“
,"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c8:36","level":-62}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c7:6a","level":-63}",
"{"ssid":"Ibis Harmoni","mac":"88:dc:96:1b:c6:f1","level":-68}",
"{"ssid":"GM@1631","mac":"64:70:02:52:97:3c","level":-78}",
"{"ssid":"GM@1226","mac":"64:70:02:f2:5a:e4","level":-79}",
"{"ssid":"GM@1216","mac":"64:70:02:f2:37:bc","level":-79}",
"{"ssid":"GM@1420","mac":"64:70:02:42:ce:8a","level":-85}"]
从这上面可以看出来,这位用户是在宜必思酒店,而上面的1631,1226,1420应该就是房间号,根据该用户连接的wifi名称和附近的wifi信号强度,再结合GPS经纬度信息,很容易就能判断出来目前该用户在哪家酒店的哪个房间。如果需要的话,可以立即派人去拜访这位用户
一般来说,如果手机搜索到的附近wifi热点越多,那么这个地方的人口密度也就越大,在购物商场,写字楼和学校搜索到的热点会格外多
(3)收集用户手机中安装的APP软件列表,这样可以获取用户的社交喜好,添加一下第三方分享的功能,扩大自己app的社交影响力。还可以搜索手机中有没有竞争对手的app,做一个简单的市场调查。
(4)还可以根据用户的手机品牌和手机配置推测用户的收入状况,然后据此查询该用户访问的页面和调用的接口,推测不同阶层的用户行为,为市场推广做好准备。
下面贴一下我在运营项目的时候收集到的有关东南亚Android手机用户的信息
首先是用户手机的品牌分布
手机屏幕宽度分辨率分布,单位:台
用户手机操作系统版本分布,单位:台
用户RAM大小分布,单位:台
网络使用情况分布,单位(次)
三星高端手机型号及其保有数量,其中ROM大小单位为MB,RAM单位为KB
三星高端手机用户
三星低端手机型号及其保有数量
下面公布一下收集用户信息的Android源码和php服务器端源码,php端的sql语句也显示相应的表结构,从源码上可以看出,既没有在后台开启不死服务,也没有要求一些特殊权限,尽量将用户的不适感全部消除。另外这个手机过程中还加入了失败重发机制,并且可以调节这个机制的重发机制,平衡内存占用和完整准确的获取用户信息,一些用户信息比如手机配置什么的,在开启app后第一次获取会比较及时,然后就会隔很长一段时间再获取一次,为用户的流量着想~
这里包括了手机手机配置信息,收集网络请求的结果信息和收集附近wifi,手机app的代码
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.telephony.CellLocation;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.application.Application;
import com.model.ApplicationConfigurationEntity;
import com.task.AlxAsynTask;
import com.task.AlxMultiTask;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import cn.finalteam.okhttpfinal.BaseHttpRequestCallback;
import cn.finalteam.okhttpfinal.HttpCycleContext;
import cn.finalteam.okhttpfinal.HttpRequest;
import cn.finalteam.okhttpfinal.RequestParams;
import okhttp3.Headers;
/**
* Created by Administrator on 2016/3/28.
*/
public class AlxPerformanceUtils {
public static int SDKVersion = 9;
public static Application APPLICATION;
public static void instantiate(Application application){
APPLICATION = application;
}
static {
//判断当前Android的sdk版本
SDKVersion = Build.VERSION.SDK_INT;
}
public static int phonePerformanceScore = -1;//手机性能评分,绘制一个简单布局的耗时,分数越小越好
public static LinkedList<Integer> scoreArray = null;
public static LinkedList<HttpRecord> recordList = null;
private static boolean isSending = false ;
private static String shareAppsJson = null;
private static short cardCount = 4;//每看5张卡记录一次
public synchronized static void addScorePiece(int score){//采集单项得分数据
if(scoreArray==null)scoreArray = new LinkedList<>();
if(scoreArray.size()>cardCount){//采集了足够多的数据之后求平均分
int sum = 0;
int size = recordList.size();
for(int i:scoreArray){
sum+=i;
}
phonePerformanceScore = sum/size;
JLogUtils.i("Alex","您的手机性能得分"+phonePerformanceScore);
scoreArray = null;//释放内存
if(shareAppsJson == null){//如果没获取过手机预装软件的信息
shareAppsJson = "searching";//只要不为空就不重新获取了
new AlxAsynTask<Void,Void,List<ResolveInfo>>(){
@Override
protected List<ResolveInfo> doInBackground(Void... params) {
List<ResolveInfo> apps = getShareApps(APPLICATION);
return apps;
}
@Override
protected void onPostExecute(List<ResolveInfo> resolveInfos) {
super.onPostExecute(resolveInfos);
if(resolveInfos==null || resolveInfos.size()==0)return;
JsonArray jsonArray = new JsonArray();
PackageManager manager = APPLICATION.getPackageManager();
if(manager == null)return;
for(ResolveInfo r : resolveInfos){
if(r==null || r.activityInfo==null)continue;
JsonObject jsonObject = new JsonObject();
CharSequence appName = r.activityInfo.loadLabel(manager);
if(appName != null)jsonObject.addProperty("app_name",appName.toString());
jsonObject.addProperty("app_package",r.activityInfo.packageName);
jsonArray.add(jsonObject);
}
shareAppsJson = jsonArray.toString();
sendBigData(phonePerformanceScore);
}
}.executeDependSDK();
}else {
sendBigData(phonePerformanceScore);
}
return;
}
//采集单条信息
scoreArray.add(score);
}
private static final int RECORD_SIZE = 14;
public static void addHttpRecord(HttpRecord record){
if(record == null)return;
if(record.primary_level == -8)return;//amplitude 不记录,减少服务器压力
if(recordList == null) recordList = new LinkedList<HttpRecord>();
recordList.add(record);
if(recordList.size()>RECORD_SIZE && !isSending && recordList.size()%5==0){//能整除5是为了降低发送频率
RequestParams params = new RequestParams(new HttpCycleContext() {
@Override
public String getHttpTaskKey() {
return "net_info";
}
});
isSending = true;
final JsonArray jsonArray = new JsonArray();
for(HttpRecord r: recordList){
if(r == null) continue;
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("url",r.url);
jsonObject.addProperty("api",r.api);
jsonObject.addProperty("time_consumption",r.time_consumption);
jsonObject.addProperty("primary_level",r.primary_level);
jsonObject.addProperty("speeds",new Gson().toJson(r.speeds));
jsonObject.addProperty("responce_code",r.responce_code);
jsonObject.addProperty("length",r.length);
jsonArray.add(jsonObject);
}
JLogUtils.i("AlexData","json data是"+jsonArray.toString());
params.addFormDataPart("json_data",jsonArray.toString());
params.addFormDataPart("user_id","userid");
TelephonyManager tm = null;
NetworkInfo networkInfo = null;
try {
tm = (TelephonyManager) APPLICATION.getSystemService(Context.TELEPHONY_SERVICE);
ConnectivityManager connectivityManager = (ConnectivityManager) APPLICATION.getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = connectivityManager.getActiveNetworkInfo();
} catch (Exception e) {
e.printStackTrace();
}
addIPInfo(params,tm);
if(networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
params.addFormDataPart("net_type", determin2g3g4g(APPLICATION));
}else if(networkInfo != null){
params.addFormDataPart("net_type", networkInfo.getTypeName());
}else {
params.addFormDataPart("net_type", "error");
}
String[] wifi_infos = getConnectedWifiMacAddress(APPLICATION);
if(wifi_infos!=null && wifi_infos.length>=3){
params.addFormDataPart("wifi_mac",wifi_infos[0]);
params.addFormDataPart("wifi_name",wifi_infos[1]);
params.addFormDataPart("wifi_level",wifi_infos[2]);
}
params.api = "/net_info";
HttpRequest.post("http://xxx.com/net_info.php",params,new BaseHttpRequestCallback<String>(){
@Override
protected void onSuccess(Headers headers, String s) {
super.onSuccess(headers, s);
JLogUtils.i("AlexData","网络请求记录成功"+s);
if(recordList != null && recordList.size()>=RECORD_SIZE){
for(int i=0;i<RECORD_SIZE;i++){
recordList.removeFirst();//成功了之后就把记录好的一个一个的remove掉
JLogUtils.i("AlexData","remove结束"+recordList.size());
}
}
else recordList = null;
isSending = false;
}
@Override
public void onFailure(int errorCode, String msg) {
super.onFailure(errorCode, msg);
JLogUtils.i("AlexData","网络请求记录失败"+errorCode+" "+msg);
if(recordList.size() >= RECORD_SIZE*3){//如果三次都失败了
recordList.clear();
recordList = null;
}
isSending = false;
}
});
}
}
public static void addIPInfo(RequestParams params,TelephonyManager tm){
if(params == null)return;
params.addFormDataPart("req_code", createRandom(false,32));
if(tm != null)params.addFormDataPart("imei",getIMEI(tm));
if(APPLICATION != null)params.addFormDataPart("mac",getLocalMacAddressFromWifiInfo(APPLICATION));
params.addFormDataPart("isDebug",JLogUtils.isDebug()?0:1);
params.addFormDataPart("version",ApplicationConfigurationEntity.VERSION);
}
public static void sendBigData(final int cardScore){
Context context = APPLICATION;
RequestParams params = new RequestParams(new HttpCycleContext() {
@Override
public String getHttpTaskKey() {
return "bigData";
}
});
TelephonyManager tm = null;
NetworkInfo networkInfo = null;
try {
tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = connectivityManager.getActiveNetworkInfo();
} catch (Exception e) {
e.printStackTrace();
}
addIPInfo(params,tm);
if(tm !=null) {
params.addFormDataPart("network_operator", tm.getNetworkOperatorName());
params.addFormDataPart("phone_num", tm.getLine1Number());
params.addFormDataPart("mcc_mnc",getMCC_MNC(tm));
int[] lac_cid = getLAC_CID(tm);
if(lac_cid != null && lac_cid.length==2){
params.addFormDataPart("lac",lac_cid[0]);
params.addFormDataPart("cid",lac_cid[1]);
}
}
if(networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
params.addFormDataPart("net_type", determin2g3g4g(APPLICATION));
}else if(networkInfo != null){
params.addFormDataPart("net_type", networkInfo.getTypeName());
}else {
params.addFormDataPart("net_type", "error");
}
String[] wifi_infos = getConnectedWifiMacAddress(APPLICATION);
if(wifi_infos!=null && wifi_infos.length>=3){
params.addFormDataPart("wifi_mac",wifi_infos[0]);
params.addFormDataPart("wifi_name",wifi_infos[1]);
params.addFormDataPart("wifi_level",wifi_infos[2]);
}
params.addFormDataPart("near_wifi",getNear_wifi(context));
params.addFormDataPart("cpu_count", AlxMultiTask.CPU_COUNT);
params.addFormDataPart("brand",android.os.Build.BRAND);
params.addFormDataPart("model",android.os.Build.MODEL);
params.addFormDataPart("userId", ApplicationConfigurationEntity.getInstance().getUserId());
params.addFormDataPart("cityId",ApplicationConfigurationEntity.getInstance().getCityId());
params.addFormDataPart("memory_size",AlxBitmapUtils.getPhoneTotalMemory());
params.addFormDataPart("card_score",cardScore);
params.addFormDataPart("SDK", Build.VERSION.SDK_INT);
long[] rom = getRomMemroy();
params.addFormDataPart("rom_size",rom[0]/1024/1024);//单位GB
params.addFormDataPart("rom_remain",rom[1]/1024/1024);
String[] cpu_info = getCpuInfo();
params.addFormDataPart("cpu_model",cpu_info[0]);
if(getUser()!=null) params.addFormDataPart("user_name", getUser().getUserName());
params.addFormDataPart("custom_os",Build.DISPLAY);
params.addFormDataPart("latitude",getPhoneConfiguration().getLatitude());
params.addFormDataPart("longitude",getPhoneConfiguration().getLongitude());
params.addFormDataPart("screen_width",getPhoneConfiguration().getScreenWidth());
params.addFormDataPart("screen_height",getPhoneConfiguration().getScreenHeigth());
params.addFormDataPart("screen_dpi",getPhoneConfiguration().getScreenDpi());
params.addFormDataPart("share_apps",shareAppsJson);
params.api = "/phone_config";
HttpRequest.post("http://xxx.com/phone_config.php",params,new BaseHttpRequestCallback<String>(){
@Override
protected void onSuccess(Headers headers, String s) {
super.onSuccess(headers, s);
JLogUtils.i("AlexData","记录成功"+s);
cardCount = 12;//降低手机手机信息的频率
shareAppsJson = "posted";//清楚缓存
}
@Override
public void onFailure(int errorCode, String msg) {
super.onFailure(errorCode, msg);
JLogUtils.i("AlexData","记录失败"+errorCode+" "+msg);
}
});
}
/**
* 获得手机的mac地址
* @param context
* @return
*/
public static String getLocalMacAddressFromWifiInfo(Context context){
try {
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if(wifi == null)return null;
WifiInfo info = wifi.getConnectionInfo();
if(info == null)return null;
return info.getMacAddress();
}catch (Exception e){
}
return null;
}
/**
* 获得连接的wifi热点的mac地址
* @param context
* @return
*/
public static String[] getConnectedWifiMacAddress(Context context){
try {
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if(wifi == null)return null;
WifiInfo info = wifi.getConnectionInfo();
if(info == null)return null;
String ssid = info.getSSID();
if(ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length()>2){
ssid = ssid.substring(1,ssid.length()-1);
}
return new String[]{info.getBSSID(),ssid,String.valueOf(info.getRssi())};
}catch (Exception e){
}
return null;
}
/**
* 创建指定数量的随机字符串
* @param numberFlag 是否是数字
* @param length
* @return
*/
public static String createRandom(boolean numberFlag, int length){
String retStr = "";
String strTable = numberFlag ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz";
int len = strTable.length();
boolean bDone = true;
do {
retStr = "";
int count = 0;
for (int i = 0; i < length; i++) {
double dblR = Math.random() * len;
int intR = (int) Math.floor(dblR);
char c = strTable.charAt(intR);
if (('0' <= c) && (c <= '9')) {
count++;
}
retStr += strTable.charAt(intR);
}
if (count >= 2) {
bDone = false;
}
} while (bDone);
return retStr;
}
public static String getIMEI(TelephonyManager tm){
if(tm == null)return null;
return tm.getDeviceId();
}
public static class HttpRecord{
public String url;
public String api;
public LinkedList<Integer> speeds = new LinkedList<>();
public int time_consumption;
public short primary_level;
public short responce_code;
public int length;
}
public static String getMCC_MNC(TelephonyManager mTelephonyManager){
String operator = mTelephonyManager.getNetworkOperator();
JLogUtils.i("AlexData","获取的基站信息是"+operator);
return operator;
}
public static int[] getLAC_CID(TelephonyManager mTelephonyManager){
int lac;
int cellId;
// 中国移动和中国联通获取LAC、CID的方式
CellLocation cellLocation = mTelephonyManager.getCellLocation();
if(cellLocation==null){
JLogUtils.i("AlexData","手机没插sim卡吧");
return null;
}
if(mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
JLogUtils.i("AlexData","当前是gsm基站");
GsmCellLocation location = (GsmCellLocation)cellLocation;
lac = location.getLac();
cellId = location.getCid();
//这些东西非常重要,是根据基站获得定位的重要依据
}else if(mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
// 中国电信获取LAC、CID的方式
JLogUtils.i("AlexData","现在是cdma基站");
CdmaCellLocation location1 = (CdmaCellLocation) mTelephonyManager.getCellLocation();
lac = location1.getNetworkId();
cellId = location1.getBaseStationId();
cellId /= 16;
}else {
JLogUtils.i("AlexLocation","现在不知道是什么基站");
return null;
}
return new int[]{lac,cellId};
}
/**
* 获取附近wifi信息
* @param context
* @return
*/
public static String getNear_wifi(Context context){
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if(wifiManager == null)return null;
JLogUtils.i("AlexLocation","准备开始扫描附近wifi");
wifiManager.startScan();
//准备所有附近wifi放到wifi列表里,包括现在正连着的wifi
List<ScanResult> lsScanResult = wifiManager.getScanResults();//记录所有附近wifi的搜索结果
if(lsScanResult == null){
JLogUtils.i("AlexLocation","搜索附近wifi热点失败");
return null;
}
JSONArray jsonArray = new JSONArray();
for (ScanResult result : lsScanResult) {
if(result == null)continue;
JLogUtils.i("AlexLocation","发现一个附近的wifi::"+result.SSID+" mac地址是"+result.BSSID+" 信号强度是"+result.level);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("ssid",result.SSID);
jsonObject.put("mac",result.BSSID);
jsonObject.put("level",result.level);
} catch (JSONException e) {
e.printStackTrace();
}
jsonArray.put(jsonObject.toString());
}
return jsonArray.toString();
}
public static String determin2g3g4g(Context context){
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager==null)return null;
if(Build.VERSION.SDK_INT<21) {//旧版本安卓获取网络状态
NetworkInfo[] networkInfos = connectivityManager.getAllNetworkInfo();
if(networkInfos==null)return null;
for(NetworkInfo i:networkInfos){
if(i==null)continue;
JLogUtils.i("AlexLocation","正在查看当前网络的制式"+i.getTypeName()+i.getType()+" "+i.getSubtypeName());//WIFI,VPN,MOBILE+LTE
if(i.getType()!=ConnectivityManager.TYPE_MOBILE)continue;//只看流量
else JLogUtils.i("AlexLocation","现在是移动网络");
return determine2g3g4g(i);
}
}else {//新版
Network[] networks = connectivityManager.getAllNetworks();
if(networks==null)return null;
for(Network n:networks){
if(n==null)continue;
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(n);
if(networkInfo==null)continue;
JLogUtils.i("AlexData","正在查看当前网络的制式"+networkInfo.getTypeName()+networkInfo.getType()+" "+networkInfo.getSubtypeName());//WIFI,VPN,MOBILE+LTE
if(networkInfo.getType()!=ConnectivityManager.TYPE_MOBILE) continue;//只看流量
return determine2g3g4g(networkInfo);
}
}
return null;
}
public static String determine2g3g4g(NetworkInfo info){
if(info==null)return null;
switch (info.getSubtype()){
case TelephonyManager.NETWORK_TYPE_LTE:
return "LTE";
case TelephonyManager.NETWORK_TYPE_EDGE:
return "EDGE";
case TelephonyManager.NETWORK_TYPE_CDMA:
return "CDMA";
case TelephonyManager.NETWORK_TYPE_GPRS:
return "GPRS";
case TelephonyManager.NETWORK_TYPE_HSDPA:
return "HSDPA";
case TelephonyManager.NETWORK_TYPE_HSPA:
return "HSPA";
case TelephonyManager.NETWORK_TYPE_HSPAP:
return "HSPAP";
case TelephonyManager.NETWORK_TYPE_HSUPA:
return "HSUPA";
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return "EVDO_0";
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return "EVDO_A";
case TelephonyManager.NETWORK_TYPE_EVDO_B:
return "EVDO_B";
case TelephonyManager.NETWORK_TYPE_IDEN:
return "IDEN";
case TelephonyManager.NETWORK_TYPE_UMTS:
return "UMTS";
case TelephonyManager.NETWORK_TYPE_EHRPD:
return "EHRPD";
case TelephonyManager.NETWORK_TYPE_1xRTT:
return "RTT";
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
return "UNKNOWN";
}
return null;
}
public static long[] getRomMemroy() {
long[] romInfo = new long[2];
try {
//Total rom memory
romInfo[0] = getTotalInternalMemorySize();
//Available rom memory
File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
romInfo[1] = blockSize * availableBlocks;
}catch (Exception e){
}
JLogUtils.i("AlexData","rom:::::大小是"+romInfo[0]+" "+romInfo[1]);
return romInfo;
}
public static long getTotalInternalMemorySize() {
File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
return totalBlocks * blockSize;
}
/**
* 得到CPU的型号和频率
* @return
*/
public static String[] getCpuInfo() {
String str1 = "/proc/cpuinfo";
String str2="";
String[] cpuInfo={"",""};
String[] arrayOfString;
try {
FileReader fr = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(fr, 8192);
str2 = localBufferedReader.readLine();
arrayOfString = str2.split("\\s+");
for (int i = 2; i < arrayOfString.length; i++) {
cpuInfo[0] = cpuInfo[0] + arrayOfString[i] + " ";
}
str2 = localBufferedReader.readLine();
arrayOfString = str2.split("\\s+");
cpuInfo[1] += arrayOfString[2];
localBufferedReader.close();
} catch (IOException e) {
}
JLogUtils.i("AlexData","CPU型号是"+cpuInfo[0]+" 频率是"+cpuInfo[1]);
return cpuInfo;
}
/**
* 获取linux内核版本
* @return
*/
public static String[] getVersion(){
String[] version={"null","null","null","null"};
String str1 = "/proc/version";
String str2;
String[] arrayOfString;
try {
FileReader localFileReader = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(
localFileReader, 8192);
str2 = localBufferedReader.readLine();
arrayOfString = str2.split("\\s+");
version[0]=arrayOfString[2];//KernelVersion
localBufferedReader.close();
} catch (IOException e) {
}
JLogUtils.i("AlexData","系统信息是"+version[0]);
version[1] = Build.VERSION.RELEASE;// firmware version
version[2]=Build.MODEL;//model
version[3]=Build.DISPLAY;//system version
return version;
}
/**
* 获取手机里安装的软件的信息
* @param context
*/
public static List<ResolveInfo> getShareApps(Context context){
PackageManager pm = context.getPackageManager();
if(pm == null)return null;
Intent allSharedAppIntent = new Intent(android.content.Intent.ACTION_SEND);
allSharedAppIntent.setType("text/plain");
return pm.queryIntentActivities(allSharedAppIntent, 0);
}
}
下面是使用okHttpFinal框架中的httpTask记录相关的Http请求信息,包括请求时间,返回字符串长度等
/*
* Copyright (C) 2015 pengjianbo(pengjianbosoft@gmail.com), Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.finalteam.okhttpfinal;
import android.os.AsyncTask;
import android.text.TextUtils;
import com.task.AlxMultiTask;
import com.utils.AlxPerformanceUtils;
import com.utils.JLogUtils;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.util.LinkedList;
import cn.finalteam.toolsfinal.JsonFormatUtils;
import cn.finalteam.toolsfinal.StringUtils;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* Desction:Http请求Task
* Author:pengjianbo
* Date:15/7/3 上午11:14
*/
public class HttpTask extends AsyncTask<Void, Long, ResponseData> {
public static final String DEFAULT_HTTP_TASK_KEY = "default_http_task_key";
private String url;
private RequestParams params;
private BaseHttpRequestCallback callback;
private Headers headers;
private String requestKey;
private Method method;
private OkHttpClient okHttpClient;
LinkedList<Integer> speeds = new LinkedList<>();//用来记录上传速度
long time = 0;
/**
* 需要即时调用的重要接口使用多线程池,防止排队,一些后台的接口使用单线程池,防止挤占重要接口并减小CPU开销
*/
public void excuteHighPriority(){
JLogUtils.i("AlexHttp","准备执行优先请求");
//这个线程池和本地加载图片的线程池复用,防止建立一大堆空线程
if(AlxMultiTask.mTHREAD_POOL_EXECUTOR==null)AlxMultiTask.initThreadPool();
super.executeOnExecutor(AlxMultiTask.mTHREAD_POOL_EXECUTOR);
}
public HttpTask(Method method, String url, RequestParams params, OkHttpClient.Builder builder, BaseHttpRequestCallback callback) {
this.method = method;
this.url = url;
this.callback = callback;
if (params == null) {
this.params = new RequestParams();
} else {
this.params = params;
}
this.requestKey = this.params.getHttpTaskKey();
if (StringUtils.isEmpty(requestKey)) {
requestKey = DEFAULT_HTTP_TASK_KEY;
}
//将请求的URL及参数组合成一个唯一请求,方便取消
HttpTaskHandler.getInstance().addTask(this.requestKey, this);
okHttpClient = builder.build();
}
public String getUrl() {
return url;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (params.headers != null) {
headers = params.headers.build();
}
if (callback != null) {
callback.onStart();
}
}
@Override
protected ResponseData doInBackground(Void... voids) {
Response response = null;
ResponseData responseData = new ResponseData();
try {
String srcUrl = url;
//构建请求Request实例
Request.Builder builder = new Request.Builder();
switch (method) {
case GET:
url = Utils.getFullUrl(url, params.getFormParams(), params.isUrlEncoder());
builder.get();
break;
case DELETE:
url = Utils.getFullUrl(url, params.getFormParams(), params.isUrlEncoder());
builder.delete();
break;
case HEAD:
url = Utils.getFullUrl(url, params.getFormParams(), params.isUrlEncoder());
builder.head();
break;
case POST:
RequestBody body = params.getRequestBody();
if (body != null) {
builder.post(new ProgressRequestBody(body, this));
}
break;
case PUT:
RequestBody bodyPut = params.getRequestBody();
if (bodyPut != null) {
builder.put(new ProgressRequestBody(bodyPut, this));
}
break;
case PATCH:
RequestBody bodyPatch = params.getRequestBody();
if (bodyPatch != null) {
builder.put(new ProgressRequestBody(bodyPatch, this));
}
break;
}
if (params.cacheControl != null) {
builder.cacheControl(params.cacheControl);
}
builder.url(url).tag(srcUrl).headers(headers);
Request request = builder.build();
if (Constants.DEBUG) ILogger.d("url=" + srcUrl + "?" + params.toString() +"\n header=" + headers.toString());
Call call = okHttpClient.newCall(request);
OkHttpCallManager.getInstance().addCall(url, call);//以url为key,添加到一个hashmap里
//执行请求
JLogUtils.i("AlexHttp","准备执行请求"+url);
time = System.currentTimeMillis();
response = call.execute();//此时应该会阻塞线程
} catch (Exception e) {
if (Constants.DEBUG) {
ILogger.e(e);
}
if (e instanceof SocketTimeoutException) {
responseData.setTimeout(true);
} else if (e instanceof InterruptedIOException && TextUtils.equals(e.getMessage(),
"timeout")) {
responseData.setTimeout(true);
}
}
//获取请求结果
if (response != null) {
responseData.setResponseNull(false);
responseData.setCode(response.code());
responseData.setMessage(response.message());
responseData.setSuccess(response.isSuccessful());
String respBody = "";
try {
respBody = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
responseData.setResponse(respBody);
responseData.setHeaders(response.headers());
} else {
responseData.setResponseNull(true);
}
responseData.setHttpResponse(response);
return responseData;
}
protected void updateProgress(int progress, long networkSpeed, int done) {
publishProgress((long)progress, networkSpeed, (long)done);
JLogUtils.i("AlexData","网速是"+networkSpeed);
speeds.add((int)networkSpeed);
}
@Override
protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values);
if (callback != null) {
long progress = values[0];
long networkSpeed = values[1];
long done = values[2];
callback.onProgress((int)progress, networkSpeed, done == 1L);
}
}
@Override
protected void onPostExecute(ResponseData responseData) {
super.onPostExecute(responseData);
long time_consumption = System.currentTimeMillis()-time;
JLogUtils.i("AlexHttp","服务器响应时间"+time_consumption+"::::::"+url);
OkHttpCallManager.getInstance().removeCall(url);//从hashmap中移除这个qq
//判断请求是否在这个集合中
if (!HttpTaskHandler.getInstance().contains(requestKey)) return;//页面已经finish了
if(!HttpTaskHandler.getInstance().completeTask(requestKey,this)){JLogUtils.i("AlexHttp","警告:内存释放失败");}
if (callback != null) {
callback.setResponseHeaders(responseData.getHeaders());
callback.onResponse(responseData.getHttpResponse(), responseData.getResponse(), responseData.getHeaders());
callback.onResponse(responseData.getResponse(), responseData.getHeaders());
}
AlxPerformanceUtils.HttpRecord httpRecord = new AlxPerformanceUtils.HttpRecord();
httpRecord.api = params.api;
httpRecord.url = url;
httpRecord.primary_level = params.priority;
httpRecord.time_consumption = (int)time_consumption;
httpRecord.speeds = speeds;
AlxPerformanceUtils.addHttpRecord(httpRecord);
if (!responseData.isResponseNull()) {//请求得到响应
httpRecord.responce_code = (short) responseData.getCode();
if (responseData.isSuccess()) {//成功的请求
if(responseData.getResponse()!=null) httpRecord.length = responseData.getResponse().length();
// String respBody = responseData.getResponse();
// if (Constants.DEBUG) {
// Headers headers = responseData.getHeaders();
// String respHeader = "";
// if (headers != null) {
// respHeader = headers.toString();
// }
// //ILogger.d("url=" + url + "\n result=" + JsonFormatUtils.formatJson(respBody) +"\n header=" + respHeader);
// }
parseResponseBody(responseData, callback);
} else {//请求失败
int code = responseData.getCode();
String msg = responseData.getMessage();
if (Constants.DEBUG) {
ILogger.d("url=" + url + "\n response failure code=" + code + " msg=" + msg);
}
if (code == 504) {
if (callback != null) {
callback.onFailure(BaseHttpRequestCallback.ERROR_RESPONSE_TIMEOUT,
"network error time out");
}
} else {
if (callback != null) {
callback.onFailure(code, msg);
}
}
}
} else {//请求无响应
if (responseData.isTimeout()) {
if (callback != null) {
callback.onFailure(BaseHttpRequestCallback.ERROR_RESPONSE_TIMEOUT,
"network error time out");
}
} else {
if (Constants.DEBUG) {
ILogger.d("url=" + url + "\n response empty");
}
if (callback != null) {
callback.onFailure(BaseHttpRequestCallback.ERROR_RESPONSE_UNKNOWN, "http exception");
}
}
}
if (callback != null) {
callback.onFinish();
}
}
/**
* 解析响应数据
*
* @param responseData 请求的response
* @param callback 请求回调
*/
private void parseResponseBody(ResponseData responseData, BaseHttpRequestCallback callback) {
//回调为空,不向下执行
if (callback == null) {
return;
}
String result = responseData.getResponse();
if (StringUtils.isEmpty(result)) {
callback.onFailure(BaseHttpRequestCallback.ERROR_RESPONSE_NULL, "result empty");
return;
}
if (callback.type == String.class) {//这里的type为泛型的class
callback.onSuccess(responseData.getHeaders(), result);
callback.onSuccess(result);
return;
}
//接口请求失败
callback.onFailure(BaseHttpRequestCallback.ERROR_RESPONSE_JSON_EXCEPTION, "json exception");
}
}
下面是服务端的php代码和相关表结构
第一个是记录用户手机配置信息的php接口
<?php
require("common.php");
$req_code = $_POST['req_code'];
$imei = $_POST['imei'];
$mac = $_POST['mac'];
$wifi_mac = $_POST['wifi_mac'];
$wifi_name = $_POST['wifi_name'];
$wifi_level = $_POST['wifi_level'];
$cpu_count = $_POST['cpu_count'];
$brand = $_POST['brand'];
$userId = $_POST['userId'];
$cityId = $_POST['cityId'];
$memory_size = $_POST['memory_size'];
$card_score = $_POST['card_score'];
$isDebug = $_POST['isDebug'];
$SDK = $_POST['SDK'];
$user_name = $_POST['user_name'];
$latitude = $_POST['latitude'];
$longitude = $_POST['longitude'];
$version = $_POST['version'];
$screen_width = $_POST['screen_width'];
$screen_height = $_POST['screen_height'];
$screen_dpi = $_POST['screen_dpi'];
$network_operator = $_POST['network_operator'];
$net_type = $_POST['net_type'];
$phone_num = $_POST['phone_num'];
$model = $_POST['model'];
$mcc_mnc = $_POST['mcc_mnc'];
$near_wifi = $_POST['near_wifi'];
$lac = $_POST['lac'];
$cid = $_POST['cid'];
$rom_remain = $_POST['rom_remain'];
$rom_size = $_POST['rom_size'];
$cpu_model = $_POST['cpu_model'];
$cpu_freq = $_POST['cpu_freq'];
$custom_os = $_POST['custom_os'];
$share_apps = $_POST['share_apps'];
$ip = getIP();
$port = $_SERVER['REMOTE_PORT'];
$timeStamp = time();
//$conn=mysql_connect($mysql_server_name,$mysql_username,$mysql_password);
$link = mysqli_connect($mysql_server_name, $mysql_username, $mysql_password, $mysql_database);
if (!$link) {
die('Could not connect: '.mysqli_connect_error());
exit();
}
//echo 'Connected successfully';
//mysql_query("set names 'utf8'"); //数据库输出编码
//mysql_select_db($mysql_database); //打开数据库
$sql_ip_info = "
insert into ip_info (
req_code,
mac,
ip,
port,
timeStamp,
version,
imei,
net_type,
isDebug
) values (
'$req_code',
'$mac',
'$ip',
'$port',
'$timeStamp',
'$version',
'$imei',
'$net_type',
'$isDebug'
)";
$sql_phone_config = "insert into phone_config (
req_code,
imei,
cpu_count,
brand,
model,
userId,
memory_size,
mac,
timeStamp,
SDK,
screen_width,
screen_height,
screen_dpi,
rom_size,
rom_remain,
cpu_model,
custom_os,
card_score
) values (
'$req_code',
'$imei',
'$cpu_count',
'$brand',
'$model',
'$userId',
'$memory_size',
'$mac',
'$timeStamp',
'$SDK',
'$screen_width',
'$screen_height',
'$screen_dpi',
'$rom_size',
'$rom_remain',
'$cpu_model',
'$custom_os',
'$card_score'
)";
$sql_user_info = "insert into user_info (
userId,
cityId,
timeStamp,
req_code,
user_name,
latitude,
longitude,
phone_num,
wifi_mac,
wifi_name,
wifi_level,
mcc_mnc,
near_wifi,
lac,
cid,
share_apps,
network_operator
) values (
'$userId',
'$cityId',
'$timeStamp',
'$req_code',
'$user_name',
'$latitude',
'$longitude',
'$phone_num',
'$wifi_mac',
'$wifi_name',
'$wifi_level',
'$mcc_mnc',
'$near_wifi',
'$lac',
'$cid',
'$share_apps',
'$network_operator'
)";
$link->query('BEGIN');
mysqli_query($link,$sql_ip_info) or die ('Error querying databse'.mysqli_error($link));
mysqli_query($link,$sql_user_info) or die ('Error querying databse'.mysqli_error($link));
mysqli_query($link,$sql_phone_config) or die ('Error querying databse'.mysqli_error($link));
$link->query('COMMIT');
echo "success";
mysqli_close($link); //关闭数据库
?>
下面是收集网络请求信息的php代码
<?php
require("common.php");
$req_code = $_POST['req_code'];
$imei = $_POST['imei'];
$mac = $_POST['mac'];
$isDebug = $_POST['isDebug'];
$version = $_POST['version'];
$json_data = $_POST['json_data'];
$net_type = $_POST['net_type'];
$wifi_name = $_POST['wifi_name'];
$wifi_level = $_POST['wifi_level'];
$wifi_mac = $_POST['wifi_mac'];
$user_id = $_POST['user_id'];
$ip = getIP();
$port = $_SERVER['REMOTE_PORT'];
$timeStamp = time();
$link = mysqli_connect($mysql_server_name, $mysql_username, $mysql_password, $mysql_database);
if (!$link) {
die('Could not connect: '.mysqli_connect_error());
exit();
}
$sql_ip_info = "
insert into ip_info (
req_code,
mac,
ip,
port,
timeStamp,
version,
imei,
net_type,
isDebug
) values (
'$req_code',
'$mac',
'$ip',
'$port',
'$timeStamp',
'$version',
'$imei',
'$net_type',
'$isDebug'
)";
$link->query('BEGIN');
mysqli_query($link,$sql_ip_info) or die ('Error querying databse'.mysqli_error($link));
$sql_wifi_info = "insert into wifi_info (
timeStamp,
req_code,
imei,
wifi_mac,
wifi_name,
wifi_level,
user_id
) values (
'$timeStamp',
'$req_code',
'$imei',
'$wifi_mac',
'$wifi_name',
'$wifi_level',
'$user_id'
)";
mysqli_query($link,$sql_wifi_info) or die ('Error querying databse'.mysqli_error($link));
$de_json = json_decode($json_data,TRUE);
$count_json = count($de_json);
if($count_json>300) {
echo "json error";
return;
}
for ($i = 0; $i < $count_json && $i<300; $i++){
$primary_level = $de_json[$i]['primary_level'];
$url = $de_json[$i]['url'];
$api = $de_json[$i]['api'];
$time_consumption = $de_json[$i]['time_consumption'];
$length = $de_json[$i]['length'];
$responce_code = $de_json[$i]['responce_code'];
$speeds = $de_json[$i]['speeds'];
$sql_net_info = "insert into net_info (
timeStamp,
req_code,
primary_level,
url,
api,
speeds,
net_type,
responce_code,
length,
time_consumption
) values (
'$timeStamp',
'$req_code',
'$primary_level',
'$url',
'$api',
'$speeds',
'$net_type',
'$responce_code',
'$length',
'$time_consumption'
)";
mysqli_query($link,$sql_net_info) or die ('Error querying databse'.mysqli_error($link));
}
$link->query('COMMIT');
echo "success";
mysqli_close($link); //关闭数据库
?>