1. 程式人生 > >DDMS的使用、記憶體溢位的除錯和模擬器的啟動命令引數

DDMS的使用、記憶體溢位的除錯和模擬器的啟動命令引數

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

Android DDMS如何使用?

By: 海市蜃樓 | In: Android開發

112009

DDMS 的全稱是Dalvik Debug Monitor Service,它為我們提供例如:為測試裝置截圖,針對特定的程序檢視正在執行的執行緒以及堆資訊、Logcat、廣播狀態資訊、模擬電話呼叫、接收SMS、虛擬地理座標等等。

如何啟動 DDMS

DDMS 工具存放在SDK – tools/路徑下,啟動DDMS方法如下:

  1. 直接雙擊ddms.bat執行;
  2. 在Eclipes除錯程式的過程中啟動DDMS,在Eclipes中的介面如下:
    Eclipes-DDMS
    選擇“Other”,介面如下:
    Open-Perspective
    雙擊DDMS就可以啟動了。

DDMS對Emulator和外接測試機有同等效用。如果系統檢測到它們(VM)同時執行,那麼DDMS將會預設指向 Emulator。以上2種啟動後的操作有些不一樣,建議分別嘗試下。

DDMS 的工作原理

DDMS將搭建起IDE與測試終端(Emulator 或者connected device)的連結,它們應用各自獨立的埠監聽偵錯程式的資訊,DDMS可以實時監測到測試終端的連線情況。當有新的測試終端連線後,DDMS將捕捉到終端的ID,並通過adb建立偵錯程式,從而實現傳送指令到測試終端的目的。
DDMS-Device


DDMS監聽第一個終端App程序的埠為8600,APP程序將分配8601,如果有更多終端或者更多APP程序將按照這個順序依次類推。DDMS通過8700埠(”base port”)接收所有終端的指令。

下邊通過GUI詳細瞭解DDMS的一些功能

Devices

在GUI的左上角可以看到標籤為”Devices”的面板,這裡可以檢視到所有與DDMS連 接的終端的詳細資訊,以及每個終端正在執行的APP程序,每個程序最右邊相對應的是與偵錯程式連結的埠。因為Android是基於Linux核心開發的操 作平臺,同時也保留了Linux中特有的程序ID,它介於程序名和埠號之間。
DDMS-Device
在面板的右上角有一排很重要的按鍵他們分別是Debug the selected process、Update Threads、Update Heap、Stop Process和ScreenShot。

Emulator Control

通過這個面板的一些功能可以非常容易的使測試終端模擬真實手機所具備的一些互動功能,比如:接聽電話,根據選項模擬各種不同網路情況,模擬接受SMS訊息和傳送虛擬地址座標用於測試GPS功能等。
DDMS-Control
Telephony Status: 通過選項模擬語音質量以及訊號連線模式。
Telephony Actions: 模擬電話接聽和傳送SMS到測試終端。
Location Control: 模擬地理座標或者模擬動態的路線座標變化並顯示預設的地理標識,可以通過以下3種方式:

  • Manual: 手動為終端傳送二維經緯座標。
  • GPX: 通過GPX檔案匯入序列動態變化地理座標,從而模擬行進中GPS變化的數值。
  • KML: 通過KML檔案匯入獨特的地理標識,並以動態形式根據變化的地理座標顯示在測試終端。

Threads、Heap、File Exporler

DDMS-THF
這幾項,我們在其他開發工具中也經常使用,就在不此詳細說明了。通過File Exporler可以檢視Android模擬器中的檔案,可以很方便的匯入/出文件。

Locate、Console

DDMS-LC
Locate:顯示輸出的除錯資訊,詳見Android下如何除錯程式?
Console:是Android模擬器輸出的資訊,載入程式等資訊;

使用DDMS模擬傳送簡訊,操作過程如下:

在Emulator Control\Telephony Actions 中輸入以下內容
DDMS-SMS
單擊發送後,在Android模擬器中開啟Messaging,看到下面的簡訊:
DDMS-Message
單擊新簡訊,詳細檢視簡訊內容:
AVD-Message
中文顯示為亂碼,在未來的開發中,我們必須要注意中文字元的問題。

總結說明

DDMS是我們開發人員最好的除錯工具,它將是每個從事Android開發的人員都不可缺少的。


Using DDMS

IN THIS DOCUMENT

  1. Running DDMS
  2. How DDMS Interacts with a Debugger
  3. Using DDMS
    1. Viewing heap usage for a process
    2. Tracking memory allocation of objects
    3. Working with an emulator or device's file system
    4. Examining thread information
    5. Starting method profiling
    6. Using the Network Traffic tool
    7. Using LogCat
    8. Emulating phone operations and location

Android ships with a debugging tool called the Dalvik Debug Monitor Server (DDMS), which provides port-forwarding services, screen capture on the device, thread and heap information on the device, logcat, process, and radio state information, incoming call and SMS spoofing, location data spoofing, and more. This page provides a modest discussion of DDMS features; it is not an exhaustive exploration of all the features and capabilities.

Running DDMS


DDMS is integrated into Eclipse and is also shipped in the tools/ directory of the SDK. DDMS works with both the emulator and a connected device. If both are connected and running simultaneously, DDMS defaults to the emulator.

  • From Eclipse: Click Window > Open Perspective > Other... > DDMS.
  • From the command line: Type ddms (or ./ddms on Mac/Linux) from the tools/directory.

How DDMS Interacts with a Debugger


On Android, every application runs in its own process, each of which runs in its own virtual machine (VM). Each VM exposes a unique port that a debugger can attach to.

When DDMS starts, it connects to adb. When a device is connected, a VM monitoring service is created between adb and DDMS, which notifies DDMS when a VM on the device is started or terminated. Once a VM is running, DDMS retrieves the VM's process ID (pid), via adb, and opens a connection to the VM's debugger, through the adb daemon (adbd) on the device. DDMS can now talk to the VM using a custom wire protocol.

DDMS assigns a debugging port to each VM on the device. Typically, DDMS assigns port 8600 for the first debuggable VM, the next on 8601, and so on. When a debugger connects to one of these ports, all traffic is forwarded to the debugger from the associated VM. You can only attach a single debugger to a single port, but DDMS can handle multiple, attached debuggers.

By default, DDMS also listens on another debugging port, the DDMS "base port" (8700, by default). The base port is a port forwarder, which can accept VM traffic from any debugging port and forward it to the debugger on port 8700. This allows you to attach one debugger to port 8700, and debug all the VMs on a device. The traffic that is forwarded is determined by the currently selected process in the DDMS Devices view.

The following screenshot shows a typical DDMS screen in Eclipse. If you are starting DDMS from the command line, the screen is slightly different, but much of the functionality is identical. Notice that the highlighted process,com.android.email, that is running in the emulator has the debugging port 8700 assigned to it as well as 8606. This signifies that DDMS is currently forwarding port 8606 to the static debugging port of 8700.

Figure 1. Screenshot of DDMS

If you are not using Eclipse and ADT, read Configuring your IDE to attach to the debugging port, for more information on attaching your debugger.

Tip: You can set a number of DDMS preferences in File > Preferences. Preferences are saved to$HOME/.android/ddms.cfg.

Known debugging issues with Dalvik
Debugging an application in the Dalvik VM should work the same as it does in other VMs. However, when single-stepping out of synchronized code, the "current line" cursor may jump to the last line in the method for one step.

Using DDMS


The following sections describe how to use DDMS and the various tabs and panes that are part of the DDMS GUI. The Eclipse version and the command line version have minor UI differences, but the same functionality. For information on running DDMS, see the previous section in this document,  Running DDMS.

Viewing heap usage for a process

DDMS allows you to view how much heap memory a process is using. This information is useful in tracking heap usage at a certain point of time during the execution of your application.

To view heap usage for a process:

  1. In the Devices tab, select the process that you want to see the heap information for.
  2. Click the Update Heap button to enable heap information for the process.
  3. In the Heap tab, click Cause GC to invoke garbage collection, which enables the collection of heap data. When the operation completes, you will see a group of object types and the memory that has been allocated for each type. You can click Cause GC again to refresh the data.
  4. Click on an object type in the list to see a bar graph that shows the number of objects allocated for a particular memory size in bytes.

Tracking memory allocation of objects

DDMS provides a feature to track objects that are being allocated to memory and to see which classes and threads are allocating the objects. This allows you to track, in real time, where objects are being allocated when you perform certain actions in your application. This information is valuable for assessing memory usage that can affect application performance.

To track memory allocation of objects:

  1. In the Devices tab, select the process that you want to enable allocation tracking for.
  2. In the Allocation Tracker tab, click the Start Tracking button to begin allocation tracking. At this point, anything you do in your application will be tracked.
  3. Click Get Allocations to see a list of objects that have been allocated since you clicked on the Start Tracking button. You can click on Get Allocations again to append to the list new objects that that have been allocated.
  4. To stop tracking or to clear the data and start over, click the Stop Tracking button.
  5. Click on a specific row in the list to see more detailed information such as the method and line number of the code that allocated the object.

Working with an emulator or device's file system

DDMS provides a File Explorer tab that allows you to view, copy, and delete files on the device. This feature is useful in examining files that are created by your application or if you want to transfer files to and from the device.

To work with an emulator or device's file system:

  1. In the Devices tab, select the emulator that you want to view the file system for.
  2. To copy a file from the device, locate the file in the File Explorer and click the Pull file button.
  3. To copy a file to the device, click the Push file button on the File Explorer tab.

Examining thread information

The Threads tab in DDMS shows you the currently running threads for a selected process.

  1. In the Devices tab, select the process that you want to examine the threads for.
  2. Click the Update Threads button.
  3. In the Threads tab, you can view the thread information for the selected process.

Starting method profiling

Method profiling is a means to track certain metrics about a method, such as number of calls, execution time, and time spent executing the method. If you want more granular control over where profiling data is collected, use thestartMethodTracing() and stopMethodTracing() methods. For more information about generating trace logs, seeProfiling and Debugging UIs.

Before you start method profiling in DDMS, be aware of the following restrictions:

  • Android 2.1 and earlier devices must have an SD card present and your application must have permission to write to the SD card.
  • Android 2.2 and later devices do not need an SD card. The trace log files are streamed directly to your development machine.

To start method profiling:

  1. On the Devices tab, select the process that you want to enable method profiling for.
  2. Click the Start Method Profiling button.
  3. Interact with your application to start the methods that you want to profile.
  4. Click the Stop Method Profiling button. DDMS stops profiling your application and opens Traceview with the method profiling information that was collected between the time you clicked on Start Method Profiling and Stop Method Profiling.

Using the Network Traffic tool

In Android 4.0, the 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. You can also distinguish between different traffic types by applying a “tag” to network sockets before use.

These tags are shown in a stack area chart in DDMS, as shown in figure 2:

Figure 2. Network Usage tab.

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 should look for short spikes that can be delayed, or that should cause a later transfer to be pre-empted.

To better identify the cause of transfer spikes, the TrafficStats API allows you to tag the data transfers occurring within a thread using setThreadStatsTag(), followed by manually tagging (and untagging) individual sockets using tagSocket()and untagSocket(). For example:

TrafficStats.setThreadStatsTag(0xF00D);TrafficStats.tagSocket(outputSocket);// Transfer data using socketTrafficStats.untagSocket(outputSocket);

Alternatively, the Apache HttpClient and URLConnection APIs included in the platform automatically tag sockets internally based on the active tag (as identified by getThreadStatsTag()). These APIs correctly tag/untag sockets when recycled through keep-alive pools. In the following example, setThreadStatsTag() sets the active tag to be 0xF00D. There can only be one active tag per thread. That is the value that will be returned by getThreadStatsTag() and thus used byHttpClient to tag sockets. The finally statement invokes clearThreadStatsTag() to clear the tag.

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.

Using LogCat

LogCat is integrated into DDMS, and outputs the messages that you print out using the Log class along with other system messages such as stack traces when exceptions are thrown. View the Reading and Writing Log Messages. topic for more information on how to log messages to the LogCat.

When you have set up your logging, you can use the LogCat feature of DDMS to filter certain messages with the following buttons:

  • Verbose
  • Debug
  • Info
  • Warn
  • Error

You can also setup your own custom filter to specify more details such as filtering messages with the log tags or with the process id that generated the log message. The add filter, edit filter, and delete filter buttons let you manage your custom filters.

Emulating phone operations and location

The Emulator control tab lets you simulate a phone's voice and data network status. This is useful when you want to test your application's robustness in differing network environments.

Changing network state, speed, and latency

The Telephony Status section of the Emulator controls tab lets you change different aspects of the phone's networks status, speed and latency. The following options are available to you and are effective immediately after you set them:

  • Voice - unregistered, home, roaming, searching, denied
  • Data - unregistered, home, roaming, searching, denied
  • Speed - Full, GSM, HSCSD, GPRS, EDGE, UMTS, HSDPA
  • Latency - GPRS, EDGE, UMTS

Spoofing calls or SMS text messages

The Telephony Actions section of the Emulator controls tab lets you spoof calls and messages. This is useful when you want to to test your application's robustness in responding to incoming calls and messages that are sent to the phone. The following actions are available to you:

  • Voice - Enter a number in the Incoming number field and click Call to send a simulated call to the emulator or phone. Click the Hang up button to terminate the call.
  • SMS - Enter a number in the Incoming number field and a message in the Message: field and click the Send button to send the message.

Setting the location of the phone

If your application depends on the location of the phone, you can have DDMS send your device or AVD a mock location. This is useful if you want to test different aspects of your application's location specific features without physically moving. The following geolocation data types are available to you:

  • Manual - set the location by manually specifying decimal or sexagesimal longitude and latitude values.
  • GPX - GPS eXchange file
  • KML - Keyhole Markup Language file
 

DDMS的使用、記憶體溢位的除錯和模擬器的啟動命令引數

  346人閱讀  評論(0)  收藏  舉報 android eclipse listview eclipse外掛 system 工具

目錄(?)[+]

 DDMS 的全稱是Dalvik Debug Monitor Service,它為我們提供例如:為測試裝置截圖,針對特定的程序檢視正在執行的執行緒以及堆資訊、Logcat、廣播狀態資訊、模擬電話呼叫、接收SMS、虛擬地理座標等等。DDMS為IDE和emultor及真正的android裝置架起來了一座橋樑。開發人員可以通過DDMS看到目標機器上執行的程序/現成狀態,可以android的螢幕到開發機上,可以看程序的heap資訊,可以檢視logcat資訊,可以檢視程序分配記憶體情況,可以像目標機發送簡訊以及打電話,可以像android開發傳送地理位置資訊。可以像GDB一樣attach某一個程序除錯。 SDKàtools目錄下提供了ddms的完整版,直接雙擊ddms.bat執行即可。下面以Eclipse的DDMS perspective為例簡單介紹DDMS的功能。

      跟debug,java的perspective一樣,安裝好adt後會有一個DDMS得perspective,開啟即可。

clip_image001

      如果perspective裡沒有顯示DDMS,剛按如下步驟執行:

  •       點選上圖中DDMS圖示左邊的那個圖示,然後在下圖中如果有DDMS,剛選擇,如果沒有,剛選擇“其他”,然後在出現的視窗中雙擊“DDMS”即可。

clip_image003clip_image005

      注意:DDMS對Emulator和外接測試機有同等效用。如果系統檢測到它們(VM)同時執行,那麼DDMS將會預設指向 Emulator。以上2種啟動後的操作有些不一樣,建議分別嘗試下。

      DDMS 的工作原理

      DDMS將搭建起IDE與測試終端(Emulator 或者connected device)的連結,它們應用各自獨立的埠監聽偵錯程式的資訊,DDMS可以實時監測到測試終端的連線情況。當有新的測試終端連線後,DDMS將捕捉到終端的ID,並通過adb建立偵錯程式,從而實現傳送指令到測試終端的目的。

clip_image006

      DDMS監聽第一個終端App程序的埠為8600,APP程序將分配8601,如果有更多終端或者更多APP程序將按照這個順序依次類推。DDMS通過8700埠(“base port”)接收所有終端的指令。

      開啟後的視窗為:

clip_image008

      下邊通過GUI詳細瞭解DDMS的一些功能

      在GUI的左上角可以看到標籤為”Devices”的面板,這裡可以檢視到所有與DDMS連 接的終端的詳細資訊,以及每個終端正在執行的APP程序,每個程序最右邊相對應的是與偵錯程式連結的埠。因為Android是基於Linux核心開發的操 作平臺,同時也保留了Linux中特有的程序ID,它介於程序名和埠號之間。

      device視窗羅列模擬器中所有的程序,右上角那一排按鈕分別為:除錯某個程序,更新某個程序,更新程序堆疊資訊,停止某個程序,最後一個圖片按鈕時抓取android目前的螢幕。

clip_image006[1]

      當你選中某個程序,並按下除錯程序按鈕時,如果eclipse中有這個程序的程式碼,那就可以進行原始碼級別的除錯。有點像GDB attach。圖片抓取按鈕可以把當前android的顯示桌面抓到你的機器上,也是非常有用。

      右邊那個視窗中有threads, heap , file explorer選項卡。分別顯示執行緒統計資訊,棧資訊,以及android的檔案系統。

clip_image009

      file explorer非常有用,他可以把檔案上傳到android手機,或者從手機下載下來,也可以進行刪除操作。選中file explorer選項卡後,按下面三個按鈕便可實現對android手機檔案系統的上傳,下載,刪除操作。

clip_image010

      emulator control也是非常重要的,通過它可以像手機發送簡訊, 打電話,已經更新手機位置資訊。

            Telephony Status: 通過選項模擬語音質量以及訊號連線模式。 
            Telephony Actions: 模擬電話接聽和傳送SMS到測試終端。 
            Location Control: 模擬地理座標或者模擬動態的路線座標變化並顯示預設的地理標識,可以通過以下3種方式:

                  · Manual: 手動為終端傳送二維經緯座標。

                  · GPX: 通過GPX檔案匯入序列動態變化地理座標,從而模擬行進中GPS變化的數值。

                  · KML: 通過KML檔案匯入獨特的地理標識,並以動態形式根據變化的地理座標顯示在測試終端。

clip_image011

clip_image013

      LogCat:顯示輸出的除錯資訊。

      Console(控制檯):是Android模擬器輸出的資訊,載入程式等資訊;

      總結:

            eclipse adt目前提供的的ddms功能只是真正ddms的一小部分,你 可以直接使用tools下面的ddms來使用所有功能。其中有一個檢視程序記憶體分配的功能比較有用。

            另個要注意的是,在DDMS中模擬傳送簡訊時,中文顯示為亂碼,在未來的開發中,我們必須要注意中文字元的問題



Android 記憶體洩漏除錯

 

一、概述

    Java程式設計中經常容易被忽視,但本身又十分重要的一個問題就是記憶體使用的問題。Android應用主要使用Java語言編寫,因此這個問題也同樣會在Android開發中出現。本文不對Java程式設計問題做探討,而是對於在Android中,特別是應用開發中的此類問題進行整理。

    由於作者接觸Android時間並不是很長,因此如有敘述不當之處,歡迎指正。

 

二、Android(Java)中常見的容易引起記憶體洩漏的不良程式碼

 

    Android主要應用在嵌入式裝置當中,而嵌入式裝置由於一些眾所周知的條件限制,通常都不會有很高的配置,特別是記憶體是比較有限的。如果我們編寫的程式碼當中有太多的對記憶體使用不當的地方,難免會使得我們的裝置執行緩慢,甚至是宕機。為了能夠使得Android應用程式安全且快速的執行,Android的每個應用程式都會使用一個專有的Dalvik虛擬機器例項來執行,它是由Zygote服務程序孵化出來的,也就是說每個應用程式都是在屬於自己的程序中執行的。一方面,如果程式在執行過程中出現了記憶體洩漏的問題,僅僅會使得自己的程序被kill掉,而不會影響其他程序(如果是system_process等系統程序出問題的話,則會引起系統重啟)。另一方面Android為不同型別的程序分配了不同的記憶體使用上限,如果應用程序使用的記憶體超過了這個上限,則會被系統視為記憶體洩漏,從而被kill掉。Android為應用程序分配的記憶體上限如下所示:

位置: /ANDROID_SOURCE/system/core/rootdir/init.rc 部分指令碼

# Define the oom_adj values for the classes of processes that can be

# killed by the kernel.  These are used in ActivityManagerService.

    setprop ro.FOREGROUND_APP_ADJ 0

    setprop ro.VISIBLE_APP_ADJ 1

    setprop ro.SECONDARY_SERVER_ADJ 2

    setprop ro.BACKUP_APP_ADJ 2

    setprop ro.HOME_APP_ADJ 4

    setprop ro.HIDDEN_APP_MIN_ADJ 7

    setprop ro.CONTENT_PROVIDER_ADJ 14

    setprop ro.EMPTY_APP_ADJ 15

 

# Define the memory thresholds at which the above process classes will

# be killed.  These numbers are in pages (4k).

    setprop ro.FOREGROUND_APP_MEM 1536

    setprop ro.VISIBLE_APP_MEM 2048

    setprop ro.SECONDARY_SERVER_MEM 4096

    setprop ro.BACKUP_APP_MEM 4096

    setprop ro.HOME_APP_MEM 4096

    setprop ro.HIDDEN_APP_MEM 5120

    setprop ro.CONTENT_PROVIDER_MEM 5632

    setprop ro.EMPTY_APP_MEM 6144

 

# Write value must be consistent with the above properties.

# Note that the driver only supports 6 slots, so we have HOME_APP at the

# same memory level as services.

    write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15

 

    write /proc/sys/vm/overcommit_memory 1

    write /proc/sys/vm/min_free_order_shift 4

    write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144

 

    # Set init its forked children's oom_adj.

    write /proc/1/oom_adj -16

 

    正因為我們的應用程式能夠使用的記憶體有限,所以在編寫程式碼的時候需要特別注意記憶體使用問題。如下是一些常見的記憶體使用不當的情況。

 

(一) 查詢資料庫沒有關閉遊標

描述:

    程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小,對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會復現記憶體問題,這樣就會給以後的測試和問題排查帶來困難和風險。

 

示例程式碼:

Cursor cursor = getContentResolver().query(uri ...);

if (cursor.moveToNext()) {

    ... ... 

}

 

修正示例程式碼:

Cursor cursor = null;

try {

    cursor = getContentResolver().query(uri ...);

    if (cursor != null && cursor.moveToNext()) {

        ... ... 

    }

} finally {

    if (cursor != null) {

        try { 

            cursor.close();

        } catch (Exception e) {

            //ignore this

        }

    }

 

(二) 構造Adapter時,沒有使用快取的 convertView

 

描述:

    以構造ListView的BaseAdapter為例,在BaseAdapter中提高了方法:

public View getView(int position, View convertView, ViewGroup parent)

來向ListView提供每一個item所需要的view物件。初始時ListView會從BaseAdapter中根據當前的屏幕布局例項化一定數量的view物件,同時ListView會將這些view物件快取起來。當向上滾動ListView時,原先位於最上面的list item的view物件會被回收,然後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被快取起來的list item的view物件(初始化時快取中沒有view物件則convertView是null)。

    由此可以看出,如果我們不去使用convertView,而是每次都在getView()中重新例項化一個View物件的話,即浪費資源也浪費時間,也會使得記憶體佔用越來越大。ListView回收list item的view物件的過程可以檢視:

android.widget.AbsListView.java --> void addScrapView(View scrap) 方法。

 

示例程式碼:

public View getView(int position, View convertView, ViewGroup parent) {

    View view = new Xxx(...);

    ... ...

    return view;

}

 

修正示例程式碼:

public View getView(int position, View convertView, ViewGroup parent) {

    View view = null;

    if (convertView != null) {

        view = convertView;

        populate(view, getItem(position));

        ...

    } else {

        view = new Xxx(...);

        ...

    }

    return view;

 

(三) Bitmap物件不在使用時呼叫recycle()釋放記憶體

 

描述:

    有時我們會手工的操作Bitmap物件,如果一個Bitmap物件比較佔記憶體,當它不在被使用的時候,可以呼叫Bitmap.recycle()方法回收此物件的畫素所佔用的記憶體,但這不是必須的,視情況而定。可以看一下程式碼中的註釋:

    /**

     * Free up the memory associated with this bitmap's pixels, and mark the

     * bitmap as "dead", meaning it will throw an exception if getPixels() or

     * setPixels() is called, and will draw nothing. This operation cannot be

     * reversed, so it should only be called if you are sure there are no

     * further uses for the bitmap. This is an advanced call, and normally need

     * not be called, since the normal GC process will free up this memory when

     * there are no more references to this bitmap.

     */

 

(四) 釋放物件的引用

 

描述:

    這種情況描述起來比較麻煩,舉兩個例子進行說明。

示例A:

假設有如下操作

public class DemoActivity extends Activity {

    ... ...

    private Handler mHandler = ...

    private Object obj;

    public void operation() {

     obj = initObj();

     ...

     [Mark]

     mHandler.post(new Runnable() {

            public void run() {

             useObj(obj);

            }

     });

    }

}

    我們有一個成員變數 obj,在operation()中我們希望能夠將處理obj例項的操作post到某個執行緒的MessageQueue中。在以上的程式碼中,即便是mHandler所在的執行緒使用完了obj所引用的物件,但這個物件仍然不會被垃圾回收掉,因為DemoActivity.obj還保有這個物件的引用。所以如果在DemoActivity中不再使用這個物件了,可以在[Mark]的位置釋放物件的引用,而程式碼可以修改為:

... ...

public void operation() {

    obj = initObj();

    ...

    final Object o = obj;

    obj = null;

    mHandler.post(new Runnable() {

        public void run() {

            useObj(o);

        }

    }

}

... ...

 

示例B:

    假設我們希望在鎖屏介面(LockScreen)中,監聽系統中的電話服務以獲取一些資訊(如訊號強度等),則可以在LockScreen中定義一個PhoneStateListener的物件,同時將它註冊到TelephonyManager服務中。對於LockScreen物件,當需要顯示鎖屏介面的時候就會建立一個LockScreen物件,而當鎖屏介面消失的時候LockScreen物件就會被釋放掉。

    但是如果在釋放LockScreen物件的時候忘記取消我們之前註冊的PhoneStateListener物件,則會導致LockScreen無法被垃圾回收。如果不斷的使鎖屏介面顯示和消失,則最終會由於大量的LockScreen物件沒有辦法被回收而引起OutOfMemory,使得system_process程序掛掉。

    總之當一個生命週期較短的物件A,被一個生命週期較長的物件B保有其引用的情況下,在A的生命週期結束時,要在B中清除掉對A的引用。

 

(五) 其他

 

    Android應用程式中最典型的需要注意釋放資源的情況是在Activity的生命週期中,在onPause()、onStop()、onDestroy()方法中需要適當的釋放資源的情況。由於此情況很基礎,在此不詳細說明,具體可以檢視官方文件對Activity生命週期的介紹,以明確何時應該釋放哪些資源。

 

三、記憶體監測工具 DDMS --> Heap

 

    無論怎麼小心,想完全避免bad code是不可能的,此時就需要一些工具來幫助我們檢查程式碼中是否存在會造成記憶體洩漏的地方。Android tools中的DDMS就帶有一個很不錯的記憶體監測工具Heap(這裡我使用eclipse的ADT外掛,並以真機為例,在模擬器中的情況類似)。用Heap監測應用程序使用記憶體情況的步驟如下:

1. 啟動eclipse後,切換到DDMS透檢視,並確認Devices檢視、Heap檢視都是開啟的;

2. 將手機通過USB連結至電腦,連結時需要確認手機是處於“USB除錯”模式,而不是作為“Mass Storage”;

3. 連結成功後,在DDMS的Devices檢視中將會顯示手機裝置的序列號,以及裝置中正在執行的部分程序資訊;

4. 點選選中想要監測的程序,比如system_process程序;

5. 點選選中Devices檢視介面中最上方一排圖示中的“Update Heap”圖示;

6. 點選Heap檢視中的“Cause GC”按鈕;

7. 此時在Heap檢視中就會看到當前選中的程序的記憶體使用量的詳細情況[如圖所示]。

 

 

說明:

a) 點選“Cause GC”按鈕相當於向虛擬機器請求了一次gc操作;

b) 當記憶體使用資訊第一次顯示以後,無須再不斷的點選“Cause GC”,Heap檢視介面會定時重新整理,在對應用的不斷的操作過程中就可以看到記憶體使用的變化;

c) 記憶體使用資訊的各項引數根據名稱即可知道其意思,在此不再贅述。

 

    如何才能知道我們的程式是否有記憶體洩漏的可能性呢。這裡需要注意一個值:Heap檢視中部有一個Type叫做data object,即資料物件,也就是我們的程式中大量存在的類型別的物件。在data object一行中有一列是“Total Size”,其值就是當前程序中所有Java資料物件的記憶體總量,一般情況下,這個值的大小決定了是否會有記憶體洩漏。可以這樣判斷:

a) 不斷的操作當前應用,同時注意觀察data object的Total Size值;

b) 正常情況下Total Size值都會穩定在一個有限的範圍內,也就是說由於程式中的的程式碼良好,沒有造成物件不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多物件,而在虛擬機器不斷的進行GC的過程中,這些物件都被回收了,記憶體佔用量會會落到一個穩定的水平;

c) 反之如果程式碼中存在沒有釋放物件引用的情況,則data object的Total Size值在每次GC後不會有明顯的回落,隨著操作次數的增多Total Size的值會越來越大,

    直到到達一個上限後導致程序被kill掉。

d) 此處已system_process程序為例,在我的測試環境中system_process程序所佔用的記憶體的data object的Total Size正常情況下會穩定在2.2~2.8之間,而當其值超過3.55後進程就會被kill。

 

    總之,使用DDMS的Heap檢視工具可以很方便的確認我們的程式是否存在記憶體洩漏的可能性。

 

四、記憶體分析工具 MAT(Memory Analyzer Tool)

    如果使用DDMS確實發現了我們的程式中存在記憶體洩漏,那又如何定位到具體出現問題的程式碼片段,最終找到問題所在呢?如果從頭到尾的分析程式碼邏輯,那肯定會把人逼瘋,特別是在維護別人寫的程式碼的時候。這裡介紹一個極好的記憶體分析工具 -- Memory Analyzer Tool(MAT)。

    MAT是一個Eclipse外掛,同時也有單獨的RCP客戶端。官方下載地址、MAT介紹和詳細的使用教程請參見:www.eclipse.org/mat,在此不進行說明了。另外在MAT安裝後的幫助文件裡也有完備的使用教程。在此僅舉例說明其使用方法。我自己使用的是MAT的eclipse外掛,使用外掛要比RCP稍微方便一些。

 

    使用MAT進行記憶體分析需要幾個步驟,包括:生成.hprof檔案、開啟MAT並匯入.hprof檔案、使用MAT的檢視工具分析記憶體。以下詳細介紹。

 

(一) 生成.hprof檔案

 

    生成.hprof檔案的方法有很多,而且Android的不同版本中生成.hprof的方式也稍有差別,我使用的版本的是2.1,各個版本中生成.prof檔案的方法請參考:

http://android.git.kernel.org/?p=platform/dalvik.git;a=blob_plain;f=docs/heap-profiling.html;hb=HEAD。

1. 開啟eclipse並切換到DDMS透檢視,同時確認Devices、Heap和logcat檢視已經打開了;

2. 將手機裝置連結到電腦,並確保使用“USB 除錯”模式連結,而不是“Mass Storage“模式;

3. 連結成功後在Devices檢視中就會看到裝置的序列號,和裝置中正在執行的部分程序;

4. 點選選中想要分析的應用的程序,在Devices檢視上方的一行圖示按鈕中,同時選中“Update Heap”和“Dump HPROF file”兩個按鈕;

5. 這是DDMS工具將會自動生成當前選中程序的.hprof檔案,並將其進行轉換後存放在sdcard當中,如果你已經安裝了MAT外掛,那麼此時MAT將會自動被啟用,並開始對.hprof檔案進行分析;

    注意:第4步和第5步能夠正常使用前提是我們需要有sdcard,並且當前程序有向sdcard中寫入的許可權(WRITE_EXTERNAL_STORAGE),否則.hprof檔案不會被生成,在logcat中會顯示諸如

     ERROR/dalvikvm(8574): hprof: can't open /sdcard/com.xxx.hprof-hptemp: Permission denied.