Android应用的电量消耗和优化的策略

摘要:
对于Android移动应用程序的开发者来说,功耗的控制一直是一个大问题。为了控制功耗,我们必须有工具或方法来准确定位应用程序的功耗。接下来,我们来分析一下如何计算Android应用程序的功耗。Android的内置设置中有一个功率计算界面,如下图所示:<忽略_ js_ op>让我们看看它是如何实现的:在Android框架中,有一个专门负责电力统计的s
 对于Android移动应用的开发者来说,耗电量的控制一直是个老大难问题。
     我们想要控制耗电量,必须要有工具或者方法比较准确的定位应用的耗电情况。下面,我们先来分析下如何计算android应用的耗电量。
   在android自带的设置里面有电量计算的界面,如下图:
<ignore_js_op>Android应用的电量消耗和优化的策略第1张
   我们看下是如何实现的:​
   在android framework里面有专门负责电量统计的Service:BatteryStatsSerive。这个Service在ActivityManagerService中创建,代码如下:
1mBatteryStatsService = new BatteryStatsService(new File(systemDir, 'batterystats.bin').toString());


其他的模块比如WakeLock和PowerManagerService会向BatteryStatsService喂数据,数据是存放到系统目录batterystats.bin文件,然后交于BatteryStatsImpl这个数据分析器来进行电量数据的分析,系统的设置就是这样得到电量的统计信息的。
    拿到相关的数据后,电量的计算又是如何得出的呢?这里用到了如下的计算公式:
    应用运行总时间 =  应用在Linux内核态运行时间 +  应用在Linux用户态运行时间
    CPU工作总时间 = 软件运行期间CPU每个频率下工作的时间之和比例
    应用消耗的电量 = CPU每个频率等级下工作的时间比例/CPU工作总时间 * 应用运行总时间
* 不同频率下消耗的电量 + 数据传输消耗的电量(WI-FI或者移动网络)+ 使用所有传感器消耗的电量 + 唤醒锁消耗的电量。
   相应的代码片段如下:
001private void processAppUsage() {

002    SensorManager sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);

003    final int which = mStatsType;

004    final int speedSteps = mPowerProfile.getNumSpeedSteps();

005    final double[] powerCpuNormal = new double[speedSteps];

006    final long[] cpuSpeedStepTimes = new long[speedSteps];

007    for (int p = 0; p < speedSteps; p++) {

008        powerCpuNormal[p] = mPowerProfile.getAveragePower

009        PowerProfile.POWER_CPU_ACTIVE, p);

010    }

011    final double averageCostPerByte = getAverageDataCost();

012    long uSecTime = mStats.computeBatteryRealtime(

013    SystemClock.elapsedRealtime() * 1000, which);

014    mStatsPeriod = uSecTime;

015     

016        SparseArray<? extends Uid> uidStats = mStats.getUidStats();

017    final int NU = uidStats.size();

018    for (int iu = 0; iu < NU; iu++) {

019        Uid u = uidStats.valueAt(iu);

020        double power = 0;

021        double highestDrain = 0;

022        String packageWithHighestDrain = null;     

023        Map<String, ? extends BatteryStats.Uid.Proc> proce                ssStats = u.getProcessStats();

024        long cpuTime = 0;

025        long cpuFgTime = 0;

026        long gpsTime = 0;

027        if (processStats.size() > 0) {

028        // Process CPU time

029            for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent : processStats.entrySet()) {

030        if (DEBUG)

031        Log.i(TAG, 'Process name = ' + ent.getKey());

032        Uid.Proc ps = ent.getValue();

033        final long userTime = ps.getUserTime(which);

034        final long systemTime = ps.getSystemTime(which);

035        final long foregroundTime = ps.getForegroundTime(which);

036            cpuFgTime += foregroundTime * 10; // convert to millis

037        final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis

038        int totalTimeAtSpeeds = 0;

039        // Get the total first

040        for (int step = 0; step < speedSteps; step++) {

041    cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);

042    totalTimeAtSpeeds += cpuSpeedStepTimes[step];

043        }

044    if (totalTimeAtSpeeds == 0)

045        totalTimeAtSpeeds = 1;

046    // Then compute the ratio of time spent at each speed

047    double processPower = 0;

048    for (int step = 0; step < speedSteps; step++) {

049    double ratio = (double) cpuSpeedStepTimes[step]/ totalTimeAtSpeeds;

050        processPower += ratio * tmpCpuTime* powerCpuNormal[step];

051    }

052    cpuTime += tmpCpuTime;

053    power += processPower;

054    if (highestDrain < processPower) {

055        highestDrain = processPower;

056        packageWithHighestDrain = ent.getKey();

057    }

058 

059    }

060     

061    }

062        }

063    cpuTime = cpuFgTime; // Statistics may not have been gathered yet.

064            }

065    power /= 1000;

066 

067    // Add cost of data traffic

068    long tcpBytesReceived = u.getTcpBytesReceived(mStatsType);

069        long tcpBytesSent = u.getTcpBytesSent(mStatsType);

070        power += (tcpBytesReceived + tcpBytesSent) * averageCostPerByte;

071            

072        // Process Sensor usage

073        Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();

074    for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry : sensorStats.entrySet()) {

075    Uid.Sensor sensor = sensorEntry.getValue();

076    int sensorType = sensor.getHandle();

077    BatteryStats.Timer timer = sensor.getSensorTime();

078    long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;

079    double multiplier = 0;

080    switch (sensorType) {

081        case Uid.Sensor.GPS:

082        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);

083        gpsTime = sensorTime;

084        break;

085    default:

086        android.hardware.Sensor sensorData = sensorManager

087        .getDefaultSensor(sensorType);

088        if (sensorData != null) {

089            multiplier = sensorData.getPower();

090            }

091        }

092    }

093    power += (multiplier * sensorTime) / 1000;

094    }

095 

096    // Add the app to the list if it is consuming power

097    if (power != 0) {

098    BatterySipper app = new BatterySipper(packageWithHighestDrain,0, u, new double[] { power });

099    app.cpuTime = cpuTime;

100    app.gpsTime = gpsTime;

101    app.cpuFgTime = cpuFgTime;

102    app.tcpBytesReceived = tcpBytesReceived;

103    app.tcpBytesSent = tcpBytesSent;

104    mUsageList.add(app);

105    }

106    if (power > mMaxPower)

107          mMaxPower = power;

108    mTotalPower += power;

109    if (DEBUG)

110    Log.i(TAG, 'Added power = ' + power);

111        }

112    }


       通过代码我们看到,每个影响电量消耗的base值其实是事先配置好的,在系统res下power_profile.xml,所以通过这个方式计算出来的电量消耗值也只能作为一个经验值或者是参考值,和物理上的耗电值应该还是有所偏差的。
       那我们还能用啥方式去比较准确的去获取耗电量呢?我们想到了曹冲称象的故事,可以用差值的方式进行尝试。在相同时间单位内,在没有安装应用的手机上和安装了应用的手机上记录耗电量,取差值为该应用的耗电量。在测试过程中注意几点,保证该手机相对“干净”,开始前需要结束所有的后台程序,将手机电量冲满,保证每次的起步点相同,这里推荐电量监控程序Battery Monitor Widget,这款软件功能比较强大,可以看到历史的电量变化。这两种测试方式可以同时使用,互为印证,已经应用到在Agoo Android SDK的测试中。
       拿到电量数据后,紧接着就是如何优化电量的问题了。通过电量的计算公式我们可以看到影响电量的因子无非就是CPU的时间和网络数据以及Wakelock,GPS的使用。
       在09年Google IO大会Jeffrey Sharkey的演讲(Coding for Life — Battery Life, That Is)中就探讨了这个问题,指出android应用的耗电主要在以下三个方面:
  • 大数据量的传输。
  • 不停的在网络间切换。
  • 解析大量的文本数据。
      并提出了相关的优化建议:
  •   在需要网络连接的程序中,首先检查网络连接是否正常,如果没有网络连接,那么就不需要执行相应的程序。
  •   使用效率高的数据格式和解析方法,推荐使用JSON和Protobuf。
  •   目在进行大数据量下载时,尽量使用GZIP方式下载。
  •   其它:回收java对象,特别是较大的java对像,使用reset方法;对定位要求不是太高的话尽量不要使用GPS定位,可能使用wifi和移动网络cell定位即可;尽量不要使用浮点运算;获取屏幕尺寸等信息可以使用缓存技术,不需要进行多次请求;使用AlarmManager来定时启动服务替代使用sleep方式的定时任务。




作为app开发者,或许很少有人会注意app对电量的损耗,但是用户对电量可是很敏感的,app做好电量损耗的优化会为自己的app加分不少。
如果是一个好的负责任的开发者,就应该限制app对电量的影响,当没有网络连接的时候,禁用后台服务更新,当电池电量低的时候减少更新的频率,确保自己的app对电池的影响降到最低。当电池充电或者电量比较饱和时,可以最大限度的发挥app的刷新率
1<receiver android:name=".PowerConnectReceiver">

2  <intent-filter>

3    <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>

4    <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>

5  </intent-filter>

6</receiver>




01public class PowerConnectionReceiver extends BroadcastReceiver {

02    @Override

03    public void onReceive(Context context, Intent intent) {

04        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

05        boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||

06                            status == BatteryManager.BATTERY_STATUS_FULL;

07     

08        int chargeFlag = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

09        boolean usbCharge = chargeFlag == BATTERY_PLUGGED_USB;

10        boolean acCharge = chargeFlag == BATTERY_PLUGGED_AC;

11    }

12}




1//获取程序是否充电

2 

3int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS,-1);

4 

5boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||status == BatteryManager.BATTERY_STATUS_FULL;




1// 充电方式,usb还是电源

2int chargeFlag = battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

3boolean usbCharge = chargeFlag == BATTERY_PLUGGED_USB;

4boolean acCharge = chargeFlag == BATTERY_PLUGGED_AC;


1不断的检测电量也会影响电池的使用时间,我们可以这样做




1<receiver android:name=".BatteryLevelReceiver">

2<intent-filter>  

3<action android:name="android.intent.action.ACTION_BATTERY_LOW"/>   <actionandroid:name="android.intent.action.ACTION_BATTERY_OKAY"/>   </intent-filter>

4</receiver>




当电量低或者满时会触发
有时间再写确定和监测连接状态




测试结论:
1)灭屏待机最省电:
a)任何App包括后台Service应该尽可能减少唤醒CPU的次数,比如IM类业务的长连接心跳、QQ提醒待机闹钟类业务的alarm硬时钟唤醒要严格控制;
b)每次唤醒CPU执行的代码应该尽可能少,从而让CPU迅速恢复休眠,比如申请wake lock的数量和持有时间要好好斟酌;
2)Wi-Fi比蜂窝数据,包括2G(GPRS)、3G更省电:
a)尽量在Wi-Fi下传输数据,当然这是废话,不过可以考虑在有Wi-Fi的时候做预加载,比如应用中心的zip包、手Q web类应用的离线资源等;
b)非Wi-Fi下,尽量减少网络访问,每一次后台交互都要考虑是否必须。虽然WiFi接入方式已经占到移动互联网用户的50%,但是是有些手机设置为待机关闭WiFi连接,即便有Wi-Fi信号也只能切换到蜂窝数据;
测试分析:
1)灭屏的情况:
a)灭屏待机,CPU处于休眠状态,最省电(7mA);
b)灭屏传输,CPU被激活,耗电显著增加,即便是处理1K的心跳包,电量消耗也会是待机的6倍左右(45mA);
c)灭屏传输,高负载download的时候WiFi最省电(70mA),3G(270mA)和2G(280mA)相当,是WiFi的4倍左右;
Android应用的电量消耗和优化的策略第2张
2)亮屏的情况:
a)亮屏待机,CPU处于激活状态,加上屏幕耗电,整机电量消耗不小(140mA);
b)亮屏传输,如果只是处理1K的心跳包,耗电增加不多(150mA),即便是很大的心跳包(64K),消耗增加也不明显(160mA);
c)亮屏传输,高负载download的时候WiFi最省电(280mA),3G(360mA)和2G(370mA)相当,是WiFi的1.3倍左右;
Android应用的电量消耗和优化的策略第3张
3)Alarm唤醒频繁会导致待机耗电增加:
手机灭屏后会进入待机状态,这时CPU会进入休眠状态。Android的休眠机制介绍的文章很多,这里引用一段网络文章:
Early suspend是android引进的一种机制,这种机制在上游备受争议,这里 不做评论。这个机制作用在关闭显示的时候,在这个时候,一些和显示有关的 设备,比如LCD背光,比如重力感应器,触摸屏,这些设备都会关掉,但是系统可能还是在运行状态(这时候还有wake lock)进行任务的处理,例如在扫描SD卡上的文件等.在嵌入式设备中,背光是一个很大的电源消耗,所以android会加入这样一种机制.
Late Resume是和suspend配套的一种机制,是在内核唤醒完毕开始执行的.主要就是唤醒在Early Suspend的时候休眠的设备.
Wake Lock在Android的电源管理系统中扮演一个核心的角色. Wake Lock是一种锁的机制,只要有人拿着这个锁,系统就无法进入休眠,可以被用户态程序和内核获得.这个锁可以是有超时的或者是没有超时的,超时的锁会在时间过去以后自动解锁.如果没有锁了或者超时了,内核就会启动休眠的那套机制来进入休眠.
当用户写入mem或者standby到/sys/power/state中的时候, state_store()会被调用,然后Android会在这里调用request_suspend_state()而标准的Linux会在这里进入enter_state()这个函数.如果请求的是休眠,那么early_suspend这个workqueue就会被调用,并且进入early_suspend
简单的说,当用户按power键,使得手机进入灭屏休眠状态,Android系统其实是做了前面说的一些工作:关闭屏幕、触摸屏、传感器、dump当前用户态和内核态程序运行上下文到内存或者硬盘、关闭CPU供电,当然为了支持语音通讯,modern等蜂窝信令还是工作的。
这种情况下,应用要唤醒CPU,只有两种可能:
a)通过服务器主动PUSH数据,通过网络设备激活CPU;
b)设置alarm硬件闹钟唤醒CPU;
这里我们重点分析第二种情况。首先来看看什么是alarm硬件闹钟。Google官方提供的解释是:Android提供的alarm services可以帮助应用开发者能够在将来某一指定的时刻去执行任务。当时间到达的时候,Android系统会通过一个Intent广播通知应用去完成这一指定任务。即便CPU休眠,也不影响alarm services的服务,这种情况下可以选择唤醒CPU。
显然唤醒CPU是有电量消耗的,CPU被唤醒的次数越多,耗电量会越大。现在很多应用为了维持心跳、拉取数据、主动PUSH会不同程度地注册alarm服务,导致Android系统被频繁唤醒。这就是为什么雷军说Android手机在安装了TOP100的应用后,待机时间会大大缩短的重要原因。
比较简单评测CPU唤醒次数的方法是看dumpsys alarm,这里会详细记录从开机到当前的各个进程和服务唤醒CPU的次数和时间。通过对比唤醒次数和唤醒时间可以帮助我们分析后台进程和服务的耗电情况。Dumpsys alarm的输出看起来像这样:
Android应用的电量消耗和优化的策略第4张
其中544代表唤醒次数,38684ms代表唤醒时间。
4)Wake locks持有时间过长会导致耗电增加:
Wake locks是一种锁机制,有些文献翻译成唤醒锁。简单说,前面讲的灭屏CPU休眠还需要做一个判断,就是看是否还有任何应用持有wake locks。如果有,CPU将不会休眠。有些应用不合理地申请wake locks,或者申请了忘记释放,都会导致手机无法休眠,耗电增加。
原始数据:
测试方法:硬件设备提供稳压电源替代手机电池供电,在不同场景下记录手机平均电流。
测试设备:Monsoon公司的Power Monitor TRMT000141
测试机型:Nexus One
Android应用的电量消耗和优化的策略第5张
灭屏benchmark(CPU进入休眠状态):7mA
Android应用的电量消耗和优化的策略第6张
灭屏WiFi:70 mA
Android应用的电量消耗和优化的策略第7张
灭屏3G net:270 mA
Android应用的电量消耗和优化的策略第8张
灭屏2G net GPRS:280mA
Android应用的电量消耗和优化的策略第9张
亮屏benchmark:140mA
Android应用的电量消耗和优化的策略第10张
亮屏Wi-Fi:280mA
Android应用的电量消耗和优化的策略第11张
亮屏3G net:360mA
Android应用的电量消耗和优化的策略第12张
亮屏2G:370mA
Android应用的电量消耗和优化的策略第13张
亮屏待机:140mA
Android应用的电量消耗和优化的策略第14张
亮屏Wi-Fi ping 1024包:150mA
Android应用的电量消耗和优化的策略第15张
亮屏Wi-Fi ping 65500包:160mA
Android应用的电量消耗和优化的策略第16张
灭屏 屏1024:45mA
Android应用的电量消耗和优化的策略第17张
灭屏ping 65500:55mA
Android应用的电量消耗和优化的策略第18张
关闭所有数据网络待机:7mA
显而易见,大部分的电都消耗在了网络连接、GPS、传感器上了。
简单的说也就是主要在以下情况下耗电比较多:
1、 大数据量的传输。
2、 不停的在网络间切换。
3、 解析大量的文本数据。
那么我们怎么样来改善一下我们的程序呢?
1、 在需要网络连接的程序中,首先检查网络连接是否正常,如果没有网络连接,那么就不需要执行相应的程序。
检查网络连接的方法如下:

ConnectivityManager mConnectivity;  

TelephonyManager mTelephony;  

……  

// 检查网络连接,如果无网络可用,就不需要进行连网操作等  

NetworkInfo info = mConnectivity.getActiveNetworkInfo();  

if (info == null ||  

        !mConnectivity.getBackgroundDataSetting()) {  

        return false;  

}  

//判断网络连接类型,只有在3G或wifi里进行一些数据更新。  

int netType = info.getType();  

int netSubtype = info.getSubtype();  

if (netType == ConnectivityManager.TYPE_WIFI) {  

    return info.isConnected();  

} else if (netType == ConnectivityManager.TYPE_MOBILE  

        && netSubtype == TelephonyManager.NETWORK_TYPE_UMTS  

        && !mTelephony.isNetworkRoaming()) {  

    return info.isConnected();  

} else {  

    return false;  

}  

2、 使用效率高的数据格式和解析方法。
通过测试发现,目前主流的数据格式,使用树形解析(如DOM)和流的方式解析(SAX)对比情况如下图所示:

很明显,使用流的方式解析效率要高一些,因为DOM解析是在对整个文档读取完后,再根据节点层次等再组织起来。而流的方式是边读取数据边解析,数据读取完后,解析也就完毕了。
在数据格式方面,JSON和Protobuf效率明显比XML好很多,XML和JSON大家都很熟悉,Protobuf是Google提出的,一种语言无关、平台无关、扩展性好的用于通信协议、数据存储的结构化数据串行化方法。有兴趣的可以到官方去看看更多的信息。
从上面的图中我们可以得出结论就是尽量使用SAX等边读取边解析的方式来解析数据,针对移动设备,最好能使用JSON之类的轻量级数据格式为佳。
3、 目前大部门网站都支持GZIP压缩,所以在进行大数据量下载时,尽量使用GZIP方式下载。
使用方法如下所示:

import java.util.zip.GZIPInputStream;  

HttpGet request =  

    new HttpGet("http://example.com/gzipcontent"); 

HttpResponse resp =  

    new DefaultHttpClient().execute(request);  

HttpEntity entity = response.getEntity();  

InputStream compressed = entity.getContent();  

InputStream rawData = new GZIPInputStream(compressed);  

使用GZIP压缩方式下载数据,能减少网络流量,下图为使用GZIP方式获取包含1800个主题的RSS对比情况。

4、 其它一些优化方法:
回收java对象,特别是较大的java对像
XmlPullParserFactory and BitmapFactory   

Matcher.reset(newString) for regex  

StringBuilder.sentLength(0)  

对定位要求不是太高的话尽量不要使用GPS定位,可能使用wifi和移动网络cell定位即可。GPS定位消耗的电量远远高于移动网络定位。
尽量不要使用浮点运算。
获取屏幕尺寸等信息可以使用缓存技术,不需要进行多次请求。
很多人开发的程序后台都会一个service不停的去服务器上更新数据,在不更新数据的时候就让它sleep,这种方式是非常耗电的,通常情况下,我们可以使用AlarmManager来定时启动服务。如下所示,第30分钟执行一次。
AlarmManager am = (AlarmManager)  

        context.getSystemService(Context.ALARM_SERVICE);  

Intent intent = new Intent(context, MyService.class);  

PendingIntent pendingIntent =  

        PendingIntent.getService(context, 0, intent, 0);  

long interval = DateUtils.MINUTE_IN_MILLIS * 30;  

long firstWake = System.currentTimeMillis() + interval;  

am.setRepeating(AlarmManager.RTC,firstWake, interval, pendingIntent);  

最后一招,在运行你的程序前先检查电量,电量太低,那么就提示用户充电之类的,使用方法:

public void onCreate() {  

    // Register for sticky broadcast and send default  

    registerReceiver(mReceiver, mFilter);  

    mHandler.sendEmptyMessageDelayed(MSG_BATT, 1000);  

}  

IntentFilter mFilter =  

        new IntentFilter(Intent.ACTION_BATTERY_CHANGED);  

BroadcastReceiver mReceiver = new BroadcastReceiver() {  

    public void onReceive(Context context, Intent intent) {  

        // Found sticky broadcast, so trigger update  

        unregisterReceiver(mReceiver);  

        mHandler.removeMessages(MSG_BATT);  

        mHandler.obtainMessage(MSG_BATT, intent).sendToTarget();  

    }  

}; 

免责声明:文章转载自《Android应用的电量消耗和优化的策略》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇《游戏引擎架构》笔记三python之判断和循环下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

图解ARP协议(二)ARP攻击原理与实践

一、ARP攻击概述 在上篇文章里,我给大家普及了ARP协议的基本原理,包括ARP请求应答、数据包结构以及协议分层标准,今天我们继续讨论大家最感兴趣的话题:ARP攻击原理是什么?通过ARP攻击可以做什么,账号是否可以被窃取?有哪些常见的ARP渗透(攻击)工具可以用来练手?ARP扫描和攻击有什么区别,底层数据包特征是怎样的? 接下来,我们通过图解的方式来深入了...

双系统安装 Windows8和Windows Server2012

亲自实践 第一种方法: 首先要有一个U盘启动比如用大白菜制作一个U盘启动,制作完之后,然后将下载的win8镜像文件解压到你的本地磁盘,重新启动电脑,然后把U盘设为第一启动盘,在重启,进入winPE系统,那个大白菜制作的PE系统是2003的这道不所谓了,启动起来桌面上有个Win7安装器,也就是nt6启动软件如下图 然后点击图中的打开按钮,在这里打开你刚才解...

Echarts的赋值,设置数据

柱形图案例的赋值 相关文档参考:https://blog.csdn.net/yangsitong1314/article/details/76984616 <div class="map_bg"> <div class="map_center">...

SpreadJS 纯前端表格控件 V12.2 发布更新

用不到100行代码,在前端实现Excel的全部功能 千万前端开发者翘首企盼,SpreadJS V12.2 终发布更新:六大功能特性,带来更多便利,用不到100行代码,在前端实现Excel的全部功能! SpreadJS 是一款基于 HTML5 的纯前端电子表格控件,以“高速低耗、高度类似Excel、可无限扩展”为产品特色,提供移动跨平台和浏览器支持,同时满...

AWTK(Toolkit AnyWhere): 为嵌入式、手机和桌面开发的通用GUI

AWTK = Toolkit AnyWhere AWTK是吸取了FTK和CanTK的精华,重新开发的GUI,计划分以下几个阶段实施: 第一阶段专注于嵌入式系统,到达并超越TouchGfx/Embedded Wizard的功能。计划在2018/9底完成。 第二阶段用AWTK本身开发界面编辑器(或组态软件IDE),从而验证AWTK支持PC软件开发的能力。计划...

iOS开发中的这些权限,你搞懂了吗?

写在前面 APP开发避免不开系统权限的问题,如何在APP以更加友好的方式向用户展示系统权限,似乎也是开发过程中值得深思的一件事; 那如何提高APP获取iOS系统权限的通过率呢?有以下几种方式:1.在用户打开APP时就向用户请求权限;2.告知用户授权权限后能够获得好处之后,再向用户请求权限;3.在绝对必要的情况下才向用户请求权限,例如:用户访问照片库时请求...