1. 程式人生 > >Android 學習資料大集合

Android 學習資料大集合

Android 值得學習的好資料,獻給大家,轉的哈

1. android單例項執行方法

我們都知道Android平臺沒有工作管理員,而內部App維護者一個Activity history stack來實現視窗顯示和銷燬,對於常規從快捷方式執行來看都是startActivity可能會使用FLAG_ACTIVITY_NEW_TASK標記來開啟一個新視窗,比如Launcher,所以考慮單任務的實現方法比較簡單,首先Android123糾正下大家一種錯誤的方法就是直接在androidmanifest.xml的application節點中加入android:launchMode="singleInstance"這句,其實這樣將不會起到任何作用,Apps內部維護的歷史棧作用於Activity,我們必須在activity節點中加入android:launchMode="singleInstance" 這句才能保證單例項,當然一般均加在主程式啟動視窗的Activity。

2. px畫素如何轉為dip裝置獨立畫素

最近有網友問如何將px畫素轉為dip獨立裝置畫素,由於Android的裝置解析度眾多,目前主流的為wvga,而很多老的裝置為hvga甚至低端的qvga,對於相容性來說使用dip無非是比較方便的,由於他和解析度無關和螢幕的密度大小有關,所以推薦使用。  px= (int) (dip*density+0.5f) //這裡android開發網提示大家很多網友獲取density(密度)的方法存在問題,從資源中獲取的是靜態定義的,一般為1.0對於HVGA是正好的,而對於wvga這樣的應該從WindowsManager中獲取,WVGA為1.5

這裡可以再補充一下dip,sip的知識

3. Android中動態改變ImageView大小

很多網友可能發現在layout.xml檔案中定義了ImageView的絕對大小後,無法動態修改以後的大小顯示,其實Android平臺在設計UI控制元件時考慮到這個問題,為了適應不同的Drawable可以通過在xml的相關ImageView中加入android:scaleType="fitXY" 這行即可,但因為使用了縮放可能會造成當前UI有所變形。使用的前提是限制ImageView所在的層,可以使用一個內嵌的方法限制顯示。

4. 如何判斷Android手機當前是否聯網?

如果擬開發一個網路應用的程式,首先考慮是否接入網路,在Android手機中判斷是否聯網可以通過 ConnectivityManager 類的isAvailable()方法判斷,首先獲取網路通訊類的例項 ConnectivityManager cwjManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); ,使用cwjManager.getActiveNetworkInfo().isAvailable(); 來返回是否有效,如果為True則表示當前Android手機已經聯網,可能是WiFi或GPRS、HSDPA等等,具體的可以通過ConnectivityManager 類的getActiveNetworkInfo() 方法判斷詳細的接入方式,需要注意的是有關呼叫需要加入<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> 這個許可權,android開發網提醒大家在真機上Market和Browser程式都使用了這個方法,來判斷是否繼續,同時在一些網路超時的時候也可以檢查下網路連線是否存在,以免浪費手機上的電力資源。

5. Drawable、Bitmap、Canvas和Paint的關係

很多網友剛剛開始學習Android平臺,對於Drawable、Bitmap、Canvas和Paint它們之間的概念不是很清楚,其實它們除了Drawable外早在Sun的J2ME中就已經出現了,但是在Android平臺中,Bitmap、Canvas相關的都有所變化。

  首先讓我們理解下Android平臺中的顯示類是View,但是還提供了底層圖形類android.graphics,今天所說的這些均為graphics底層圖形介面。

  Bitmap - 稱作點陣圖,一般點陣圖的檔案格式字尾為bmp,當然編碼器也有很多如RGB565、RGB888。作為一種逐畫素的顯示物件執行效率高,但是缺點也很明視訊記憶體儲效率低。我們理解為一種儲存物件比較好。

  Drawable - 作為Android平下通用的圖形物件,它可以裝載常用格式的影象,比如GIF、PNG、JPG,當然也支援BMP,當然還提供一些高階的視覺化物件,比如漸變、圖形等。

  Canvas - 名為畫布,我們可以看作是一種處理過程,使用各種方法來管理Bitmap、GL或者Path路徑,同時它可以配合Matrix矩陣類給影象做旋轉、縮放等操作,同時Canvas類還提供了裁剪、選取等操作。

   Paint - 我們可以把它看做一個畫圖工具,比如畫筆、畫刷。他管理了每個畫圖工具的字型、顏色、樣式。

  如果涉及一些Android遊戲開發、顯示特效可以通過這些底層圖形類來高效實現自己的應用。

6. Activity切換導致的onCreate重複執行

部分網友會發現Activity在切換到後臺或佈局從橫屏LANDSCAPE切換到PORTRAIT,會重新切換Activity會觸發一次onCreate方法,我們可以在androidmanifest.xml中的activit元素加入這個屬性android:configChanges="orientation|keyboardHidden" 即可,比如

<activity android:name=".android123" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name">

  同時在Activity的Java檔案中過載onConfigurationChanged(Configuration newConfig)這個方法,這樣就不會在佈局切換或視窗切換時過載onCreate等方法。程式碼如下:

@Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
     if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
     {
//land
     }
     else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
     {
//port
     }
    }

7. Android的ImageButton問題

很多網友對Android提供的ImageButton有個疑問,當顯示Drawable圖片時就不會再顯示文字了,其實解決的方法有兩種,第一種就是圖片中就寫入文字,但是這樣解決會增加程式體積,同時硬編碼方式會影響多國語言的釋出。第二種解決方法很簡單,通過分析可以看到ImageButton的layout,我們可以直接直接繼承,新增一個TextView,對齊方式為右側即可實現ImageButton支援文字右側顯示。

8. Android程式碼優化技術

1.Java記憶體控制

  對於字串操作而言如果需要連加這樣的操作建議使用StringBuilder,經過除錯不難發現如果你的字串每次連加,使用String需要的記憶體開銷會遠大於StringBuilder,然後Android手機常規的執行記憶體大約在128MB左右,對於執行多工就需要考慮了,Android開發網提示因為Java有GC不需要手動釋放那麼分配的時候就要格外的小心,頻繁的GC操作仍然是很影響效能的,在除錯時我們可以通過logcat檢視記憶體釋放情況。

  2.迴圈使用

  平時在訪問一個屬性的時候效率遠比一個固定變數低,如果你的迴圈估計次數常常大於5,假設xxx.GetLength()方法的值一般大於5,推薦這樣寫,比如

  for(int i=0;i<xxx.GetLength();i++)

  這裡xxx.GetLength在每次迴圈都要呼叫,必然會影響程式效率,在遊戲開發中顯得更為明顯,改進的方法應該為

  int j=xxx.GetLength()

   for(int i=0;i<j;i++)

  3.圖片的優化

  在Android平臺中2維影象處理庫BitmapFactory做的比較智慧,為了減少檔案體積和效率,常常不用很多資原始檔,而把很多小圖片放在一個圖片中,有切片的方式來完成,在J2ME中我們這樣是為了將少檔案頭而解決MIDP這些裝置的問題,而Android中雖然機型硬體配置都比較高,有關Android G1硬體配置可以參考G1手機引數以及評測,但是當資源多時這樣的執行效率還是令人滿意的,至少Dalvik優化的還不是很夠。

9. Android開發進階之NIO非阻塞包(一)

對於Android的網路通訊效能的提高,我們可以使用Java上高效能的NIO (New I/O) 技術進行處理,NIO是從JDK 1.4開始引入的,NIO的N我們可以理解為Noblocking即非阻塞的意思,相對應傳統的I/O,比如Socket的accpet()、read()這些方法而言都是阻塞的。

  NIO主要使用了Channel和Selector來實現,Java的Selector類似Winsock的Select模式,是一種基於事件驅動的,整個處理方法使用了輪訓的狀態機,如果你過去開發過Symbian應用的話這種方式有點像活動物件,好處就是單執行緒更節省系統開銷,NIO的好處可以很好的處理併發,對於Android網遊開發來說比較關鍵,對於多點Socket連線而言使用NIO可以大大減少執行緒使用,降低了執行緒死鎖的概率,畢竟手機遊戲有UI執行緒,音樂執行緒,網路執行緒,管理的難度可想而知,同時I/O這種低速裝置將影響遊戲的體驗。

  NIO作為一種中高負載的I/O模型,相對於傳統的BIO (Blocking I/O)來說有了很大的提高,處理併發不用太多的執行緒,省去了建立銷燬的時間,如果執行緒過多排程是問題,同時很多執行緒可能處於空閒狀態,大大浪費了CPU時間,同時過多的執行緒可能是效能大幅下降,一般的解決方案中可能使用執行緒池來管理排程但這種方法治標不治本。使用NIO可以使併發的效率大大提高。當然NIO和JDK 7中的AIO還存在一些區別,AIO作為一種更新的當然這是對於Java而言,如果你開發過Winsock伺服器,那麼IOCP這樣的I/O完成埠可以解決更高階的負載,當然了今天Android123主要給大家講解下為什麼使用NIO在Android中有哪些用處。

   NIO我們分為幾個型別分別描述,作為Java的特性之一,我們需要了解一些新的概念,比如ByteBuffer類,Channel,SocketChannel,ServerSocketChannel,Selector和SelectionKey。有關具體的使用,Android開發網將在明天詳細講解。網友可以在Android SDK文件中看下java.nio和java.nio.channels兩個包瞭解。http://www.android123.com.cn/androidkaifa/695.html

瞭解下這種技術,看看在馬上要做的專案中是否用得到

10. Android Theme和Styles內部定義解析

昨天我們講到的有關在AndroidManifest.xml中定義Activity的theme方法來實現無標題的方法,在使用xml讓你的Activity無標題方法 一文中講到的,很多網友不明白為什麼這樣做,其實在Android123以前的文章中多次提到了styles樣式定義方法,今天Android開發網再次把一些網友回顧瞭解下android樣式的內部定義。在一個工程的res/values/theme.xml中我們可以方便的定義自己的風格主題,比如下面的cwjTheme中我們使用了基於android內部的白色調的背景Theme.Light,設定windowsNoTitle為true代表沒有標題,背景顏色我們使用了android內部定義的透明,同時設定listView控制元件的樣式為cwjListView,xml樣式程式碼如下:

  <?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="cwjTheme" parent="android:Theme.Light">
   <item name="android:windowNoTitle">true</item>
   <item name="android:windowBackground">@android:color/transparent</item>
   <item name="android:listViewStyle">@style/cwjListView</item>
</style> 

有關ListView控制元件我們自定義的風格就是修改下系統listview這個控制元件的每行分隔符樣式,這裡我們在工程下res/drawable資料夾下放一個圖片名為list_selector圖片,這樣我們的cwjListView的程式碼可以這樣寫

  <style name="cwjListView" parent="@android:style/Widget.ListView">
     <item name="android:listSelector">@drawable/list_selector</item>
   </style>
</resources>

  通過定義style可以設定更多,比如讓cwjListView的字型顏色就加入textAppearance屬性,比如 <item name="textAppearance">@android:style/TextAppearance</item> 等等。

11.Android JSON解析示例程式碼

來自Google官方的有關Android平臺的JSON解析示例,如果遠端伺服器使用了json而不是xml的資料提供,在Android平臺上已經內建的org.json包可以很方便的實現手機客戶端的解析處理。下面Android123一起分析下這個例子,幫助Android開發者需要有關 HTTP通訊、正則表示式、JSON解析、appWidget開發的一些知識。

public class WordWidget extends AppWidgetProvider { //appWidget
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        context.startService(new Intent(context, UpdateService.class)); //避免ANR,所以Widget中開了個服務
    }

    public static class UpdateService extends Service {
        @Override
        public void onStart(Intent intent, int startId) {
            // Build the widget update for today
            RemoteViews updateViews = buildUpdate(this);

            ComponentName thisWidget = new ComponentName(this, WordWidget.class);
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            manager.updateAppWidget(thisWidget, updateViews);
        }

        public RemoteViews buildUpdate(Context context) {
            // Pick out month names from resources
            Resources res = context.getResources();
            String[] monthNames = res.getStringArray(R.array.month_names);

             Time today = new Time();
            today.setToNow();

            String pageName = res.getString(R.string.template_wotd_title,
                    monthNames[today.month], today.monthDay);
            RemoteViews updateViews = null;
            String pageContent = "";

            try {
                SimpleWikiHelper.prepareUserAgent(context);
                pageContent = SimpleWikiHelper.getPageContent(pageName, false);
            } catch (ApiException e) {
                Log.e("WordWidget", "Couldn't contact API", e);
            } catch (ParseException e) {
                Log.e("WordWidget", "Couldn't parse API response", e);
            }

            Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX); //正則表示式處理,有關定義見下面的SimpleWikiHelper類
            Matcher matcher = pattern.matcher(pageContent);
            if (matcher.find()) {
                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);

                String wordTitle = matcher.group(1);
                updateViews.setTextViewText(R.id.word_title, wordTitle);
                updateViews.setTextViewText(R.id.word_type, matcher.group(2));
                updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());

                String definePage = res.getString(R.string.template_define_url,
                        Uri.encode(wordTitle));
                Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage)); //這裡是開啟相應的網頁,所以Uri是http的url,action是view即開啟web瀏覽器
                PendingIntent pendingIntent = PendingIntent.getActivity(context,
                        0 /* no requestCode */, defineIntent, 0 /* no flags */);
                updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent); //單擊Widget開啟Activity

            } else {
                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
                CharSequence errorMessage = context.getText(R.string.widget_error);
                updateViews.setTextViewText(R.id.message, errorMessage);
            }
            return updateViews;
        }

        @Override
        public IBinder onBind(Intent intent) {
            // We don't need to bind to this service
            return null;
        }
    }
}

  有關網路通訊的實體類,以及一些常量定義如下:

  public class SimpleWikiHelper {
    private static final String TAG = "SimpleWikiHelper";

    public static final String WORD_OF_DAY_REGEX =
            "(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";

    private static final String WIKTIONARY_PAGE =
            "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
            "rvprop=content&format=json%s";

    private static final String WIKTIONARY_EXPAND_TEMPLATES =
            "&rvexpandtemplates=true";

    private static final int HTTP_STATUS_OK = 200;

    private static byte[] sBuffer = new byte[512];

    private static String sUserAgent = null;

     public static class ApiException extends Exception {
        public ApiException(String detailMessage, Throwable throwable) {
            super(detailMessage, throwable);
        }

        public ApiException(String detailMessage) {
            super(detailMessage);
        }
    }

    public static class ParseException extends Exception {
        public ParseException(String detailMessage, Throwable throwable) {
            super(detailMessage, throwable);
        }
    }

    public static void prepareUserAgent(Context context) {
        try {
            // Read package name and version number from manifest
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
            sUserAgent = String.format(context.getString(R.string.template_user_agent),
                    info.packageName, info.versionName);

        } catch(NameNotFoundException e) {
            Log.e(TAG, "Couldn't find package information in PackageManager", e);
        }
    }

    public static String getPageContent(String title, boolean expandTemplates)
            throws ApiException, ParseException {
        String encodedTitle = Uri.encode(title);
        String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";

        String content = getUrlContent(String.format(WIKTIONARY_PAGE, encodedTitle, expandClause));
        try {
            JSONObject response = new JSONObject(content);
            JSONObject query = response.getJSONObject("query");
            JSONObject pages = query.getJSONObject("pages");
            JSONObject page = pages.getJSONObject((String) pages.keys().next());
            JSONArray revisions = page.getJSONArray("revisions");
            JSONObject revision = revisions.getJSONObject(0);
            return revision.getString("*");
        } catch (JSONException e) {
            throw new ParseException("Problem parsing API response", e);
        }
    }

    protected static synchronized String getUrlContent(String url) throws ApiException {
        if (sUserAgent == null) {
            throw new ApiException("User-Agent string must be prepared");
        }

        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);
        request.setHeader("User-Agent", sUserAgent); //設定客戶端標識

        try {
            HttpResponse response = client.execute(request);

            StatusLine status = response.getStatusLine();
            if (status.getStatusCode() != HTTP_STATUS_OK) {
                throw new ApiException("Invalid response from server: " +
                        status.toString());
            }

            HttpEntity entity = response.getEntity();
            InputStream inputStream = entity.getContent(); //獲取HTTP返回的資料流

            ByteArrayOutputStream content = new ByteArrayOutputStream();

            int readBytes = 0;
            while ((readBytes = inputStream.read(sBuffer)) != -1) {
                content.write(sBuffer, 0, readBytes); //轉化為位元組陣列流
            }

            return new String(content.toByteArray()); //從位元組陣列構建String
        } catch (IOException e) {
            throw new ApiException("Problem communicating with API", e);
        }
    }
}

有關整個每日維基的widget例子比較簡單,主要是幫助大家積累常用程式碼,瞭解Android平臺 JSON的處理方式,畢竟很多Server還是Java的。

12.Android中使用定時器TimerTask類介紹

在Android平臺中需要反覆按週期執行方法可以使用Java上自帶的TimerTask類,TimerTask相對於Thread來說對於資源消耗的更低,除了使用Android自帶的AlarmManager使用Timer定時器是一種更好的解決方法。 我們需要引入import java.util.Timer; 和 import java.util.TimerTask;

private Timer  mTimer = new Timer(true);
private TimerTask mTimerTask;

    mTimerTask = new TimerTask()
    {
      public void run()
      {
       Log.v("android123","cwj");
      }       
     };
     mTimer.schedule(mTimerTask, 5000,1000);  //在1秒後每5秒執行一次定時器中的方法,比如本文為呼叫log.v列印輸出。

  如果想取消可以呼叫下面方法,取消定時器的執行

   while(!mTimerTask.cancel());
      mTimer.cancel();

  最後Android123提示大家,如果處理的東西比較耗時還是開個執行緒比較好,Timer還是會阻塞主執行緒的執行,更像是一種訊息的執行方式。當然比Handler的postDelay等方法更適合處理計劃任務。

13.Android應用Icon大小在不同解析度下定義

對於Android平臺來說,不同解析度下Icon的大小設計有著不同的要求,對於目前主流的HDPI即WVGA級別來說,通常hdpi的應用icon大小為72x72,而標準的mdpi即hvga為48x48,對於目前HTC和Motorola推出的一些QVGA的使用了ldpi,圖示為32x32,常見的Android圖示大小設計規範如下表所示:

Launcher
36 x 36 px
48 x 48 px
72 x 72 px

Menu
36 x 36 px
48 x 48 px
72 x 72 px

Status Bar
24 x 24 px
32 x 32 px
48 x 48 px

Tab
24 x 24 px
32 x 32 px
48 x 48 px

Dialog
24 x 24 px
32 x 32 px
48 x 48 px

List View
24 x 24 px
32 x 32 px
48 x 48 px

  對於android介面設計的安全色,如下表

圖片

而對於系統自帶預設程式的圖示,下面為png的透明格式,直接滑鼠右鍵另存為即可

圖片

看看sdk文件上的關於介面圖示的詳細說明。

14.Android控制元件美化Shape你會用嗎?

如果你對Android系統自帶的UI控制元件感覺不夠滿意,可以嘗試下自定義控制元件,我們就以Button為例,很早以前Android123就寫到過Android Button按鈕控制元件美化方法裡面提到了xml的selector構造。當然除了使用drawable這樣的圖片外今天Android開發網談下自定義圖形shape的方法,對於Button控制元件Android上支援以下幾種屬性shape、gradient、stroke、corners等。

  我們就以目前系統的Button的selector為例說下:

          <shape>
            <gradient
                android:startColor="#ff8c00"
                android:endColor="#FFFFFF"
                android:angle="270" />
            <stroke
                android:width="2dp"
                android:color="#dcdcdc" />
            <corners
                android:radius="2dp" />
            <padding
                android:left="10dp"
                android:top="10dp"
                android:right="10dp"
                android:bottom="10dp" />
        </shape>

    對於上面,這條shape的定義,分別為漸變,在gradient中startColor屬性為開始的顏色,endColor為漸變結束的顏色,下面的angle是角度。接下來是stroke可以理解為邊緣,corners為拐角這裡radius屬性為半徑,最後是相對位置屬性padding。

對於一個Button完整的定義可以為

  <?xml version="1.0" encoding="utf-8"?>
<selector
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape>
            <gradient
                android:startColor="#ff8c00"
                android:endColor="#FFFFFF"
                android:angle="270" />
            <stroke
                android:width="2dp"
                android:color="#dcdcdc" />
            <corners
                android:radius="2dp" />
            <padding
                android:left="10dp"
                android:top="10dp"
                android:right="10dp"
                android:bottom="10dp" />
        </shape>
    </item>

    <item android:state_focused="true" >
        <shape>
            <gradient
                android:startColor="#ffc2b7"
                android:endColor="#ffc2b7"
                android:angle="270" />
            <stroke
                android:width="2dp"
                android:color="#dcdcdc" />
            <corners
                android:radius="2dp" />
            <padding
                android:left="10dp"
                android:top="10dp"
                android:right="10dp"
                android:bottom="10dp" />
        </shape>
    </item>

    <item>      
        <shape>
            <gradient
                android:startColor="#ff9d77"
                android:endColor="#ff9d77"
                android:angle="270" />
            <stroke
                android:width="2dp"
                android:color="#fad3cf" />
            <corners
                android:radius="2dp" />
            <padding
                android:left="10dp"
                android:top="10dp"
                android:right="10dp"
                android:bottom="10dp" />
        </shape>
    </item>
</selector>

注意Android123提示大家,以上幾個item的區別主要是體現在state_pressed按下或state_focused獲得焦點時,噹噹來判斷顯示什麼型別,而沒有state_xxx屬性的item可以看作是常規狀態下。

15. Android開發者應該保持以下特質

Android123推薦新手應該遵循

  1. 深讀SDK文件

  2. 深讀SDK的APIDemo和Samples

  3. 掌握GIT開原始碼

  4. 多瞭解Android開源專案,學習別人的手法寫程式。

16. Android陣列排序常見方法

  Android的陣列排序方式基本上使用了Sun原生的Java API實現,常用的有Comparator介面實現compare方法和Comparable介面的compareTo方法,我們對於一個數組列表比如ArrayList可以通過這兩個介面進行排序和比較,這裡Android123給大家一個例子

private final Comparator cwjComparator = new Comparator() {

        private final Collator   collator = Collator.getInstance();
        public final int compare(Object a, Object b) {
            CharSequence  a = ((Item) a).sName;
            CharSequence  b = ((Item) b).sID;
            return collator.compare(a, b);
        }
    };

我們的ArrayList物件名為mList,則執行排序可以呼叫方法

Collections.sort(mList, cwjComparator);

17.Android控制元件TextProgressBar進度條上顯文字

Android系統的進度條控制元件預設的設計的不是很周全,比如沒有包含文字的顯示,那麼如何在Android進度條控制元件上顯示文字呢? 來自Google內部的程式碼來了解下,主要使用的addView這樣的方法通過覆蓋一層Chronometer秒錶控制元件來實現,整個程式碼如下

   public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener {
    public static final String TAG = "TextProgressBar";
    static final int CHRONOMETER_ID = android.R.id.text1;
    static final int PROGRESSBAR_ID = android.R.id.progress;
    Chronometer mChronometer = null;
    ProgressBar mProgressBar = null;
    long mDurationBase = -1;
    int mDuration = -1;

    boolean mChronometerFollow = false;
    int mChronometerGravity = Gravity.NO_GRAVITY;
    public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public TextProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TextProgressBar(Context context) {
        super(context);
    }

    //Android開發網提示關鍵部分在這裡

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        int childId = child.getId();
        if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
            mChronometer = (Chronometer) child;
            mChronometer.setOnChronometerTickListener(this);
            // Check if Chronometer should move with with ProgressBar
            mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
            mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK);
        } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
            mProgressBar = (ProgressBar) child;
        }
    }

    @android.view.RemotableViewMethod
    public void setDurationBase(long durationBase) {
        mDurationBase = durationBase;
        if (mProgressBar == null || mChronometer == null) {
            throw new RuntimeException("Expecting child ProgressBar with id " +
                    "'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
        }
        // Update the ProgressBar maximum relative to Chronometer base
        mDuration = (int) (durationBase - mChronometer.getBase());
        if (mDuration <= 0) {
            mDuration = 1;
        }
        mProgressBar.setMax(mDuration);
    }
    public void onChronometerTick(Chronometer chronometer) {
        if (mProgressBar == null) {
            throw new RuntimeException(
                "Expecting child ProgressBar with id 'android.R.id.progress'");
        }
        // Stop Chronometer if we're past duration
        long now = SystemClock.elapsedRealtime();
        if (now >= mDurationBase) {
            mChronometer.stop();
        }

        int remaining = (int) (mDurationBase - now);
        mProgressBar.setProgress(mDuration - remaining);
        if (mChronometerFollow) {
            RelativeLayout.LayoutParams params;
            params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
            int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin);
            int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
                    mProgressBar.getMax()) + params.leftMargin;
            int adjustLeft = 0;
            int textWidth = mChronometer.getWidth();
            if (mChronometerGravity == Gravity.RIGHT) {
                adjustLeft = -textWidth;
            } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
                adjustLeft = -(textWidth / 2);
            }
            leadingEdge += adjustLeft;
            int rightLimit = contentWidth - params.rightMargin - textWidth;
            if (leadingEdge < params.leftMargin) {
                leadingEdge = params.leftMargin;
            } else if (leadingEdge > rightLimit) {
                leadingEdge = rightLimit;
            }
            params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
            params.leftMargin = leadingEdge;
            mChronometer.requestLayout();
        }
    }
}

18. Android記憶體管理-SoftReference的使用

很多時候我們需要考慮Android平臺上的記憶體管理問題,Dalvik VM給每個程序都分配了一定量的可用堆記憶體,當我們處理一些耗費資源的操作時可能會產生OOM錯誤(OutOfMemoryError)這樣的異常,Android123觀察了下國內的類似Market客戶端設計,基本上都沒有采用很好的記憶體管理機制和快取處理。

  如果細心的網友可能發現Android Market客戶端載入時,每個列表項的圖示是非同步重新整理顯示的,但當我們快速的往下滾動到一定數量比如50個,再往回滾動時可能我們看到了部分App的圖示又重新開始載入,當然這一過程可能是從SQLite資料庫中快取的,但是在記憶體中已經通過類似SoftReference的方式管理記憶體。

  在Java中記憶體管理,引用分為四大類,強引用HardReference、弱引用WeakReference、軟引用SoftReference和虛引用PhantomReference。它們的區別也很明顯,HardReference物件是即使虛擬機器記憶體吃緊丟擲OOM也不會導致這一引用的物件被回收,而WeakReference等更適合於一些數量不多,但體積稍微龐大的物件,在這四個引用中,它是最容易被垃圾回收的,而我們對於顯示類似Android Market中每個應用的App Icon時可以考慮使用SoftReference來解決記憶體不至於快速回收,同時當記憶體短缺面臨Java VM崩潰丟擲OOM前時,軟引用將會強制回收記憶體,最後的虛引用一般沒有實際意義,僅僅觀察GC的活動狀態,對於測試比較實用同時必須和ReferenceQueue一起使用。

  對於一組資料,我們可以通過HashMap的方式來新增一組SoftReference物件來臨時保留一些資料,同時對於需要反覆通過網路獲取的不經常改變的內容,可以通過本地的檔案系統或資料庫來儲存快取,希望給國內做App Store這樣的客戶端一些改進建議。

19. 反射在Android開發中的利弊

由於Android 2.2的推出,很多新的API加入導致很多專案移植需要考慮使用Java的反射機制Reflection來動態呼叫,動態呼叫的好處就是不需要使用引用檔案,直接通過JDK中宣告好的方法直接呼叫,本身原理基於JVM的,從Java 1.5開始支援,原理上就是根據類名而不例項化物件的情況下,獲得物件的方法或屬性而直接呼叫。

  Android開發時反射能幫助我們多少?

  1. 有些網友可能發現Android的SDK比較封閉,很多敏感的方法常規的使用者無法編譯,我們如果翻看了程式碼直接在反射中宣告動態呼叫即可。比如很多internal或I開頭的AIDL介面均可以通過反射輕鬆呼叫。

  2. 反射對於Android123來說更重要的是考慮到應用的相容性,我們目前主要相容從Android 1.5到2.2的專案,API Level從3到8可以方便的擴充,呼叫前我們預留一個標誌位宣告該API的最低以及最高的API Level為多少可以呼叫。

  3. 對於除錯Java的反射是功臣了,在Logcat中我們可以看到出錯的地方肯定有類似java.lang.reflect.XXX的字樣,這種自檢機制可以幫助我們方便的除錯Android應用程式。

  反射的缺點有哪些?

  1. 因為是動態執行的,效率自然沒有預編譯時引用現有的庫效率高,就像平時我們Win32開發時,可以不用h檔案,直接通過GetProcAddress一樣去動態獲取方法的地址。當然效率要根據複雜程度而決定,一般稍微複雜的處理效能損失可能超過20%,對於一些複雜的涉及Java自動型別轉換判斷,執行時間可能是直接引用的上千倍,所以最終我們除錯時必須考慮效能問題。

  2. 因為反射是動態的,所以需要處理很多異常,不然Dalvik崩潰出Force Close的概率會大很多,很簡單的一個反射就需要至少3個異常捕獲,本身try-catch效率就不是很高,自然進一步影響執行效率,對於Android開發我們必須考慮這些問題。

  3. 反射因為導致程式碼臃腫,自然稍微複雜的幾個方法實用反射將會導致程式碼可讀性和維護性降低,如果很抽象的呼叫Android開發網強烈不推薦這種方法。

  最後要說的是Reflection並不是Java的專利,微軟的.Net也同樣支援,同時更多的動態語言如Ruby等均支援這一特性。

20.AsyncTask對比Thread加Handler

很多網友可能發現Android平臺很多應用使用的都是AsyncTask,而並非Thread和Handler去更新UI,這裡Android123給大家說下他們到底有什麼區別,我們平時應該使用哪種解決方案。從Android 1.5開始系統將AsyncTask引入到android.os包中,過去在很早1.1和1.0 SDK時其實官方將其命名為UserTask,其內部是JDK 1.5開始新增的concurrent庫,做過J2EE的網友可能明白併發庫效率和強大性,比Java原始的Thread更靈活和強大,但對於輕量級的使用更為佔用系統資源。Thread是Java早期為實現多執行緒而設計的,比較簡單不支援concurrent中很多特性在同步和執行緒池類中需要自己去實現很多的東西,對於分散式應用來說更需要自己寫排程程式碼,而為了Android UI的重新整理Google引入了Handler和Looper機制,它們均基於訊息實現,有事可能訊息佇列阻塞或其他原因無法準確的使用。

  Android開發網推薦大家使用AsyncTask代替Thread+Handler的方式,不僅呼叫上更為簡單,經過實測更可靠一些,Google在Browser中大量使用了非同步任務作為處理耗時的I/O操作,比如下載檔案、讀寫資料庫等等,它們在本質上都離不開訊息,但是AsyncTask相比Thread加Handler更為可靠,更易於維護,但AsyncTask缺點也是有的比如一旦執行緒開啟即dobackground方法執行後無法給執行緒傳送訊息,僅能通過預先設定好的標記來控制邏輯,當然可以通過執行緒的掛起等待標誌位的改變來通訊,對於某些應用Thread和Handler以及Looper可能更靈活。

21. Android Drawable疊加處理方法

大家可能知道Bitmap的疊加處理在Android平臺中可以通過Canvas一層一層的畫就行了,而Drawable中如何處理呢? 除了使用BitmapDrawable的getBitmap方法將Drawable轉換為Bitmap外,今天Android123給大家說下好用簡單的LayerDrawable類,LayerDrawable顧名思義就是層圖形物件。下面直接用一個簡單的程式碼表示:

    Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.cwj);
    Drawable[] array = new Drawable[3];

     array[0] = new PaintDrawable(Color.BLACK); //黑色
     array[1] = new PaintDrawable(Color.WHITE); //白色
     array[2] = new BitmapDrawable(bm); //點陣圖資源
    LayerDrawable ld = new LayerDrawable(array); //引數為上面的Drawable陣列
        ld.setLayerInset(1, 1, 1, 1, 1);  //第一個引數1代表陣列的第二個元素,為白色
        ld.setLayerInset(2, 2, 2, 2, 2); //第一個引數2代表陣列的第三個元素,為點陣圖資源
    mImageView.setImageDrawable(ld);

  上面的方法中LayerDrawable是關鍵,Android開發網提示setLayerInset方法原型為public void setLayerInset (int index, int l, int t, int r, int b) 其中第一個引數為層的索引號,後面的四個引數分別為left、top、right和bottom。對於簡單的圖片合成我們可以將第一和第二層的PaintDrawable換成BitmapDrawable即可實現簡單的圖片合成。

22. onRetainNonConfigurationInstance和getLastNonConfigurationInstance

很多網友可能知道Android橫豎屏切換時會觸發onSaveInstanceState,而還原時會產生onRestoreInstanceState,但是Android的Activity類還有一個方法名為onRetainNonConfigurationInstance和getLastNonConfigurationInstance這兩個方法。

   我們可以通過  onRetainNonConfigurationInstance 代替 onSaveInstanceState,比如距離2

  @Override
  public Object onRetainNonConfigurationInstance()
{   
       //這裡需要儲存的內容,在切換時不是bundle了,我們可以直接通過Object來代替
      return obj;
}

在恢復視窗時,我們可以不使用 onRestoreInstanceState,而代替的是 getLastNonConfigurationInstance 方法。我們可以直接在onCreate中使用,比如

  Object obj = getLastNonConfigurationInstance();     最終obj的內容就是上次切換時的內容。

  這裡Android123提醒大家,每次Activity橫豎屏切換時onCreate方法都會被觸發。

23. Android中String資原始檔的format方法

很多時候我們感性Google在設計Android時遵守了大量MVC架構方式,可以讓寫公共程式碼、美工和具體邏輯開發人員獨立出來。有關Android的資原始檔values/strings.xml中如何實現格式化字串呢? 這裡Android123舉個簡單的例子,以及最終可能會用到哪些地方。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">cwj_Demo</string>
    <string name="hello">android開發網</string>
</resources> 

上面是一段簡單的字串資原始檔,沒有用到格式化,因為比較簡單直接描述了意思,當我們設計一個類似 Delete xxx File ? 的時候,我們可能需要在Java中動態獲取 xxx 的名稱,所以定義資源時使用格式化可以輕鬆解決,不需要一堆String去拼接或StringBuffer一個一個append這樣的愚蠢方法,看例子

    <string name="alert">Delete %1$s File</string>   這裡%1$s代表這是一個字串型的,如果是整數型可以寫為%1$d,類似printf這樣的格式化字串函式,當然如果包含了多個需要格式化的內容,則第二個可以寫為%2$s或%2$d了,那麼最終在Java中如何呼叫呢? 看下面的例子:

   例一: 整數型的

  <string name="alert">I am %1$d years old</string>  定義的是這樣的

   當然,我們杜絕意外情況,比如冒出個secret這樣的string型別的,注意上面是%1$d不是%1$s,所以預設標準的合併成為

   int nAge=23;

   String sAgeFormat = getResources().getString(R.string.alert); 

   String sFinalAge = String.format(sAgeFormat, nAge); 

    這樣執行完後,就組成了 I am 23 years old,是不是很方便啊.  當然了,下面看下String字串時的情況.

  例二: 字串型的

  String sName="cwj"

  String sCity="Shanghai"

   資源定義為   <string name="alert2">My name is %1$s , I am form %2$s</string> 

   則Java中只需要

  String sInfoFormat = getResources().getString(R.string.alert2); 

  String sFinalInfo=String.format(sInfoFormat, sName, sCity); 

  我們看到了整個,整個定義類似MFC的CString::Format或Mac OS中的NSLog,但是需要顯示類似C#中那樣顯示的標出引數的數字,比如%1或%n,這裡數字代表引數的第n個。本行最終sFinalInfo顯示的內容為

  My name is cwj , I am form Shanghai 。當然了你有什麼不懂的地方可以來函至 [email protected]

24. Android工程內嵌資原始檔的兩種方法

Android軟體一般處理大的資源通過sdcard比如線上下載資源到sdcard,而apk中內嵌資源或二進位制檔案時一般使用下面的兩種方法:

  方法一

  res/raw目錄下存放,比如cwj.dat一個二進位制檔案,我們可以讀取可以直接  InputStream is=context.getResources().openRawResource(R.raw.cwj); 

  方法二

  工程根目錄下的assets資料夾中存放,比如assets/cwj.dat 這樣我們使用下面的程式碼

  AssetManager am = context.getAssets(); 
  InputStream is = am.open(cwj.dat);  

  這裡Android123提示大家Google的Android系統處理Assert有個bug,在AssertManager中不能處理單個超過1MB的檔案,不然會報異常具體數值大家可以測試下傳個稍大的檔案,我們在兩年前的文章中有提到,而第一種raw沒這個限制可以放個4MB的Mp3檔案沒問題。

25. Android自定義View以及layout屬性全攻略

對於Android系統的自定義View可能大家都熟悉了,對於自定義View的屬性新增,以及Android的Layout的名稱空間問題,很多網友還不是很清楚,今天Android123一起再帶大家溫習一下

  CwjView myView=new CwjView(context);

  如果用於遊戲或整個窗體的介面,我們可能直接在onCreate中setContentView(myView); 當然如果是控制元件,我們可能會需要從Layout的xml中宣告,比如

  <cn.com.android123.CwjView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
  />

  當然,我們也可以直接從父類宣告比如

  <View class="cn.com.android123.CwjView"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
  />

上面我們僅用了父類View的兩個屬性,均來自android名稱空間,而名稱為layout_width或layout_height,我們自定義的控制元件可能有更多的功能,比如

    <cn.com.android123.CwjView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
  cwj:age="22"
   cwj:university="sjtu"
   cwj:city="shanghai"
   />

我們可以看到上面的三個屬性,是我們自定義的。作為標準xml規範,可能還包含了類似 xmlns:android="http://schemas.android.com/apk/res/android"  這樣的語句,對於定義完整的View,我們的名稱空間為cwj,這裡可以寫為 xmlns:cwj=http://schemas.android.com/apk/res/cn.com.android123.cwjView 或 xmlns:cwj=http://schemas.android.com/apk/res/android 都可以

  對於定義的cwj名稱空間和age、university以及city的三個屬性我們如何定義呢? 在工程的res/values目錄中我們新建一個cwj_attr.xml檔案,編碼方式為utf-8是一個好習慣,內容如下

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <declare-styleable name="CwjView">
  <attr name="age" format="integer" />
  <attr name="city" format="string" />
  <attr name="university" format="string" />
  </declare-styleable>
</resources>

  這裡我們可能對format不是很熟悉,目前Android系統內建的格式型別有integer比如ProgressBar的進度值,float比如RatingBar的值可能是3.5顆星,boolean比如ToggleButton的是否勾選,string比如TextView的text屬性,當然除了我們常見的基礎型別外,Android的屬性還有特殊的比如color是用於顏色屬性的,可以識別為#FF0000等型別,當然還有dimension的尺寸型別,比如23dip,15px,18sp的長度單位,還有一種特殊的為reference,一般用於引用@+id/cwj @drawable/xxx這樣的型別。

  當然什麼時候用reference呢? 我們就以定義一個顏色為例子,

  <attr name="red" format="color|reference" />  這裡我們用了邏輯或的運算子,定義的紅色是顏色型別的,同時可以被引用

  當然,對於我們自定義的類中,我們需要使用一個名為obtainStyledAttributes的方法來獲取我們的定義。在我們自定義View的構造方法(Context context, AttributeSet attrs)的過載型別中可以用

  public CwjView(Context context, AttributeSet attrs) {
  super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs,
          R.styleable.cwj_attr);
        mAge = a.getInteger(R.styleable.CwjView_age, 22);
        mCity = a.getString(R.styleable.CwjView_city, "shanghai");
        mUniversity= a.getString(R.styleable.CwjView_university, "sjtu");
        a.recycle(); //Android123提示大家不要忘了回收資源

}

這樣類的全域性成員變數 mAge、mCity就獲取了我們需要的內容,當然根據layout中的數值我們自定義的CwjView需要動態的處理一些資料的情況,可以使用AttributeSet類的getAttributeResourceValue方法獲取。

public CwjView(Context context, AttributeSet attrs)
{
  super(context, attrs);
  resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView", "age", 100); 
  resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView", "city", "shanghai");
  //resID就可以任意使用了
}

以上兩種方法中,引數的最後一個數值為預設的,如果您有不明白的地方可以來函到 [email protected] 我們會在第一時間回覆。

26. 自定義Android主題風格theme.xml方法

在Android中可以通過自定義主題風格方式來實現個性化以及複用,首先我們建立theme.xml主題檔案,儲存位置為工程的res/values/theme.xml ,這裡我們可以可以為主題起一個名稱,比如CWJ,這裡去除了xml的檔案頭<?xml version="1.0" encoding="utf-8"?>這行,我們在工程中只需在androidmanifest.xml檔案的Activity節點中加入android:theme="@style/Theme.CWJ" 屬性,則這個Activity就使用了這種主題風格,整個xml的關鍵程式碼如下:

<resources>
    <style name="Theme.CWJ" parent="android:Theme">
        <item name="android:windowBackground">@drawable/android123</item>
    </style>
</resources>

  其中上面的程式碼中,我們定義設定全域性android:windowBackground即背景值為/res/drawable中的android123圖片為背景,更多的屬性定義可以參考view的layout xml屬性設定,比如我們設定所有字型顏色、大體大小和樣式,可以在style節點中加入

  <item name="android:textColor">#fff</item>
  <item name="android:textSize">14sp</item>
  <item name="android:textStyle">bold</item> 

當然我們可以將上面的android123的圖片改進下,使用一個xml檔案替代,比如使用bitmap物件,則/res/drawable/android123.xml的完整程式碼變為

  <?xml version="1.0" encoding="utf-8"?>

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
     android:src="@drawable/cwj_image"
     android:tileMode="repeat" /> 

  這裡我們使用了一個bitmap物件來解析cwj_image圖片,當然這裡可以識別各種型別的圖片,其中android:tileMode是bitmap的內部屬性,其中tileMode設定為repeat代表重複,這樣可以節省bitmap資源,比如我們的背景是一層樓,那麼全屏可以顯示同樣的為5層效果,而圖片僅是一層大小,對於資源利用相對更高。

  當然bitmap的屬性tileMode的值為repeat外還有其他的值比如clamp、mirror,這些值並沒有在SDK中並沒有找到定義,通過上次Android開發網的 Android自定義View以及layout屬性全攻略 一文,我們可以聯想到bitmap屬於android.graphics.Bitmap 包,由於是android框架,所以下載git的base包,找到該類,類的例項化時android123已經在 Android自定義View以及layout屬性全攻略 說的很清楚,所以我們定位到res\values中找到attr.xml有關bitmap的定義即可,有關bitmap的更多屬性如  antialias、filter和dither都可以找到使用。

27. android除錯工具monkey壓力測試實戰

很多Android開發者可能因為沒有充分測試自己的軟體造成很容易出現FC(Force Close)的問題,這裡我們可以通過使用Android韌體中自帶的monkey工具來做軟體的壓力測試,monkey工具可以模擬各種按鍵,觸屏,軌跡球、activity等事件,這裡Android123提示大家說白了monkey就是一個小猴子隨機狂玩你的android軟體,看看會不會產生異常。

  具體的使用我們通過Android SDK給我們的adb除錯橋連結裝置或模擬器,進入Linux Shell狀態,當然我們可以輸入adb shell獲取裝置的shell,也可以直接通過adb命令執行,比如說adb shell monkey來檢視monkey工具中的引數說明,如圖: 

圖片

我們要測試的apk檔案要在android裝置中已經安裝,當然模擬器中也可以測試的。執行adb shell monkey -p cn.com.android123.cwj -v 100 我們執行這句的中包含了p引數,這裡代表已安裝軟體的packageName,而v代表檢視monkey生成的詳細隨機事件名,最後的數字100為我們測試的隨機事件數量為100.有關更多的測試方法,請檢視上圖中的引數,整個測試比較簡單單很有效,不妨試試。

28. 自定義View

有關Android的自定義View的框架今天我們一起討論下,對於常規的遊戲,我們在View中需要處理以下幾種問題: 1.控制事件 2.重新整理View 3. 繪製View

  1. 對於控制事件今天我們只處理按鍵事件onKeyDown,以後的文章中將會講到螢幕觸控的具體處理onTouchEvent以及Sensor重力感應等方法。

  2. 重新整理view的方法這裡主要有invalidate(int l, int t, int r, int b) 重新整理區域性,四個引數分別為左、上、右、下。整個view重新整理 invalidate(),重新整理一個矩形區域 invalidate(Rect dirty) ,重新整理一個特性Drawable, invalidateDrawable(Drawable drawable) ,執行invalidate類的方法將會設定view為無效,最終導致onDraw方法被重新呼叫。由於今天的view比較簡單,Android123提示大家如果線上程中重新整理,除了使用handler方式外,可以在Thread中直接使用postInvalidate方法來實現。

  3. 繪製View主要是onDraw()中通過形參canvas來處理,相關的繪製主要有drawRect、drawLine、drawPath等等。view方法內部還重寫了很多介面,其回撥方法可以幫助我們判斷出view的位置和大小,比如onMeasure(int, int) Called to determine the size requirements for this view and all of its children.  、onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children 和onSizeChanged(int, int, int, int) Called when the size of this view has changed. 具體的作用,大家可以用Logcat獲取當view變化時每個形參的變動。

  下面cwjView是我們為今後遊戲設計的一個簡單自定義View框架,我們可以看到在Android平臺自定義view還是很簡單的,同時Java支援多繼承可以幫助我們不斷的完善複雜的問題。

public class cwjView extends View {

    public cwjView(Context context) {
      super(context);
      setFocusable(true); //允許獲得焦點
      setFocusableInTouchMode(true); //獲取焦點時允許觸控
   }

   @Override
   protected Parcelable onSaveInstanceState() {  //處理視窗儲存事件
      Parcelable pSaved = super.onSaveInstanceState();
      Bundle bundle = new Bundle();
     //dosomething
      return bundle;
   }
   @Override
   protected void onRestoreInstanceState(Parcelable state) {  //處理視窗還原事件
      Bundle bundle = (Bundle) state;

     //dosomething
     super.onRestoreInstanceState(bundle.getParcelable("cwj"));
      return;
   }
       @Override
   protected void onSizeChanged(int w, int h, int oldw, int oldh) //處理視窗大小變化事件
   {
      super.onSizeChanged(w, h, oldw, oldh);
   }

   @Override
   protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) 
   {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec); //如果不讓父類處理記住呼叫setMeasuredDimension
   }
   @Override
   protected void onLayout (boolean changed, int left, int top, int right, int bottom)
   {
    super.onLayout (changed,left,top, ight,bottom) ;
   }

   @Override
   protected void onDraw(Canvas canvas) {
      Paint bg = new Paint();
      bg.setColor(Color.Red);
      canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //將view的左上角四分之一填充為紅色 
   }

   @Override
   public boolean onTouchEvent(MotionEvent event) {
         return super.onTouchEvent(event); //讓父類處理螢幕觸控事件
   }

   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) { //處理按鍵事件,響應的軌跡球事件為 public boolean onTrackballEvent (MotionEvent event)
      switch (keyCode) {
      case KeyEvent.KEYCODE_DPAD_UP:
         break;
      case KeyEvent.KEYCODE_DPAD_DOWN:
         break;
      case KeyEvent.KEYCODE_DPAD_LEFT:
         break;
      case KeyEvent.KEYCODE_DPAD_RIGHT:
         break;
      case KeyEvent.KEYCODE_DPAD_CENTER: //處理中鍵按下
         break;
      default:
         return super.onKeyDown(keyCode, event);
      }
      return true;
   }

}

  上面我們可以看到onMeasure使用的是父類的處理方法,如果我們需要解決自定義View的大小,可以嘗試下面的方法

   @Override
   protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) 
   {
      height = View.MeasureSpec.getSize(heightMeasureSpec);
      width = View.MeasureSpec.getSize(widthMeasureSpec);