編輯:關於Android編程
前言
作為一名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下面是使用okHttpFinal框架中的httpTask記錄相關的Http請求信息,包括請求時間,返回字符串長度等scoreArray = null; public static LinkedList 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 >(){ @Override protected List doInBackground(Void... params) { List apps = getShareApps(APPLICATION); return apps; } @Override protected void onPostExecute(List 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 (); 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 (){ @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*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 (){ @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 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 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 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); } }
/* * Copyright (C) 2015 pengjianbo([email protected]), 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下面是服務端的php代碼和相關表結構{ 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 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接口
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); //關閉數據庫 ?>
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); //關閉數據庫 ?>
Android的framework層都是由c++來實現的 大家都知道c++最令人頭痛的莫過於內存洩漏了 ,如果是一個人開發還好 當new出來一個對象後 應該會記得dele
在 Android 3.0(API level 11) 之後,Google 為 Android添加了屬性動畫(Property Animation),該動畫系統是一個強大
最近項目裡要做一個簡單的曲線圖來標識數據,開始以為很簡單,android已經有那麼多的開源圖表庫了,什麼achartenginee,hellochart,mpandroi
AndroidProgressLayout實現為界面添加圓形進度條。調用setprogress()方法顯示和隱藏進度條在Android的開發中,往往有這種需求,比如一個耗