要成爲Android 高手並非一件容易的事情。並非不少人想象的 可以飛快的寫出幾行漂亮的代碼去解決一些困難的問題 就是Android 高手了。真正的Android 高手須要考慮的問題遠遠不是寫些漂亮的代碼就足夠的。下面是成爲一名真正的Android 高手必須掌握和遵循的一些準則:
1. 學會懶惰
2. 精通Android 體系架構、MVC、常見的設計模式、控制反轉(IoC)
3. 編寫可重用、可擴展、可維護、靈活性高的代碼
4. 高效的編寫高效的代碼
5. 學會至少一門服務器端開發技術java
沒搞錯吧?居然讓程序開發人員學會懶惰?程序開發人員多是世界上最爲忙碌的一類人啦!對,沒錯,學會懶惰!正由於程序開發人員忙碌,正由於程序開發人員可能會在客戶無限變化的需求之下沒日沒夜的加班,因此要學會懶惰,這樣,你就能夠把更多的時間浪費在美好的事物身上!
如何懶惰:android
輪子理論 也即 不要重複發明輪子,這是西方國家的一句諺語,原話是:Don’t Reinvent the Wheel。不要重複發明輪子 意思是企業中任何一項工做實際上都有人作過,咱們所須要作的就是找到作過這件事情的人。拿到軟件領域中就是指有的項目或功能,別人已經作過,咱們須要用的時候,直接拿來用便可,而不要從新制造。
Android 號稱是首個爲移動終端打造的真正開放和完整的移動軟件。Android 發佈後不久Google 公司就發佈了操做系統核心(Kernel)與部分驅動程序的源代碼,到目前位置除了Google Map 等Google 公司的核心組件沒有開放源代碼外,Android 基本完成了徹底的開源,這就極大的促進了Android 的普及和移植。受到Android 開放行爲和開源精神的影響,在世界各地,有成千上萬的程序員喜歡和別人分享本身的聰明才智和本身編寫的代碼。你能夠在Google 的Android 討論組或者Google 搜索引擎上搜索到不少優秀的程序代碼。這樣作並非鼓勵你們成天等着讓別人爲你編寫代碼,而是你能夠「站在偉人的肩膀上」,充分發揚「拿來主義」,聰明地應用別人的程序代碼能夠節省你大量的時間。
下面筆者爲你們介紹幾個通用的類,這些類來自筆者平日的收集,若是你能把它們加入到你本身的類庫中,早晚你會發現本身在進行Android 開發的時候受益無窮:
1) 從輸入流中獲取數據並以字節數組返回,這種輸入流能夠來自Android 本地也能夠來自網絡。程序員
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class StreamTool {
/** * 從輸入流獲取數據 * @param inputStream * @return * @throws Exception */
public static byte[] readInputStream(InputStream inputStream) throws Exception {
byte[] buffer = new byte[1024]; //你能夠根據實際須要調整緩存大小
int len = -1;
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
while( (len = inputStream.read(buffer)) != -1 ){
outSteam.write(buffer, 0, len);
}
outSteam.close();
inputStream.close();
return outSteam.toByteArray();
}
}
2) 經過Android 客戶端上傳數據到服務器:能夠上傳簡單的表單,也能夠方便的上傳帶有附件的文件,此類遠遠比Android 自身的HttpClient 更高效、更易於使用:web
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
public class HttpRequester {
/** * 直接經過HTTP 協議提交數據到服務器,實現以下面表單提交功能: * <FORM METHOD=POST ACTION="http://192.168.0.200:8080/ssi/fileload/test.do" enctype="multipart/form-data"> <INPUT TYPE="text" NAME="name"> <INPUT TYPE="text" NAME="id"> <input type="file" name="imagefile"/> <input type="file" name="zip"/> </FORM> * @param actionUrl 上傳路徑(注:避免使用localhost 或127.0.0.1這樣的路徑 測試, 由於它會指向手機模擬器, 你可使用http://www.itcast.cn 或 http://192.168.1.10:8080這樣的路徑測試) * @param params 請求參數key 爲參數名,value 爲參數值 * @param file 上傳文件 */
public static String post(String actionUrl, Map<String, String> params, FormFile[]
files) {
try {
String BOUNDARY = "---------7d4a6d158c9"; //數據分隔線
String MULTIPART_FORM_DATA = "multipart/form-data";
URL url = new URL(actionUrl);
HttpURLConnection conn = (HttpURLConnection)
url.openConnection();
conn.setConnectTimeout(5* 1000);
conn.setDoInput(true);//容許輸入
conn.setDoOutput(true);//容許輸出
conn.setUseCaches(false);//不使用Cache
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type", MULTIPART_FORM_DATA
* "; boundary=" + BOUNDARY);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {//構建表單
字段內容
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data; name=\""+
entry.getKey() + "\"\r\n\r\n");
sb.append(entry.getValue());
sb.append("\r\n");
}
DataOutputStream outStream = new
DataOutputStream(conn.getOutputStream());
outStream.write(sb.toString().getBytes());//發送表單字段數據
for(FormFile file : files){//發送文件數據
StringBuilder split = new StringBuilder();
split.append("--");
split.append(BOUNDARY);
split.append("\r\n");
split.append("Content-Disposition: form-data;name=\""+
file.getFormname()+"\";filename=\""+ file.getFilname() + "\"\r\n");
split.append("Content-Type: "+ file.getContentType()+"\r\n\r\n");
outStream.write(split.toString().getBytes());
if(file.getInStream()!=null){
byte[] buffer = new byte[1024];
int len = 0;
while((len = file.getInStream().read(buffer))!=-1){
outStream.write(buffer, 0, len);
}
file.getInStream().close();
}else{
outStream.write(file.getData(), 0, file.getData().length);
}
outStream.write("\r\n".getBytes());
}
byte[] end_data = ("--" + BOUNDARY + "--\r\n").getBytes();//數據結
束標誌
outStream.write(end_data);
outStream.flush();
int cah = conn.getResponseCode();
if (cah != 200) throw new RuntimeException("請求url 失敗");
InputStream is = conn.getInputStream();
int ch;
StringBuilder b = new StringBuilder();
while( (ch = is.read()) != -1 ){
b.append((char)ch);
}
outStream.close();
conn.disconnect();
return b.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** * 提交數據到服務器 * @param actionUrl 上傳路徑(注:避免使用localhost 或127.0.0.1這樣的路徑 測試, 由於它會指向手機模擬器, 你可使用http://www.itcast.cn 或 http://192.168.1.10:8080這樣的路徑測試) * @param params 請求參數key 爲參數名,value 爲參數值 * @param file 上傳文件 */
public static String post(String actionUrl, Map<String, String> params, FormFile
file) {
return post(actionUrl, params, new FormFile[]{file});
}
public static byte[] postFromHttpClient(String path, Map<String, String> params,
String encode) throws Exception{
List<NameValuePair> formparams = new ArrayList<NameValuePair>();// 用
於存放請求參數
for(Map.Entry<String, String> entry : params.entrySet()){
formparams.add(new BasicNameValuePair(entry.getKey(),
entry.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams,
"UTF-8");
HttpPost httppost = new HttpPost(path);
httppost.setEntity(entity);
HttpClient httpclient = new DefaultHttpClient();//看做是瀏覽器
HttpResponse response = httpclient.execute(httppost);//發送post 請求
return StreamTool.readInputStream(response.getEntity().getContent());
}
/** * 發送請求 * @param path 請求路徑 * @param params 請求參數key 爲參數名稱value 爲參數值 * @param encode 請求參數的編碼 */
public static byte[] post(String path, Map<String, String> params, String encode)
throws Exception{
//String params = "method=save&name="+ URLEncoder.encode(" 老畢",
"UTF-8")+ "&age=28&";//須要發送的參數
StringBuilder parambuilder = new StringBuilder("");
if(params!=null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
parambuilder.append(entry.getKey()).append("=")
.append(URLEncoder.encode(entry.getValue(),
encode)).append("&");
}
parambuilder.deleteCharAt(parambuilder.length()-1);
}
byte[] data = parambuilder.toString().getBytes();
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);//容許對外發送請求參數
conn.setUseCaches(false);//不進行緩存
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("POST");
//下面設置http 請求頭
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
conn.setRequestProperty("Connection", "Keep-Alive");
//發送參數
DataOutputStream outStream = new
DataOutputStream(conn.getOutputStream());
outStream.write(data);//把參數發送出去
outStream.flush();
outStream.close();
if(conn.getResponseCode()==200){
return StreamTool.readInputStream(conn.getInputStream());
}
return null;
}
}
2,Inventing the Wheel(發明輪子)。
發明輪子?不錯,發明輪子!咱們不只要發明輪子,更要成爲努力成爲世界上發明輪子的主導力量,惟有這樣,才能談的上中華名族軟件大業的真正強大。在Android,要發明輪子,就是咱們要主動的是解決一些世界上他人未解決的難題或者創造新的編程框架或者對Android 進行深度的改造以適合本身的業務發展須要。Google 發佈了Android 後不久,中國移動便投入了大量的人力和物力,在Android 的基礎上建立融入本身業務並開發、封裝了新的功能的和框架的OMS,這是Android 中發明輪子的一個很是重要的例子。可能你會說,這發明輪子也太難了吧,別急,咱們慢慢來,開發一個框架特定領域的框架吧!你可能會一臉無辜的說,開發一個框架是說的那麼容易嗎?固然不是啦。可是也並不是不可能,首先,咱們分析一下框架的魅力的源泉,看看Spring、Struts 等Java EE 框架,在看看.NET框架,固然也能夠看看發展的如火如荼、層出不窮的PHP 框架,她們的強大和魅力的源泉都在於:IoC(Inversion of Control)。
Don’t call us, we’ll call you(別找我,我會來找你的)。咱們下面就本身發明一個輪子的模型,實際展現一個框架最初核心的類,讓你一飽眼福:
1) 下面的類是文件下載類,支持文件的多線程斷點續傳,使用該類的便可安全、高效的下載任何類型的二進制文件:算法
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import cn.itcast.service.FileService;
import android.content.Context;
import android.util.Log;
/** * 文件下載器 */
public class FileDownloader {
private Context context;
private FileService fileService;
private static final String TAG = "FileDownloader";
/* 已下載文件大小*/
private int downloadSize = 0;
/* 原始文件大小*/
private int fileSize = 0;
/* 線程數*/
private DownloadThread[] threads;
/* 下載路徑*/
private URL url;
/* 本地保存文件*/
private File saveFile;
/* 下載記錄文件*/
private File logFile;
/* 緩存各線程最後下載的位置*/
private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();
/* 每條線程下載的大小*/
private int block;
private String downloadUrl;//下載路徑
/** * 獲取線程數 */
public int getThreadSize() {
return threads.length;
}
/** * 獲取文件大小 * @return */
public int getFileSize() {
return fileSize;
}
/** * 累計已下載大小 * @param size */
protected synchronized void append(int size) {
downloadSize += size;
}
/** * 更新指定線程最後下載的位置 * @param threadId 線程id * @param pos 最後下載的位置 */
protected void update(int threadId, int pos) {
this.data.put(threadId, pos);
}
/** * 保存記錄文件 */
protected synchronized void saveLogFile() {
this.fileService.update(this.downloadUrl, this.data);
}
/** * 構建文件下載器 * @param downloadUrl 下載路徑 * @param fileSaveDir 文件保存目錄 * @param threadNum 下載線程數 */
public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int
threadNum) {
try {
this.context = context;
this.downloadUrl = downloadUrl;
fileService = new FileService(context);
this.url = new URL(downloadUrl);
if(!fileSaveDir.exists()) fileSaveDir.mkdirs();
this.threads = new DownloadThread[threadNum];
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6*1000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg,image/pjpeg, application/x-shockwave-flash, application/xaml+xml,application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", downloadUrl);
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
printResponseHeader(conn);
if (conn.getResponseCode()==200) {
this.fileSize = conn.getContentLength();//根據響應獲取文件大小
if (this.fileSize <= 0) throw new RuntimeException("1沒法獲知文件大 小");
String filename = getFileName(conn);
this.saveFile = new File(fileSaveDir, filename);/* 保存文件*/
Map<Integer, Integer> logdata = fileService.getData(downloadUrl);
if(logdata.size()>0){
for(Map.Entry<Integer, Integer> entry : logdata.entrySet())
data.put(entry.getKey(), entry.getValue()+1);
}
this.block = this.fileSize / this.threads.length + 1;
if(this.data.size()==this.threads.length){
for (int i = 0; i < this.threads.length; i++) {
this.downloadSize += this.data.get(i+1)-(this.block * i);
}
print("已經下載的長度"+ this.downloadSize);
}
}else{
throw new RuntimeException("2服務器響應錯誤");
}
} catch (Exception e) {
print(e.toString());
throw new RuntimeException("3鏈接不到下載路徑");
}
}
/** * 獲取文件名 */
private String getFileName(HttpURLConnection conn) {
String filename = this.url.toString().substring(this.url.toString().lastIndexOf('/') +
1);
if(filename==null || "".equals(filename.trim())){//若是獲取不到文件名稱
for (int i = 0;; i++) {
String mine = conn.getHeaderField(i);
if (mine == null) break;
if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){
Matcher m =
Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());
if(m.find()) return m.group(1);
}
}
filename = UUID.randomUUID()+ ".tmp";//默認取一個文件名
}
return filename;
}
/** * 開始下載文件 * @param listener 監聽下載數量的變化,若是不須要了解實時下載的數量,能夠設 置爲null * @return 已下載文件大小 * @throws Exception */
public int download(DownloadProgressListener listener) throws Exception{
try {
if(this.data.size() != this.threads.length){
this.data.clear();
for (int i = 0; i < this.threads.length; i++) {
this.data.put(i+1, this.block * i);
}
}
for (int i = 0; i < this.threads.length; i++) {
int downLength = this.data.get(i+1) - (this.block * i);
if(downLength < this.block && this.data.get(i+1)<this.fileSize){ //該線
程未完成下載時,繼續下載
RandomAccessFile randOut = new
RandomAccessFile(this.saveFile, "rw");
if(this.fileSize>0) randOut.setLength(this.fileSize);
randOut.seek(this.data.get(i+1));
this.threads[i] = new DownloadThread(this, this.url, randOut,
this.block, this.data.get(i+1), i+1);
this.threads[i].setPriority(7);
this.threads[i].start();
}else{
this.threads[i] = null;
}
}
this.fileService.save(this.downloadUrl, this.data);
boolean notFinish = true;//下載未完成
while (notFinish) {// 循環判斷是否下載完畢
Thread.sleep(900);
notFinish = false;//假定下載完成
for (int i = 0; i < this.threads.length; i++){
if (this.threads[i] != null && !this.threads[i].isFinish()) {
notFinish = true;//下載沒有完成
if(this.threads[i].getDownLength() == -1){//若是下載失敗,再
從新下載
RandomAccessFile randOut = new
RandomAccessFile(this.saveFile, "rw");
randOut.seek(this.data.get(i+1));
this.threads[i] = new DownloadThread(this, this.url,
randOut, this.block, this.data.get(i+1), i+1);
this.threads[i].setPriority(7);
this.threads[i].start();
}
}
}
if(listener!=null) listener.onDownloadSize(this.downloadSize);
}
fileService.delete(this.downloadUrl);
} catch (Exception e) {
print(e.toString());
throw new Exception("下載失敗");
}
return this.downloadSize;
}
/** * 獲取Http 響應頭字段 * @param http * @return */
public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {
Map<String, String> header = new LinkedHashMap<String, String>();
for (int i = 0;; i++) {
String mine = http.getHeaderField(i);
if (mine == null) break;
header.put(http.getHeaderFieldKey(i), mine);
}
return header;
}
/** * 打印Http 頭字段 * @param http */
public static void printResponseHeader(HttpURLConnection http){
Map<String, String> header = getHttpResponseHeader(http);
for(Map.Entry<String, String> entry : header.entrySet()){
String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";
print(key+ entry.getValue());
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
public static void main(String[] args) {
/* FileDownloader loader = new FileDownloader(context, "http://browse.babasport.com/ejb3/ActivePort.exe", new File("D:\\androidsoft\\test"), 2); loader.getFileSize();//獲得文件總大小 try { loader.download(new DownloadProgressListener(){ public void onDownloadSize(int size) { print("已經下載:"+ size); } }); } catch (Exception e) { e.printStackTrace(); }*/
}
}
2) 下面的類是真正支持下載的線程類:數據庫
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.util.Log;
public class DownloadThread extends Thread {
private static final String TAG = "DownloadThread";
private RandomAccessFile saveFile;
private URL downUrl;
private int block;
/* 下載開始位置*/
private int threadId = -1;
private int startPos;
private int downLength;
private boolean finish = false;
private FileDownloader downloader;
public DownloadThread(FileDownloader downloader, URL downUrl,
RandomAccessFile saveFile, int block, int startPos, int threadId) {
this.downUrl = downUrl;
this.saveFile = saveFile;
this.block = block;
this.startPos = startPos;
this.downloader = downloader;
this.threadId = threadId;
this.downLength = startPos - (block * (threadId - 1));
}
@Override
public void run() {
if(downLength < block){//未下載完成
try {
HttpURLConnection http = (HttpURLConnection)
downUrl.openConnection();
http.setRequestMethod("GET");
http.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
http.setRequestProperty("Accept-Language", "zh-CN");
http.setRequestProperty("Referer", downUrl.toString());
http.setRequestProperty("Charset", "UTF-8");
http.setRequestProperty("Range", "bytes=" + this.startPos + "-");
http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
http.setRequestProperty("Connection", "Keep-Alive");
InputStream inStream = http.getInputStream();
int max = 1024 * 1024;
byte[] buffer = new byte[max];
int offset = 0;
print("線程" + this.threadId + "從位置"+ this.startPos+ "開始下 載");
while (downLength < block && (offset = inStream.read(buffer, 0,
max)) != -1) {
saveFile.write(buffer, 0, offset);
downLength += offset;
downloader.update(this.threadId, block * (threadId - 1) +
downLength);
downloader.saveLogFile();
downloader.append(offset);
int spare = block-downLength;//求剩下的字節數
if(spare < max) max = (int) spare;
}
saveFile.close();
inStream.close();
print("線程" + this.threadId + "完成下載");
this.finish = true;
this.interrupt();
} catch (Exception e) {
this.downLength = -1;
print("線程"+ this.threadId+ ":"+ e);
}
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
/** * 下載是否完成 * @return */
public boolean isFinish() {
return finish;
}
/** * 已經下載的內容大小 * @return 若是返回值爲-1,表明下載失敗 */
public long getDownLength() {
return downLength;
}
}
3) 下面爲監聽器接口,會實時顯示下載的大小,在實際使用的時候建議採用匿名類的方式構建此接口:apache
public interface DownloadProgressListener {
public void onDownloadSize(int size);
}
上面的三個文件在一塊兒就構建起了一個迷你型的Android 下載框架,這個下載框架能夠用於下載任何類型的二進制文件,之後須要下載的時候直接使用便可。其中IoC 很是直接的體現就是DownloadProgressListener ,在使用的時候只須要只須要傳入該接口一個實現實例便可自動的獲取實時的下載長度。編程
1,請看某個著名的IT 公司一則招聘信息的其中一條要求:「熟悉Android 系統架構及相關技術,1年以上實際Android 平臺開發經驗;」,裏面很是明確的說道要求熟練Android系統架構,這從某種程度上說明了對Android 體系架構的理解的重要性,下面咱們看看Android 體系結構圖,該圖源自Android 的文檔:
很明顯,上圖包含四個主要的層次:canvas
上面的四個層次,下層爲上層服務,上層須要下層的支持,調用下層的服務,這種嚴格分層的方式帶來的極大的穩定性、靈活性和可擴展性,使得不一樣層的開發人員能夠按照規範專心特定層的開發。
Android 應用程序使用框架的API 並在框架下運行,這就帶來了程序開發的高度一致性,另外一方面也告訴咱們,要想寫出優質高效的程序就必須對整個Application Framework 進行很是深刻的理解。精通Application Framework,你就能夠真正的理解Android 的設計和運行機制,也就更可以駕馭整個應用層的開發。
2,Android 的官方建議應用程序的開發採用MVC 模式。何謂MVC?先看看下圖
MVC 是Model,View,Controller 的縮寫,從上圖能夠看出MVC 包含三個部分:設計模式
Android 鼓勵弱耦合和組件的重用,在Android 中MVC 的具體體現以下:
3,設計模式和IoC(控制反轉)
毫無疑問,Android 的之因此可以成爲一個開放的氣象萬千的系統,與設計模式的精妙應用是分不開的,只要你稍微用心觀察,就會發如今Android 中處處都是A 設計模式或者設計模式的聯合運用,一下的設計模式是您想遊刃有餘的駕馭Android 必須掌握的:
Android 框架魅力的源泉在於IoC,在開發Android 的過程當中你會時刻感覺到IoC 帶來的巨大方便,就拿Activity 來講,下面的函數是框架調用自動調用的:
protected void onCreate(Bundle savedInstanceState) ;
不是程序編寫者主動去調用,反而是用戶寫的代碼被框架調用,這也就反轉了!固然IoC 自己的內涵遠遠不止這些,可是從這個例子中也能夠窺視出IoC帶來的巨大好處。此類的例子在Android 隨處可見,例如說數據庫的管理類,例如說Android 中SAX 的Handler 的調用等。有時候,您甚至須要本身編寫簡單的IoC 實現,上面展現的多線程如今就是一個說明。
Android 應用程序的開發是使用Java 編寫,在架構上使用MVC,鼓勵組件之間的若耦合。
開發出編寫可重用、可擴展、可維護、靈活性高的代碼須要經歷遵循如下原則:
靈活的使用設計模式能夠在面對變幻無窮的業務需求是編寫出可重用、可擴展、可維護、靈活性高的代碼。
固然,因爲Android 是運行在移動設備上的,而移動設備的處理能力是有限的,因此有時間必須在編寫可重用、可擴展、可維護、靈活性高的代碼與高效的代碼之間作出適當的平衡。
高效快速的編寫代碼和編寫高效率執行的代碼不少時候都是對立的死敵,不少時候,你想快速的開發,代碼的執行效率每每就會慢下來;你想編寫高效的代碼,開發速度就會慢下來。
不重複發明輪子和發明新的輪子是高效的編寫高效的代碼的正確是道路。
關於高效的代碼,下面網絡的一篇文章,直接轉載(不知道是哪位哥們寫的)以下:
「現代的手持設備,與其說是電話,更像一臺拿在手中的電腦。可是,即便是「最快」的手持設備,其性能也趕不上一臺普通的臺式電腦。
這就是爲何咱們在書寫Android 應用程序的時候要格外關注效率。這些設備並無那麼快,而且受電池電量的制約。這意味着,設備沒有更多的能力,咱們必須把程序寫的儘可能有效。
本文討論了不少能讓開發者使他們的程序運行更有效的方法,遵守這些方法,你可使你的程序發揮最大的效力。
對於佔用資源的系統,有兩條基本原則:
全部下面的內容都遵守這兩個原則。
有些人可能立刻會跳出來,把本節的大部份內容歸於「草率的優化」(xing:參見[The Root of All Evil]),不能否認微優化(micro-optimization。xing:代碼優化,相對於結構優化)的確會帶來不少問題,諸如沒法使用更有效的數據結構和算法。可是在手持設備上,你別無選擇。
假如你認爲Android 虛擬機的性能與臺式機至關,你的程序頗有可能一開始就佔用了系統的所有內存(xing:內存很小),這會讓你的程序慢得像蝸牛同樣,更遑論作其餘的操做了。
Android 的成功依賴於你的程序提供的用戶體驗。而這種用戶體驗,部分依賴於你的程序是響應快速而靈活的,仍是響應緩慢而僵化的。由於全部的程序都運行在同一個設備之上,都在一塊兒,這就若是在同一條路上行駛的汽車。而這篇文檔就至關於你在取得駕照以前必需要學習的交通規則。若是你們都按照這些規則去作,駕駛就會很順暢,可是若是你不這樣作,
你可能會車毀人亡。這就是爲何這些原則十分重要。
當咱們開門見山、直擊主題以前,還必需要提醒你們一點:無論VM 是否支持實時(JIT)編譯器(xing:它容許實時地將Java 解釋型程序自動編譯成本機機器語言,以使程序執行的速度更快。有些JVM 包含JIT 編譯器。),下面提到的這些原則都是成立的。假如咱們有目標徹底相同的兩個方法,在解釋執行時foo()比bar()快,那麼編譯以後,foo()依然會比bar()快。因此不要寄但願於編譯器能夠拯救你的程序。
避免創建對象
世界上沒有免費的對象。雖然GC 爲每一個線程都創建了臨時對象池,可使建立對象的代價變得小一些,可是分配內存永遠都比不分配內存的代價大。
若是你在用戶界面循環中分配對象內存,就會引起週期性的垃圾回收,用戶就會以爲界面像打嗝同樣一頓一頓的。
因此,除非必要,應儘可能避免盡力對象的實例。下面的例子將幫助你理解這條原則:
當你從用戶輸入的數據中截取一段字符串時,儘可能使用substring 函數取得原始數據的一個子串,而不是爲子串另外創建一份拷貝。這樣你就有一個新的String 對象,它與原始數據共享一個char 數組。
若是你有一個函數返回一個String對象,而你確切的知道這個字符串會被附加到一個StringBuffer,那麼,請改變這個函數的參數和實現方式,直接把結果附加到StringBuffer 中,而不要再創建一個短命的臨時對象。
一個更極端的例子是,把多維數組分紅多個一維數組。
int 數組比Integer 數組好,這也歸納了一個基本事實,兩個平行的int 數組比(int,int)對象數組性能要好不少。同理,這試用於全部基本類型的組合。
若是你想用一種容器存儲(Foo,Bar)元組,嘗試使用兩個單獨的Foo[]數組和Bar[]數組,必定比(Foo,Bar)數組效率更高。(也有例外的狀況,就是當你創建一個API,讓別人調用它的時候。這時候你要注重對API 藉口的設計而犧牲一點兒速度。固然在API 的內部,你仍要儘量的提升代碼的效率)整體來講,就是避免建立短命的臨時對象。減小對象的建立就能減小垃圾收集,進而減小對用戶體驗的影響。
使用本地方法
當你在處理字串的時候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊實現的方法(specialty methods)。這些方法都是使用C/C++實現的,比起Java 循環快10 到100 倍。
使用實類比接口好
假設你有一個HashMap 對象,你能夠將它聲明爲HashMap 或者Map:
Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();
哪一個更好呢?
按照傳統的觀點Map 會更好些,由於這樣你能夠改變他的具體實現類,只要這個類繼承自Map 接口。傳統的觀點對於傳統的程序是正確的,可是它並不適合嵌入式系統。調用一個接口的引用會比調用實體類的引用多花費一倍的時間。
若是HashMap 徹底適合你的程序,那麼使用Map 就沒有什麼價值。若是有些地方你不能肯定,先避免使用Map,剩下的交給IDE 提供的重構功能好了。(固然公共API 是一個例外:一個好的API 經常會犧牲一些性能)
用靜態方法比虛方法好
若是你不須要訪問一個對象的成員變量,那麼請把方法聲明成static。虛方法執行的更快,由於它能夠被直接調用而不須要一個虛函數表。另外你也能夠經過聲明體現出這個函數的調用不會改變對象的狀態。
不用getter 和setter
在不少本地語言如C++中,都會使用getter(好比:i = getCount())來避免直接訪問成員變量(i = mCount)。在C++中這是一個很是好的習慣,由於編譯器可以內聯訪問,若是你須要約束或調試變量,你能夠在任什麼時候候添加代碼。
在Android 上,這就不是個好主意了。虛方法的開銷比直接訪問成員變量大得多。在通用的接口定義中,能夠依照OO 的方式定義getters 和setters,可是在通常的類中,你應該直接訪問變量。
將成員變量緩存到本地
訪問成員變量比訪問本地變量慢得多,下面一段代碼:
for (int i = 0; i < this.mCount; i++)
dumpItem(this.mItems[i]);
最好改爲這樣:
int count = this.mCount;
Item[] items = this.mItems;
for (int i = 0; i < count; i++)
dumpItems(items[i]);
(使用」this」是爲了代表這些是成員變量)
另外一個類似的原則是:永遠不要在for 的第二個條件中調用任何方法。以下面方法所示,在每次循環的時候都會調用getCount()方法,這樣作比你在一個int 先把結果保存起來開銷大不少。
for (int i = 0; i < this.getCount(); i++)
dumpItems(this.getItem(i));
一樣若是你要屢次訪問一個變量,也最好先爲它創建一個本地變量,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height)
{
if (isHorizontalScrollBarEnabled()) {
int size = mScrollBar.getSize(false);
if (size <= 0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams(computeHorizontalScrollRange(),computeHorizontalScrollOffset(),computeHorizontalScrollExtent(), false);
mScrollBar.draw(canvas);
}
}
這裏有4次訪問成員變量mScrollBar,若是將它緩存到本地,4 次成員變量訪問就會變成4次效率更高的棧變量訪問。
另外就是方法的參數與本地變量的效率相同。
使用常量
讓咱們來看看這兩段在類前面的聲明:
static int intVal = 42;
static String strVal = "Hello, world!";
必以其會生成一個叫作的初始化類的方法,當類第一次被使用的時候這個方法會被
執行。方法會將42 賦給intVal,而後把一個指向類中常量表的引用賦給strVal。當之後要用到這些值的時候,會在成員變量表中查找到他們。
下面咱們作些改進,使用「final」關鍵字:
static final int intVal = 42;
static final String strVal = "Hello, world!";
如今,類再也不需< clinit >方法,由於在成員變量初始化的時候,會將常量直接保存到類文件中。用到intVal 的代碼被直接替換成42,而使用strVal 的會指向一個字符串常量,而不是使用成員變量。
將一個方法或類聲明爲」final」不會帶來性能的提高,可是會幫助編譯器優化代碼。舉例說,
若是編譯器知道一個」getter」方法不會被重載,那麼編譯器會對其採用內聯調用。
你也能夠將本地變量聲明爲」final」,一樣,這也不會帶來性能的提高。使用」final」只能使本地變量看起來更清晰些(可是也有些時候這是必須的,好比在使用匿名內部類的時候)(xing:原文是or you have to, e.g. for use in an anonymous inner class)
謹慎使用 foreach
foreach 能夠用在實現了Iterable 接口的集合類型上。foreach 會給這些對象分配一個iterator,而後調用hasNext()和next()方法。你最好使用foreach 處理ArrayList 對象,可是對其餘集合對象,foreach 至關於使用iterator。
下面展現了foreach 一種可接受的用法:
public class Foo {
int mSplat;
static Foo mArray[] = new Foo[27];
public static void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; i++) {
sum += mArray[i].mSplat;
}
}
public static void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; i++) {
sum += localArray[i].mSplat;
}
}
public static void two() {
int sum = 0;
for (Foo a: mArray) {
sum += a.mSplat;
}
}
}
在zero()中,每次循環都會訪問兩次靜態成員變量,取得一次數組的長度。
retrieves the static field twice and gets the array length once for every iteration through the loop.
在one()中,將全部成員變量存儲到本地變量。
pulls everything out into local variables, avoiding the lookups.
two()使用了在java1.5 中引入的foreach 語法。編譯器會將對數組的引用和數組的長度保存到本地變量中,這對訪問數組元素很是好。可是編譯器還會在每次循環中產生一個額外的對本地變量的存儲操做(對變量a 的存取)這樣會比one()多出4 個字節,速度要稍微慢一些。
綜上所述:foreach 語法在運用於array 時性能很好,可是運用於其餘集合對象時要當心,由於它會產生額外的對象。
避免使用枚舉
枚舉變量很是方便,但不幸的是它會犧牲執行的速度和並大幅增長文件體積。例如:
public class Foo {
public enum Shrubbery { GROUND, CRAWLING, HANGING }}
會產生一個900 字節的.class 文件(Foo$Shubbery.class)。在它被首次調用時,這個類會調用初始化方法來準備每一個枚舉變量。每一個枚舉項都會被聲明成一個靜態變量,並被賦值。而後將這些靜態變量放在一個名爲」$VALUES」的靜態數組變量中。而這麼一大堆代碼,僅僅是爲了使用三個整數。
這樣:Shrubbery shrub = Shrubbery.GROUND;會引發一個對靜態變量的引用,若是這個靜態變量是final int,那麼編譯器會直接內聯這個常數。
一方面說,使用枚舉變量可讓你的API 更出色,並能提供編譯時的檢查。因此在一般的時候你毫無疑問應該爲公共API 選擇枚舉變量。可是當性能方面有所限制的時候,你就應該避免這種作法了。
有些狀況下,使用ordinal()方法獲取枚舉變量的整數值會更好一些,舉例來講,將:
for (int n = 0; n < list.size(); n++) {
if (list.items[n].e == MyEnum.VAL_X) // do stuff 1
else if (list.items[n].e == MyEnum.VAL_Y)// do stuff 2
}
替換爲:
int valX = MyEnum.VAL_X.ordinal();
int valY = MyEnum.VAL_Y.ordinal();
int count = list.size();
MyItem items = list.items();
for (int n = 0; n < count; n++) {
int valItem = items[n].e.ordinal();
if (valItem == valX)// do stuff 1
else if (valItem == valY)// do stuff 2
}
會使性能獲得一些改善,但這並非最終的解決之道。
將與內部類一同使用的變量聲明在包範圍內
public class Foo {
private int mValue;
public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}
private void doStuff(int value) {
System.out.println("Value is " + value);
}
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}}
是,咱們定義了一個內部類(Foo
成員是非法的。要跨越這個鴻溝,編譯器須要生成一組方法:
static int Foo.access$100(Foo foo) {
return foo.mValue;
}
static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
內部類在每次訪問」mValue」和」doStuff」方法時,都會調用這些靜態方法。就是說,上面的代碼說明了一個問題,你是在經過接口方法訪問這些成員變量和函數而不是直接調用它們。
在前面咱們已經說過,使用接口方法(getter、setter)比直接訪問速度要慢。因此這個例子就是在特定語法下面產生的一個「隱性的」性能障礙。
經過將內部類訪問的變量和函數聲明由私有範圍改成包範圍,咱們能夠避免這個問題。這樣作可讓代碼運行更快,而且避免產生額外的靜態方法。(遺憾的是,這些域和方法能夠被同一個包內的其餘類直接訪問,這與經典的OO 原則相違背。所以當你設計公共API 的時候應該謹慎使用這條優化原則)避免使用浮點數在奔騰CPU 出現以前,遊戲設計者作得最多的就是整數運算。隨着奔騰的到來,浮點運算處理器成爲了CPU 內置的特性,浮點和整數配合使用,可以讓你的遊戲運行得更順暢。一般在桌面電腦上,你能夠隨意的使用浮點運算。
可是很是遺憾,嵌入式處理器一般沒有支持浮點運算的硬件,全部對」float」和」double」的運算都是經過軟件實現的。一些基本的浮點運算,甚至須要毫秒級的時間才能完成。甚至是整數,一些芯片有對乘法的硬件支持而缺乏對除法的支持。這種狀況下,整數的除法和取模運算也是有軟件來完成的。因此當你在使用哈希表或者作大量數學運算時必定要當心謹慎。」
可能有朋友會問:學習Android 應用程序開發爲何還須要學習學會至少一門服務器端開發技術呢?答案以下:一方面Android 號稱是首個爲移動終端打造的真正開放和完整的移動軟件。做爲一種移動終端,必須與服務器端結合才能發揮巨大的做用。簡言之,須要:雲端+雲的方式。Android 是爲移動互聯網時代量身打造的,移動互聯網時代的服務模式是「手機終端+互聯網絡+應用軟件」,移動互聯網時代應用技術之一的Android 只是用於開發移動終端軟件,而服務端技術用於開發互聯網絡應用,因此將來移動互聯網時代軟件的主流應用模式將是「手機客戶端+互聯網絡應用服務端」,這種模式要求作移動互聯網開發的程序員不但要掌握像Android 這樣的手機終端軟件技術還要掌握開發互聯網絡應用的服務器端技術。目前,軟件企業廣泛存在這樣的問題,作移動互聯網開發Android 終端軟件的程序員不瞭解web 應用技術,而作web 應用的程序員不瞭解移動終端技術,這樣就致使了客戶端與服務端在銜接上出現了問題。目前的現狀是:既掌握移動互聯網Android 終端技術,又掌握web 應用技術的程序員比較稀缺,隨着中國步入移動互聯網時代,企業對這種移動互聯網時代綜合性人才的需求很旺盛。若是不瞭解web 應用技術,最終會遇到了技術和發展的瓶頸;另外一方面,Google 聯合OHA 推出的真正優點之一也在於和和互聯網結合,Google 的用意之一也是想開闢新的終端去使用Google 的優點服務。
服務器端開發技術目前主流的有Sun的JavaEE、微軟的.NET,開源的以PHP和MySQL爲表明的LAMP 體系,咱們該選擇哪種呢?從理論上講,不少人傾向於選擇Java EE,畢竟它們都是使用Java 做爲開發語言的,可是不少人面對Java EE 衆多的框架就望而生畏,其實在學習Java EE 的時候能夠從Struts 入手,隨着業務的需求逐步深刻。固然,選擇微軟的.NET 也行,畢竟該技術體系也佔有很大市場份額。其實,筆者認爲,選擇LAMP 能夠是會得到最高的「性價比」的,一方面PHP 是如今Web 方面的主流語言,大多數新型的網站尤爲是創業性質的網站通常都會選用PHP 做爲服務端開發語言,另外一方面,前面也說過,Android 是爲移動互聯而生的,二者達到了完美的契合。
若是您精通Android,又精通LAMP、Java EE、.NET,請聯繫筆者:
官方博客: http://www.cnblogs.com/guoshiandroid/