Android开发训练之第五章第三节——Transferring Data Without Draining the Battery
Transferring Data Without Draining the Battery
DEPENDENCIES AND PREREQUISITES
- Android 2.0 (API Level 5) or higher
YOU SHOULD ALSO READ
In this class you will learn to minimize the battery life impact of downloads and network connections, particularly in relation to the wireless radio.
This class demonstrates the best practices for scheduling and executing downloads using techniques such as caching, polling, and prefetching. You will learn how the power-use profile of the wireless radio can affect your choices on when, what, and how to transfer data in order to minimize impact on battery life.
Lessons
- Optimizing Downloads for Efficient Network Access
- This lesson introduces the wireless radio state machine, explains how your app’s connectivity model interacts with it, and how you can minimize your data connection and use prefetching and bundling to minimize the battery drain associated with your data transfers.
- Minimizing the Effect of Regular Updates
- This lesson will examine how your refresh frequency can be varied to best mitigate the effect of background updates on the underlying wireless radio state machine.
- Redundant Downloads are Redundant
- The most fundamental way to reduce your downloads is to download only what you need. This lesson introduces some best practices to eliminate redundant downloads.
- Modifying your Download Patterns Based on the Connectivity Type
- When it comes to impact on battery life, not all connection types are created equal. Not only does the Wi-Fi radio use significantly less battery than its wireless radio counterparts, but the radios used in different wireless radio technologies have different battery implications.
Optimizing Downloads for Efficient Network Access
Using the wireless radio to transfer data is potentially one of your app's most significant sources of battery drain. To minimize the battery drain associated with network activity, it's critical that you understand how your connectivity model will affect the underlying radio hardware.
This lesson introduces the wireless radio state machine and explains how your app's connectivity model interacts with it. It goes on to propose ways to minimize your data connections, use prefetching, and bundle your transfers in order to minimize the battery drain associated with your data transfers.
The Radio State Machine
A fully active wireless radio consumes significant power, so it transitions between different energy states in order to conserve power when not in use, while attempting to minimize latency associated with "powering up" the radio when it's required.
The state machine for a typical 3G network radio consists of three energy states:
- Full power: Used when a connection is active, allowing the device to transfer data at its highest possible rate.
- Low power: An intermediate state that uses around 50% of the battery power at the full state.
- Standby: The minimal energy state during which no network connection is active or required.
While the low and idle states drain significantly less battery, they also introduce significant latency to network requests. Returning to full power from the low state takes around 1.5 seconds, while moving from idle to full can take over 2 seconds.
To minimize latency, the state machine uses a delay to postpone the transition to lower energy states. Figure 1 uses AT&T's timings for a typical 3G radio.
Figure 1. Typical 3G wireless radio state machine.
The radio state machine on each device, particularly the associated transition delay ("tail time") and startup latency, will vary based on the wireless radio technology employed (2G, 3G, LTE, etc.) and is defined and configured by the carrier network over which the device is operating.
This lesson describes a representative state machine for a typical 3G wireless radio, based on data provided by AT&T. However, the general principles and resulting best practices are applicable for all wireless radio implementations.
This approach is particularly effective for typical web browsing as it prevents unwelcome latency while users browse the web. The relatively low tail-time also ensures that once a browsing session has finished, the radio can move to a lower energy state.
Unfortunately, this approach can lead to inefficient apps on modern smartphone OSs like Android, where apps run both in the foreground (where latency is important) and in the background (where battery life should be prioritized).
How Apps Impact the Radio State Machine
Every time you create a new network connection, the radio transitions to the full power state. In the case of the typical 3G radio state machine described above, it will remain at full power for the duration of your transfer—plus an additional 5 seconds of tail time—followed by 12 seconds at the low energy state. So for a typical 3G device, every data transfer session will cause the radio to draw energy for almost 20 seconds.
In practice, this means an app that transfers unbundled data for 1 second every 18 seconds will keep the wireless radio perpetually active, moving it back to high power just as it was about to become idle. As a result, every minute it will consume battery at the high power state for 18 seconds, and at the low power state for the remaining 42 seconds.
By comparison, the same app that bundles transfers of 3 seconds of every minute will keep the radio in the high power state for only 8 seconds, and will keep it in the low power state for only an additional 12 seconds.
The second example allows the radio to be idle for an additional 40 second every minute, resulting in a massive reduction in battery consumption.
Figure 2. Relative wireless radio power use for bundled versus unbundled transfers.
Prefetch Data
Prefetching data is an effective way to reduce the number of independent data transfer sessions. Prefetching allows you to download all the data you are likely to need for a given time period in a single burst, over a single connection, at full capacity.
By front loading your transfers, you reduce the number of radio activations required to download the data. As a result you not only conserve battery life, but also improve the latency, lower the required bandwidth, and reduce download times.
Prefetching also provides an improved user experience by minimizing in-app latency caused by waiting for downloads to complete before performing an action or viewing data.
However, used too aggressively, prefetching introduces the risk of increasing battery drain and bandwidth use—as well as download quota—by downloading data that isn't used. It's also important to ensure that prefetching doesn't delay application startup while the app waits for the prefetch to complete. In practical terms that might mean processing data progressively, or initiating consecutive transfers prioritized such that the data required for application startup is downloaded and processed first.
How aggressively you prefetch depends on the size of the data being downloaded and the likelihood of it being used. As a rough guide, based on the state machine described above, for data that has a 50% chance of being used within the current user session, you can typically prefetch for around 6 seconds (approximately 1-2 Mb) before the potential cost of downloading unused data matches the potential savings of not downloading that data to begin with.
Generally speaking, it's good practice to prefetch data such that you will only need to initiate another download every 2 to 5 minutes, and in the order of 1 to 5 megabytes.
Following this principle, large downloads—such as video files—should be downloaded in chunks at regular intervals (every 2 to 5 minutes), effectively prefetching only the video data likely to be viewed in the next few minutes.
Note that further downloads should be bundled, as described in the next section, Batch Transfers and Connections, and that these approximations will vary based on the connection type and speed, as discussed inModify your Download Patterns Based on the Connectivity Type.
Let's look at some practical examples:
A music player
You could choose to prefetch an entire album, however should the user stop listening after the first song, you've wasted a significant amount of bandwidth and battery life.
A better approach would be to maintain a buffer of one song in addition to the one being played. For streaming music, rather than maintaining a continuous stream that keeps the radio active at all times, consider using HTTP live streaming to transmit the audio stream in bursts, simulating the prefetching approach described above.
A news reader
Many news apps attempt to reduce bandwidth by downloading headlines only after a category has been selected, full articles only when the user wants to read them, and thumbnails just as they scroll into view.
Using this approach, the radio will be forced to remain active for the majority of users' news-reading session as they scroll headlines, change categories, and read articles. Not only that, but the constant switching between energy states will result in significant latency when switching categories or reading articles.
A better approach would be to prefetch a reasonable amount of data at startup, beginning with the first set of news headlines and thumbnails—ensuring a low latency startup time—and continuing with the remaining headlines and thumbnails, as well as the article text for each article available from at least the primary headline list.
Another alternative is to prefetch every headline, thumbnail, article text, and possibly even full article pictures—typically in the background on a predetermined schedule. This approach risks spending significant bandwidth and battery life downloading content that's never used, so it should be implemented with caution.
One solution is to schedule the full download to occur only when connected to Wi-Fi, and possibly only when the device is charging. This is investigated in more detail in Modify your Download Patterns Based on the Connectivity Type.
Batch Transfers and Connections
Every time you initiate a connection—irrespective of the size of the associated data transfer—you potentially cause the radio to draw power for nearly 20 seconds when using a typical 3G wireless radio.
An app that pings the server every 20 seconds, just to acknowledge that the app is running and visible to the user, will keep the radio powered on indefinitely, resulting in a significant battery cost for almost no actual data transfer.
With that in mind it's important to bundle your data transfers and create a pending transfer queue. Done correctly, you can effectively phase-shift transfers that are due to occur within a similar time window, to make them all happen simultaneously—ensuring that the radio draws power for as short a duration as possible.
The underlying philosophy of this approach is to transfer as much data as possible during each transfer session in an effort to limit the number of sessions you require.
That means you should batch your transfers by queuing delay tolerant transfers, and preempting scheduled updates and prefetches, so that they are all executed when time-sensitive transfers are required. Similarly, your scheduled updates and regular prefetching should initiate the execution of your pending transfer queue.
For a practical example, let's return to the earlier examples from Prefetch Data.
Take a news application that uses the prefetching routine described above. The news reader collects analytics information to understand the reading patterns of its users and to rank the most popular stories. To keep the news fresh, it checks for updates every hour. To conserve bandwidth, rather than download full photos for each article, it prefetches only thumbnails and downloads the full photos when they are selected.
In this example, all the analytics information collected within the app should be bundled together and queued for download, rather than being transmitted as it's collected. The resulting bundle should be transferred when either a full-sized photo is being downloaded, or when an hourly update is being performed.
Any time-sensitive or on-demand transfer—such as downloading a full-sized image—should preempt regularly scheduled updates. The planned update should be executed at the same time as the on-demand transfer, with the next update scheduled to occur after the set interval. This approach mitigates the cost of performing a regular update by piggy-backing on the necessary time-sensitive photo download.
Reduce Connections
It's generally more efficient to reuse existing network connections than to initiate new ones. Reusing connections also allows the network to more intelligently react to congestion and related network data issues.
Rather than creating multiple simultaneous connections to download data, or chaining multiple consecutive GET requests, where possible you should bundle those requests into a single GET.
For example, it would be more efficient to make a single request for every news article to be returned in a single request / response than to make multiple queries for several news categories. The wireless radio needs to become active in order to transmit the termination / termination acknowledgement packets associated with server and client timeout, so it's also good practice to close your connections when they aren't in use, rather than waiting for these timeouts.
That said, closing a connection too early can prevent it from being reused, which then requires additional overhead for establishing a new connection. A useful compromise is not to close the connection immediately, but to still close it before the inherent timeout expires.
Use the DDMS Network Traffic Tool to Identify Areas of Concern
The Android DDMS (Dalvik Debug Monitor Server) includes a Detailed Network Usage tab that makes it possible to track when your application is making network requests. Using this tool, you can monitor how and when your app transfers data and optimize the underlying code appropriately.
Figure 3 shows a pattern of transferring small amounts of data roughly 15 seconds apart, suggesting that efficiency could be dramatically improved by prefetching each request or bundling the uploads.
Figure 3. Tracking network usage with DDMS.
By monitoring the frequency of your data transfers, and the amount of data transferred during each connection, you can identify areas of your application that can be made more battery-efficient. Generally, you will be looking for short spikes that can be delayed, or that should cause a later transfer to be preempted.
To better identify the cause of transfer spikes, the Traffic Stats API allows you to tag the data transfers occurring within a thread using the TrafficStats.setThreadStatsTag()
method, followed by manually tagging (and untagging) individual sockets using tagSocket()
and untagSocket()
. For example:
TrafficStats.setThreadStatsTag(0xF00D);
TrafficStats.tagSocket(outputSocket);
// Transfer data using socket
TrafficStats.untagSocket(outputSocket);
The Apache HttpClient
and URLConnection
libraries automatically tag sockets based on the currentgetThreadStatsTag()
value. These libraries also tag and untag sockets when recycled through keep-alive pools.
TrafficStats.setThreadStatsTag(0xF00D);
try {
// Make network request using HttpClient.execute()
} finally {
TrafficStats.clearThreadStatsTag();
}
Socket tagging is supported in Android 4.0, but real-time stats will only be displayed on devices running Android 4.0.3 or higher.
Minimizing the Effect of Regular Updates
THIS LESSON TEACHES YOU TO
- Use Google Cloud Messaging as an alternative to polling
- Optimize polling with inexact repeating alarms and exponential back-offs
YOU SHOULD ALSO READ
The optimal frequency of regular updates will vary based on device state, network connectivity, user behavior, and explicit user preferences.
Optimizing Battery Life discusses how to build battery-efficient apps that modify their refresh frequency based on the state of the host device. That includes disabling background service updates when you lose connectivity and reducing the rate of updates when the battery level is low.
This lesson will examine how your refresh frequency can be varied to best mitigate the effect of background updates on the underlying wireless radio state machine.
Use Google Cloud Messaging as an Alternative to Polling
Every time your app polls your server to check if an update is required, you activate the wireless radio, drawing power unnecessarily, for up to 20 seconds on a typical 3G connection.
Google Cloud Messaging for Android (GCM) is a lightweight mechanism used to transmit data from a server to a particular app instance. Using GCM, your server can notify your app running on a particular device that there is new data available for it.
Compared to polling, where your app must regularly ping the server to query for new data, this event-driven model allows your app to create a new connection only when it knows there is data to download.
The result is a reduction in unnecessary connections, and a reduced latency for updated data within your application.
GCM is implemented using a persistent TCP/IP connection. While it's possible to implement your own push service, it's best practice to use GCM. This minimizes the number of persistent connections and allows the platform to optimize bandwidth and minimize the associated impact on battery life.
Optimize Polling with Inexact Repeating Alarms and Exponential Backoffs
Where polling is required, it's good practice to set the default data refresh frequency of your app as low as possible without detracting from the user experience.
A simple approach is to offer preferences to allow users to explicitly set their required update rate, allowing them to define their own balance between data freshness and battery life.
When scheduling updates, use inexact repeating alarms that allow the system to "phase shift" the exact moment each alarm triggers.
int alarmType = AlarmManager.ELAPSED_REALTIME;
long interval = AlarmManager.INTERVAL_HOUR;
long start = System.currentTimeMillis() + interval; alarmManager.setInexactRepeating(alarmType, start, interval, pi);
If several alarms are scheduled to trigger at similar times, this phase-shifting will cause them to be triggered simultaneously, allowing each update to piggyback on top of a single active radio state change.
Wherever possible, set your alarm type to ELAPSED_REALTIME
or RTC
rather than to their _WAKEUP
equivalents. This further reduces battery impact by waiting until the phone is no longer in standby mode before the alarm triggers.
You can further reduce the impact of these scheduled alarms by opportunistically reducing their frequency based on how recently your app was used.
One approach is to implement an exponential back-off pattern to reduce the frequency of your updates (and / or the degree of prefetching you perform) if the app hasn't been used since the previous update. It's often useful to assert a minimum update frequency and to reset the frequency whenever the app is used, for example:
SharedPreferences sp =
context.getSharedPreferences(PREFS, Context.MODE_WORLD_READABLE); boolean appUsed = sp.getBoolean(PREFS_APPUSED, false);
long updateInterval = sp.getLong(PREFS_INTERVAL, DEFAULT_REFRESH_INTERVAL); if (!appUsed)
if ((updateInterval *= 2) > MAX_REFRESH_INTERVAL)
updateInterval = MAX_REFRESH_INTERVAL; Editor spEdit = sp.edit();
spEdit.putBoolean(PREFS_APPUSED, false);
spEdit.putLong(PREFS_INTERVAL, updateInterval);
spEdit.apply(); rescheduleUpdates(updateInterval);
executeUpdateOrPrefetch();
You can use a similar exponential back-off pattern to reduce the effect of failed connections and download errors.
The cost of initiating a network connection is the same whether you are able to contact your server and download data or not. For time-sensitive transfers where successful completion is important, an exponential back-off algorithm can be used to reduce the frequency of retries in order to minimize the associated battery impact, for example:
private void retryIn(long interval) {
boolean success = attemptTransfer();
if (!success) {
retryIn(interval*2 < MAX_RETRY_INTERVAL ?
interval*2 : MAX_RETRY_INTERVAL);
}
}
Alternatively, for transfers that are failure tolerant (such as regular updates), you can simply ignore failed connection and transfer attempts.
Redundant Downloads are Redundant
THIS LESSON TEACHES YOU TO
YOU SHOULD ALSO READ
The most fundamental way to reduce your downloads is to download only what you need. In terms of data, that means implementing REST APIs that allow you to specify query criteria that limit the returned data by using parameters such as the time of your last update.
Similarly, when downloading images, it's good practice to reduce the size of the images server-side, rather than downloading full-sized images that are reduced on the client.
Cache Files Locally
Another important technique is to avoid downloading duplicate data. You can do this by aggressive caching. Always cache static resources, including on-demand downloads such as full size images, for as long as reasonably possible. On-demand resources should be stored separately to enable you to regularly flush your on-demand cache to manage its size.
To ensure that your caching doesn't result in your app displaying stale data, be sure to extract the time at which the requested content was last updated, and when it expires, from within the HTTP response headers. This will allow you to determine when the associated content should be refreshed.
long currentTime = System.currentTimeMillis()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); long expires = conn.getHeaderFieldDate("Expires", currentTime);
long lastModified = conn.getHeaderFieldDate("Last-Modified", currentTime); setDataExpirationDate(expires); if (lastModified < lastUpdateTime) {
// Skip update
} else {
// Parse update
}
Using this approach, you can also effectively cache dynamic content while ensuring it doesn't result in your application displaying stale information.
You can cache non-sensitive data can in the unmanaged external cache directory:
Context.getExternalCacheDir();
Alternatively, you can use the managed / secure application cache. Note that this internal cache may be flushed when the system is running low on available storage.
Context.getCacheDir();
Files stored in either cache location will be erased when the application is uninstalled.
Use the HttpURLConnection Response Cache
Android 4.0 added a response cache to HttpURLConnection
. You can enable HTTP response caching on supported devices using reflection as follows:
private void enableHttpResponseCache() {
try {
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {
Log.d(TAG, "HTTP response cache is unavailable.");
}
}
This sample code will turn on the response cache on Android 4.0+ devices without affecting earlier releases.
With the cache installed, fully cached HTTP requests can be served directly from local storage, eliminating the need to open a network connection. Conditionally cached responses can validate their freshness from the server, eliminating the bandwidth cost associated with the download.
Uncached responses get stored in the response cache for for future requests.
Modifying your Download Patterns Based on the Connectivity Type
THIS LESSON TEACHES YOU TO
YOU SHOULD ALSO READ
When it comes to impact on battery life, not all connection types are created equal. Not only does the Wi-Fi radio use significantly less battery than its wireless radio counterparts, but the radios used in different wireless radio technologies have different battery implications.
Use Wi-Fi
In most cases a Wi-Fi radio will offer greater bandwidth at a significantly lower battery cost. As a result, you should endeavor to perform data transfers when connected over Wi-Fi whenever possible.
You can use a broadcast receiver to listen for connectivity changes that indicate when a Wi-Fi connection has been established to execute significant downloads, preempt scheduled updates, and potentially even temporarily increase the frequency of regular updates as described inOptimizing Battery Life lesson Determining and Monitoring the Connectivity Status.
Use Greater Bandwidth to Download More Data Less Often
When connected over a wireless radio, higher bandwidth generally comes at the price of higher battery cost. Meaning that LTE typically consumes more energy than 3G, which is in turn more expensive than 2G.
This means that while the underlying radio state machine varies based on the radio technology, generally speaking the relative battery impact of the state change tail-time is greater for higher bandwidth radios.
At the same time, the higher bandwidth means you can prefetch more aggressively, downloading more data over the same time. Perhaps less intuitively, because the tail-time battery cost is relatively higher, it's also more efficient to keep the radio active for longer periods during each transfer session to reduce the frequency of updates.
For example, if an LTE radio is has double the bandwidth and double the energy cost of 3G, you should download 4 times as much data during each session—or potentially as much as 10mb. When downloading this much data, it's important to consider the effect of your prefetching on the available local storage and flush your prefetch cache regularly.
You can use the connectivity manager to determine the active wireless radio, and modify your prefetching routines accordingly:
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); TelephonyManager tm =
(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
int PrefetchCacheSize = DEFAULT_PREFETCH_CACHE;
switch (activeNetwork.getType()) {
case (ConnectivityManager.TYPE_WIFI):
PrefetchCacheSize = MAX_PREFETCH_CACHE; break;
case (ConnectivityManager.TYPE_MOBILE): {
switch (tm.getNetworkType()) {
case (TelephonyManager.NETWORK_TYPE_LTE |
TelephonyManager.NETWORK_TYPE_HSPAP):
PrefetchCacheSize *= 4;
break;
case (TelephonyManager.NETWORK_TYPE_EDGE |
TelephonyManager.NETWORK_TYPE_GPRS):
PrefetchCacheSize /= 2;
break;
default: break;
}
break;
}
default: break;
}
Android开发训练之第五章第三节——Transferring Data Without Draining the Battery的更多相关文章
- Android开发训练之第五章第六节——Transferring Data Using Sync Adapters
Transferring Data Using Sync Adapters GET STARTED DEPENDENCIES AND PREREQUISITES Android 2.1 (API Le ...
- Android开发训练之第五章——Building Apps with Connectivity & the Cloud
Building Apps with Connectivity & the Cloud These classes teach you how to connect your app to t ...
- Android开发训练之第五章第七节——Transmitting Network Data Using Volley
Transmitting Network Data Using Volley GET STARTED DEPENDENCIES AND PREREQUISITES Android 1.6 (API L ...
- Android开发训练之第五章第五节——Resolving Cloud Save Conflicts
Resolving Cloud Save Conflicts IN THIS DOCUMENT Get Notified of Conflicts Handle the Simple Cases De ...
- Android开发训练之第五章第四节——Syncing to the Cloud
Syncing to the Cloud GET STARTED DEPENDENCIES AND PREREQUISITES Android 2.2 (API level 8) and higher ...
- Android开发艺术探索第五章——理解RemoteViews
Android开发艺术探索第五章--理解RemoteViews 这门课的重心在于RemoteViews,RemoteViews可以理解为一种远程的View,其实他和远程的Service是一样的,Rem ...
- Android开发艺术探索笔记——第一章:Activity的生命周期和启动模式
Android开发艺术探索笔记--第一章:Activity的生命周期和启动模式 怀着无比崇敬的心情翻开了这本书,路漫漫其修远兮,程序人生,为自己加油! 一.序 作为这本书的第一章,主席还是把Activ ...
- Android群英传笔记——第五章:Android Scroll分析
Android群英传笔记--第五章:Android Scroll分析 滑动事件算是Android比较常用的效果了,而且滑动事件他本身也是有许多的知识点,今天,我们就一起来耍耍Scroll吧 一.滑动效 ...
- 《NodeJs开发指南》第五章微博开发实例的中文乱码问题
在<NodeJs开发指南>第五章,按照书中的要求写好微博实例后,运行代码,发现中文显示出现乱码,原因是:views文件夹下的ejs文件的编码格式不是utf-8. 解决方法:以记事本方式打开 ...
随机推荐
- SpringMVC系列(十六)Spring MVC与Struts2的对比
• Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter• Spring MVC 会稍微比 Struts2 快些. Spring MVC 是基于方法设计, 而 Stu ...
- SpringMVC系列(十二)自定义拦截器
Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口– preHandle():这个方法在业务处 ...
- python3 pyodbc简单使用
转自:https://my.oschina.net/zhengyijie/blog/35587 1.连接数据库 首先要import pyodbc 1)直接连接数据库和创建一个游标(cursor) cn ...
- kubeadm init 卡在 Created API client, waiting for the control plane to become ready
执行 kubeadm init 时出现卡在了 [apiclient] Created API client, waiting for the control plane to become ready ...
- MySql数据库恢复(*frm)文件
mysql数据库恢复(*frm)文件 WorkBench 在使用虚拟服务器时,服务器提供商一般不会像我们使用本地数据库一样:使用导入导出(这样的文件后缀是*.sql).大部分时候提供的是一个文件夹,里 ...
- 【Spark】session 代替 SparkConf、SparkContext和SQLContext
http://www.raincent.com/content-85-7196-1.html
- Java编程思想学习笔记——注解
前言 在Android开发的过程中,我们为了减少重复代码的编写,会使用类似ButterKnife,AndroidAnnotations 这类依赖注解库.代码示例如下: //不使用 Button btn ...
- 使用jstl+el表达式遇到的几个问题
1.使用jstl访问Map<Integer,String>中的内容时总取不到? el表达式的一个bug,在解析数字的时候,会自动将数字转换成Long类型. 我的解决办法是,Map的key改 ...
- Apache+php5
.下载回来的是解压文件,解压好放到要安装的位置.(我这里以D:\Acpache24为例) .打开Apache24\conf下httpd.conf 文件,用记事本打开即可. ()第37行ServerRo ...
- [原]unity调Android(三)
private UnityPlayer mUnityPlayer; @Override protected void onCreate(Bundle savedInstanceState) ...