Android 获取应用的点击次数和使用时长

1. 前言

接到一个新的需求:需要统计在定制设备中的用户使用情况,包括获取相应时间内设备中每个应用的流量使用情况,每个应用的使用时长和点击次数等等等。

2. 获取流量

android系统提供了TrafficStats可以获取流量,网上资料很多,这里只给出我的工具类的代码:

    //接受总量
    public static long getTotalRex(int uid) {
        return TrafficStats.getUidRxBytes(uid);
    }


    //发送总量
    public static long getTotalTrx(int uid) {
        return TrafficStats.getUidTxBytes(uid);
    }

    //接受总的手机流量  下载
    public static long getMobileRexTotalBytes() {
        return TrafficStats.getMobileRxBytes();
    }

    //发送总的手机流量  上传
    public static long getMobileTrxTotalBytes() {
        return TrafficStats.getMobileTxBytes();//获取手机3g/2g网络上传的总流量
    }

    // 手机下载wifi
    public static long getWifiRexTotalBytes() {
        return TrafficStats.getTotalRxBytes() - getMobileRexTotalBytes();
    }


    // 手机上传wifi
    public static long getWifiTrxTotalBytes() {
        return TrafficStats.getTotalTxBytes() - getMobileTrxTotalBytes();
    }

然后根据逻辑写相应的代码;

这里主要说说应用的点击次数和使用时长的实现;

3. 获取使用时长和点击次数

Android5.0之前,通过PkgUsageStats这个类可以统计到应用的使用情况,但这些类在SDK不公开。这里提供一种我们的解决方案,仅供参考:

sdk上级目录/sdk/platforms/android-19/data/layoutlib.jar

将layoutlib.jar使用User Library的方式(AndroidStudio可以使用Provided添加依赖),然后就可以使用这些类,下面给出具体实现:


    //  获取使用时长
    public static long getUseDuration(String pkgName) {

        // 注意适配性问题,无法找到该类
        try{
            com.android.internal.app.IUsageStats mUsageStatsService = com.android.internal.app.IUsageStats.Stub
                    .asInterface(ServiceManager.getService("usagestats"));
            PkgUsageStats[] stats;
            try {
                stats = mUsageStatsService.getAllPkgUsageStats();
            } catch (Exception e) {
                LogUtil.d("no permission get use duration");
                e.printStackTrace();
                return 0;
            }
            if (stats == null) {
                return 0;
            }

            for (PkgUsageStats ps : stats) {
                if (ps.packageName.equals(pkgName)) {
                    return ps.usageTime;
                }
            }
        }catch(Exception e){

        }
        return 0;
    }




    // 获取使用次数
    public static long getUseTime(String pkgName) {
        // 注意适配性问题,无法找到该类
        try{
            com.android.internal.app.IUsageStats mUsageStatsService = com.android.internal.app.IUsageStats.Stub
                    .asInterface(ServiceManager.getService("usagestats"));
            PkgUsageStats[] stats = null;
            try {
                stats = mUsageStatsService.getAllPkgUsageStats();
            } catch (Exception e) {
                LogUtil.d("no permission get use duration");
                e.printStackTrace();
                return 0;
            }
            if (stats == null) {
                return 0;
            }
            for (PkgUsageStats ps : stats) {
                if (ps.packageName.equals(pkgName)) {
                    return ps.launchCount;
                }
            }
        }catch(Exception e){

        }
        return 0;
    }

走一下IUsageStats 源码,从getAllPkgUsageStats()方法,将带到的信息保存到PkgUsageStats[]数组中并返回;

……省略代码
private static final java.lang.String DESCRIPTOR = "com.android.internal.app.IUsageStats";
……省略代码

private android.os.IBinder mRemote;
More ...Proxy(android.os.IBinder remote) {
    mRemote = remote;
}

……省略代码

@Override 
public com.android.internal.os.PkgUsageStats[] More ...getAllPkgUsageStats() throws android.os.RemoteException{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    com.android.internal.os.PkgUsageStats[] _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_getAllPkgUsageStats, _data, _reply, 0);
        _reply.readException();
        //信息的数组
        _result = _reply.createTypedArray(com.android.internal.os.PkgUsageStats.CREATOR);
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
    }
}

内部实现都是Parcel 和 Parcelable(之后补充这块知识),简单看一下createTypedArray()

public final  T[] More ...createTypedArray(Parcelable.Creator c) {
    int N = readInt();
    if (N < 0) {
        return null;
    }
    T[] l = c.newArray(N);
    for (int i=0; iif (readInt() != 0) {
            l[i] = c.createFromParcel(this);
        }
    }
    return l;
}

//调用native方法
public final long More ...readInt() {
    return nativeReadInt(mNativePtr);
}

同流量一样,Linux 系统下所有的信息都是以文件的形式存在的,所以应用程序的使用信息也会被保存在操作系统的文件中,将信息从文件中读出,然后就将其用Parcelable序列化的过程;最后在看一下PkgUsageStats类源码:

public class More ...PkgUsageStats implements Parcelable {
    //包名
    public String packageName;
    //启动次数
    public int launchCount;
    //使用时长
    public long usageTime;
    ……省略大量代码
}

根据自身的业务逻辑,判断包名,获取每个应用的点击次数和使用时长;

以上只能保证写代码的时候不会出现错误提示,但是运行起来并不会带到想要的效果,还需要一下几步:

  1. 在应用程序的AndroidManifest.xml中的manifest节点中加入”android:sharedUserId=”android.uid.system”这个属性。
  2. 使用目标系统的platform密钥来重新给apk文件重新签名。

如果有相应的文件,使用如下批处理,a.apk表示你自己的apk,b.apk表示系统签名后生成的新apk

java -jar signapk.jar -w platform.x509.pem platform.pk8 a.apk b.apk

pause

之后将新生成的文件放到系统目录即可统计应用的使用次数和使用时长,具体情况视代码逻辑而定。

参考:

http://www.cnblogs.com/chenbin7/archive/2013/01/10/2854768.html
http://blog.csdn.net/pierce0young/article/details/22292603

4. Android5.0以上系统获取应用的点击次数和使用时长

由于我们手中只有一套android4.4相关品牌手机的系统签名,所以开发测试的时候都没有什么问题,定制机器中有一批是android5.1的系统,这批机器在我们的后台一直看不到数据,开始我以为是厂商没有用系统签名,后来拿到机器查看日志发现报错信息是找不到com.android.internal.app.IUsageStats 类,没用系统签名只提示没有相应的权限;后来对比system/priv-app/Settings.apk后发现确实用了系统签名。

于是又在开始找关于android5.0获取应用的信息的资料,发现android5.0废弃了com.android.internal.app.IUsageStats,用的是android.app.usage.UsageStats,这里直接给出实现的代码:


    //使用时长
    public static long getUseDurationWithL(List usageStatsList, String pkName) {
        for (UsageStats stat : usageStatsList){
            if(stat.getPackageName().equals(pkName)){
                return stat.getTotalTimeInForeground();
            }
        }
        return 0;
    }

通过包名,获取该应用的使用时长,直接有getTotalTimeInForeground()方法,可是点击次数没有直接的方法可用,试着在androidstudio中打get提示的也没有相关的方法,后来直接看源码,发现这一句代码:

     /**
     * {@hide}
     */
    public int mLaunchCount;

不管什么原因,使该字段{@hide},先直接拿来用,解决问题再说:

    //点击次数
    public static long getUseTimeWithL(List usageStatsList, String pkName) {
        for (UsageStats stat : usageStatsList){

            if(stat.getPackageName().equals(pkName)){

                int count = 0;

                try {
                    Field field = stat.getClass().getDeclaredField("mLaunchCount");
                    count = field.getInt(stat);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

                return count;
            }
        }
        return 0;
    }

最关键的一步,就是如何得到List,还是先直接给代码:

public class UsageStatsUtil {

    public static List getUsageStatsList(Context context){

        if(null == context) return null;

        UsageStatsManager usm = getUsageStatsManager(context);

        Calendar beginCal = Calendar.getInstance();
        beginCal.set(Calendar.DATE, 31);
        beginCal.set(Calendar.MONTH, 12);
        beginCal.set(Calendar.YEAR, 1970);

        long startTime = beginCal.getTimeInMillis();
        long endTime = System.currentTimeMillis();

        final List usageStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, endTime);

        if(usageStatsList.isEmpty()){
            LogUtil.d("usage stats list is null");
        }else{
            LogUtil.d("usage stats list size = " + usageStatsList.size());
        }

        return usageStatsList;
    }


    private static UsageStatsManager getUsageStatsManager(Context context){
        UsageStatsManager usm = (UsageStatsManager) context.getSystemService("usagestats");
        return usm;
    }
}

通过UsageStatsManager 得到,其中的queryUsageStats(int, long, long)方法表示获取给定时间范围的应用程序使用统计数据:

public List More ...queryUsageStats(int intervalType, long beginTime, long endTime) {
      try {
          @SuppressWarnings("unchecked")
          ParceledListSlice slice = mService.queryUsageStats(intervalType, beginTime,
                  endTime, mContext.getOpPackageName());
          if (slice != null) {
              return slice.getList();
          }
      } catch (RemoteException e) {
          // fallthrough and return null.
      }
      return Collections.emptyList();
}

这里主要参考:

http://stackoverflow.com/questions/26431795/how-to-use-usagestatsmanager

还需要注意两点

  1. 在AndroidManifest.xml文件中添加权限 < uses-permission android:name=”android.permission.PACKAGE_USAGE_STATS”
    tools:ignore=”ProtectedPermissions” />
  2. 保证 设置->安全->有权查看使用情况的应用 自己的应用是打开的状态

Android5.0 是不需要系统签名的,只要保证以上 有权查看使用情况的应用 中打开就行;

你可能感兴趣的:(Android相关)