Best Practices for Performance

摘自 android develop training。

Best Practices for Performance

================================
Performance Tips
================================

两个基本原则
1 Don't do work that you don't need to do.
2 Don't allocate memory if you can avoid it.

Avoid Creating Unnecessary Objects

太多的短暂的临时对象,会使gc间隔的执行。所以避免创建不必要的对象啦。

如:
使用 StringBuffer
把多维数组,切分为多个并行的一位数组。
(Foo, Bar)---> Foo[], Bar[]

Prefer Static Over Virtual
如果不需要访问对象的值,用static method ,可提升15%-20%的调用速度

Use Static Final For Constants
static final int intVal = 42;
static final String strVal = "Hello, world!";

Avoid Internal Getters/Setters
注意是 Internal
对public interface 用getters, Setters
但 在一个类里面应直接使用成员变量

without a JIT 3X faster, with a JIT 7X faster than invoking a trivial Getters

Use Enhanced For Loop Syntax
对于ArrayList 应使用 hand-writtern counted loop is about 3X faster
而对于其他的集合,用使用 for-each loop

Consider Package Instead of Private Access with Private Inner Classes
意思就是 内部类访问外部类的变量时,变量应定义为包级别的,而不是private。
这样内部类就可以这接访问。(当然 私有的也可以访问,但编译器会把它弄成方法级别的调用,上面有讲过,这样会慢)

Avoid Using Floating-Point
As a rule of thumb(作为一个经验), floating-point is about 2x slower than integer on Android-powered devices.

Know and Use the Libraries
使用原生库

Use Native Methods Carefully
有还是没有JIT的问题

==================================
Improving Layout Performance
==================================
------------------------------------------
Optimizing Layout Hierarchies
-----------------------------------------

1.Inspect Your Layout

使用<sdk>/tools/下的 Hirarchy Viewer 工具检查layout。

make the layout shallow(浅的) and wide, rather than narrow and deep
使layout扁平化,另外, layout_weight 属性 which can slow down the speed of measurement

2.use Lint 

在eclipse界面的ADT上有个勾的选项,能检查一些layout的问题。

----------------------------------------------
Re-using Layouts with <include/>
----------------------------------------------

1.Use the <include> Tag
titlebar.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=”match_parent”
    android:layout_height="wrap_content"
    android:background="@color/titlebar_bg">

    <ImageView android:layout_width="wrap_content"
               android:layout_height="wrap_content" 
               android:src="@drawable/gafricalogo" />
</FrameLayout>

<include layout="@layout/titlebar"/>

You can also override all the layout parameters 

<include android:id=”@+id/news_title”
         android:layout_width=”match_parent”
         android:layout_height=”match_parent”
         layout=”@layout/title”/>

2.Use the <merge> Tag

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/add"/>
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/delete"/>
</merge>

The <merge /> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another

Now, when you include this layout in another layout (using the <include/> tag), 
the system ignores the <merge> element and places the two buttons directly in the layout, in place of the <include/> tag.
把它include 到别的 layout 中时, 系统会忽略<mege>, 会直接将两个Button 加进layout中

---------------------------------------
Loading Views On Demand
--------------------------------------

1.ViewStud
ViewStub is a lightweight view with no dimension and doesn’t draw anything or participate in the layout. 

<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

2.Load the ViewStub Layout

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

Once visible/inflated, the ViewStub element is no longer part of the view hierarchy. 
It is replaced by the inflated layout and the ID for the root view of that layout is the one specified by the android:inflatedId attribute of the ViewStub.
(The ID android:id specified for the ViewStub is valid only until the ViewStub layout is visible/inflated.)

一旦ViewStub显示,它就不属于view的层次范围了。会由infalted layout代替(android:layout), 由infaltedID 来指定它的id
一个缺点是,ViewStub不支持使用<merge/>tag

-----------------------------------------------
Making ListView Scrolling Smooth
-----------------------------------------------

1.Use a Background Thread
AsyncTask

2.Hold View Objects in a View Holder
ViewHolder

=======================================
Running in a Background Service
=======================================

The most useful of these is IntentService.

-------------------------------------------
Creating a Background Service
-------------------------------------------

IntentService 可以运行在一个单独的后台线程中。也不会被大多数的事件的生命周期影响,所以在 在 AsyncTask 关了后,还可继续运行。

IntentService 有一些限制:
1)不能通过 UI 直接交互,把结果返回到 UI ,必须把它发送到 Activity
2)要按顺序执行,一个一个来。
3)正在运行的 IntentService 不能被打断

1.Create an IntentService

Public class RSSPullService extends IntentService {
@Override
protected void onHandleIntent(Intent workIntent) {
// Gets data from the incoming Intent
String dataString = workIntent.getDataString();
}
...
// Do work here, based on the contents of dataString
...
}

2.Define the IntentService in the Manifest

<application
        android:icon="@drawable/icon"
        android:label="@string/app_name">
        ...
        <!--
            Because android:exported is set to "false",
            the service is only available to this app.
        -->
        <service
            android:name=".RSSPullService"
            android:exported="false"/>
        ...
 <application/>

Notice that the <service> element doesn't contain an intent filter. The Activity that sends work requests to the service uses an explicit Intent,so no filter is needed. 
This also means that only components in the same app or other applications with the same user ID can access the service.

----------------------------------------------------------------------
Sending Work Requests to the Background Service
----------------------------------------------------------------------

1.Create and Send a Work Request to an IntentService

/*
 * Creates a new Intent to start the RSSPullService
 * IntentService. Passes a URI in the
 * Intent's "data" field.
 */
mServiceIntent = new Intent(getActivity(), RSSPullService.class);
mServiceIntent.setData(Uri.parse(dataUrl));
// Starts the IntentService
getActivity().startService(mServiceIntent);

发送完了,当然要接收了,下节介绍。

---------------------------------
Reporting Work Status
---------------------------------

建议使用 LocalBroadcastManager

1.Report Status From an IntentService

For example:
public final class Constants {
    ...
    // Defines a custom Intent action
    public static final String BROADCAST_ACTION =
        "com.example.android.threadsample.BROADCAST";
    ...
    // Defines the key for the status "extra" in an Intent
    public static final String EXTENDED_DATA_STATUS =
        "com.example.android.threadsample.STATUS";
    ...
}
public class RSSPullService extends IntentService {
...
    /*
     * Creates a new Intent containing a Uri object
     * BROADCAST_ACTION is a custom Intent action
     */
    Intent localIntent =
            new Intent(Constants.BROADCAST_ACTION)
            // Puts the status into the Intent
            .putExtra(Constants.EXTENDED_DATA_STATUS, status);
    // Broadcasts the Intent to receivers in this app.
    LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
...
}

2.Receive Status Broadcasts from an IntentService

// Broadcast receiver for receiving status updates from the IntentService
private class DownloadStateReceiver extends BroadcastReceiver
{
    // Prevents instantiation
    private DownloadStateReceiver() {
    }
    // Called when the BroadcastReceiver gets an Intent it's registered to receive
    @
    public void onReceive(Context context, Intent intent) {
...
        /*
         * Handle Intents here.
         */
...
    }
}

// Class that displays photos
public class DisplayActivity extends FragmentActivity {
    ...
    public void onCreate(Bundle stateBundle) {
        ...
        super.onCreate(stateBundle);
        ...
        // The filter's action is BROADCAST_ACTION
        IntentFilter mStatusIntentFilter = new IntentFilter(
                Constants.BROADCAST_ACTION);
    
        // Adds a data filter for the HTTP scheme
        mStatusIntentFilter.addDataScheme("http");
        ...

// Instantiates a new DownloadStateReceiver
        DownloadStateReceiver mDownloadStateReceiver =
                new DownloadStateReceiver();
        // Registers the DownloadStateReceiver and its intent filters
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mDownloadStateReceiver,
                mStatusIntentFilter);
        ...

同一个BroadcastReceiver 可以注册不同的Intent

/*
         * Instantiates a new action filter.
         * No data filter is needed.
         */
        statusIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);
        ...
        // Registers the receiver with the new filter
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
                mDownloadStateReceiver,
                mIntentFilter);
}

BroadcastReceiver 接收和处理Intent,都是在后台处理的。当app不可见时,如想通知用户事件的话,用 Notification.
决不能 start an Activity 去响应一个 broadcast Intent。


====================================
Loading Data in the Background
====================================

-----------------------------------------------------
Running a Query with a CursorLoader
------------------------------------------------------

1.Define an Activity That Uses CursorLoader

public class PhotoThumbnailFragment extends FragmentActivity implements
        LoaderManager.LoaderCallbacks<Cursor> {
...
}

2.Initialize the Query

// Identifies a particular Loader being used in this component
    private static final int URL_LOADER = 0;
    ...
    /* When the system is ready for the Fragment to appear, this displays
     * the Fragment's View
     */
    public View onCreateView(
            LayoutInflater inflater,
            ViewGroup viewGroup,
            Bundle bundle) {
        ...
        /*
         * Initializes the CursorLoader. The URL_LOADER value is eventually passed
         * to onCreateLoader().
         */
        getLoaderManager().initLoader(URL_LOADER, null, this);
        ...
 }

Note: The method getLoaderManager() is only available in the Fragment class. To get a LoaderManager in a FragmentActivity, call getSupportLoaderManager().

3.Start the Query

/*
* Callback that's invoked when the system has initialized the Loader and
* is ready to start the query. This usually happens when initLoader() is
* called. The loaderID argument contains the ID value passed to the
* initLoader() call.
*/
@Override
public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle)
{
    /*
     * Takes action based on the ID of the Loader that's being created
     */
    switch (loaderID) {
        case URL_LOADER:
            // Returns a new CursorLoader
            return new CursorLoader(
                        getActivity(),   // Parent activity context
                        mDataUrl,        // Table to query
                        mProjection,     // Projection to return
                        null,            // No selection clause
                        null,            // No selection arguments
                        null             // Default sort order
        );
        default:
            // An invalid id was passed in
            return null;
    }
}

it starts the query in the background. When the query is done, the background framework calls onLoadFinished(), which is described in the next lesson.

------------------------------
Handling the Results
------------------------------
除了要实现 onCreateLoader() 和 onLoadFinished() 外,还要实现 onLoaderReset()
此方法回调于 当 CursorLoader 检测到Cusor关联的数据有变化时。framework also re-runs the current query.

1.Handle Query Results
把 cursor 和 CursorAdapter 联系起来

public String[] mFromColumns = {
    DataProviderContract.IMAGE_PICTURENAME_COLUMN
};
public int[] mToFields = {
    R.id.PictureName
};
// Gets a handle to a List View
ListView mListView = (ListView) findViewById(R.id.dataList);
/*
 * Defines a SimpleCursorAdapter for the ListView
 *
 */
SimpleCursorAdapter mAdapter =
    new SimpleCursorAdapter(
            this,                // Current context
            R.layout.list_item,  // Layout for a single row
            null,                // No Cursor yet
            mFromColumns,        // Cursor columns to use
            mToFields,           // Layout fields to use
            0                    // No flags
    );
// Sets the adapter for the view
mListView.setAdapter(mAdapter);
...
/*
 * Defines the callback that CursorLoader calls
 * when it's finished its query
 */
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    ...
    /*
     * Moves the query results into the adapter, causing the
     * ListView fronting this adapter to re-display
     */
    mAdapter.changeCursor(cursor);
}

2.Delete Old Cursor References

/*
 * Invoked when the CursorLoader is being reset. For example, this is
 * called if the data in the provider changes and the Cursor becomes stale(陈腐的).
 */
@Override
public void onLoaderReset(Loader<Cursor> loader) {
    
    /*
     * Clears out the adapter's reference to the Cursor.
     * This prevents memory leaks.
     */
    mAdapter.changeCursor(null);
}

 Once onLoaderReset() finishes, CursorLoader re-runs its query.

=================================================================
Optimizing Battery Life
=================================================================

------------------------------------------------------------------
Monitoring the Battery Level and Charging State
------------------------------------------------------------------
1.Determine the Current Charging State

你可以检查当前的 充电状态,当电池耗尽时,来减少或停止你的更新工作。
BatteryManager 广播所有的电池和充电细节 in a sticky Intent.

因为是 sticky intent, 所以不用注册 BroadcastReceiver

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);

// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                     status == BatteryManager.BATTERY_STATUS_FULL;

// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

可以根据这些信息来改变后台的更新速率。

2.Monitor Changes in Charging State
当设备连接或不连接电源时,那个 BatteryManger broadcasts an action 。
所以,尽管你的app没在运行,也应当注册它。特别是当你启动app时,是否要启动后台更新。

manifest中

<receiver android:name=".PowerConnectionReceiver">
  <intent-filter>
    <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
    <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
  </intent-filter>
</receiver>

public class PowerConnectionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) { 
        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                            status == BatteryManager.BATTERY_STATUS_FULL;
    
        int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
        boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
    }
}

3.Determine the Current Battery Level

int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

float batteryPct = level / (float)scale;

4.Monitor Significant Changes in Battery Level
it's good practice to only monitor significant changes in battery level—specifically when the device enters or exits a low battery state.

<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
  <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
  <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
  </intent-filter>
</receiver>

-----------------------------------------------------------------------------
Determining and Monitoring the Docking State and Type
-----------------------------------------------------------------------------
1.Determine the Current Docking State

IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);
int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;

2.Determine the Current Dock Type

有四种dock type
Car 
Desk 
Low-End(Analog)Desk 
High-End(Digital)Desk

boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK || 
                 dockState == EXTRA_DOCK_STATE_LE_DESK ||
                 dockState == EXTRA_DOCK_STATE_HE_DESK;

3.Monitor for Changes in the Dock State or Type
Whenever the device is docked or undocked, the ACTION_DOCK_EVENT action is broadcast.

<action android:name="android.intent.action.ACTION_DOCK_EVENT"/>

-------------------------------------------------------
Determining and Monitoring the Connectivity Status
-------------------------------------------------------
1.Determine if you Have an Internet Connection
当你没连上网时,没有必要更新一些网络的资源。

ConnectivityManager cm =
        (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork.isConnectedOrConnecting();

2.Determine the Type of your Internet Connection

boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

3.Monitor for Changes in Connectivity

<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

it's good practice to monitor this broadcast only when you've previously suspended updates or downloads in order to resume them

------------------------------------------------------------------
Manipulating Broadcast Receivers On Demand
------------------------------------------------------------------

1.Toggle and Cascade State Change Receivers to Improve Efficiency
disable or enable the broadcast received at runtime.

ComponentName receiver = new ComponentName(context, myReceiver.class);

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP)


=======================================================================
Sending Operations to Multiple Threads
=======================================================================

-------------------------------------------------------
Specifying the Code to Run on a Thread
-------------------------------------------------------

1.Define a Class that Implements Runnable

public class PhotoDecodeRunnable implements Runnable {
@Override
public void run() {
/*
* Code you want to run on the thread goes here
*/
}
}

2.Implement the run() Method

Runnable 不会运行在UI Thread 上, 所以你不能直接修改 UI上的对象,如 View等。

class PhotoDecodeRunnable implements Runnable {
...
    /*
     * Defines the code to run for this task.
     */
    @Override
    public void run() {
        // Moves the current Thread into the background
// This approach reduces resource competition between the Runnable object's thread and the UI thread.
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        ...
        /*
         * Stores the current Thread in the PhotoTask instance,
         * so that the instance
         * can interrupt the Thread.
         */
        mPhotoTask.setImageDecodeThread(Thread.currentThread());
        ...
    }
...
}

-------------------------------------------------------
Creating a Manager for Multiple Threads
-------------------------------------------------------

1.Define the Thread Pool Class

public class PhotoManager {
    ...
    static  {
        ...
        // Creates a single static instance of PhotoManager
        sInstance = new PhotoManager();
    }
    ...
    
     /**
     * Constructs the work queues and thread pools used to download
     * and decode images. Because the constructor is marked private,
     * it's unavailable to other classes, even in the same package.
     */
     private PhotoManager() { 
    // Defines a Handler object that's attached to the UI thread
        mHandler = new Handler(Looper.getMainLooper()) {
            /*
             * handleMessage() defines the operations to perform when
             * the Handler receives a new Message to process.
             */
            @Override
            public void handleMessage(Message inputMessage) {
                ...
            }
        ...
        }
     }

     // Called by the PhotoView to get a photo
    static public PhotoTask startDownload(
        PhotoView imageView,
        boolean cacheFlag) {
        ...
        // Adds a download task to the thread pool for execution
        sInstance.
                mDownloadThreadPool.
                execute(downloadTask.getHTTPDownloadRunnable());


...
     } 
}

2.Determine the Thread Pool Parameters

    /*
     * Gets the number of available cores
     * (not always the same as the maximum number of cores)
     */
    private static int NUMBER_OF_CORES =
            Runtime.getRuntime().availableProcessors();

3.Create a Pool of Threads

ThreadPollExecutor 当它实例化时,会创建它线程池所有的线程对象。

private PhotoManager() {
        ...
        // Sets the amount of time an idle thread waits before terminating
        private static final int KEEP_ALIVE_TIME = 1;
        // Sets the Time Unit to seconds
        private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
        // Creates a thread pool manager
        mDecodeThreadPool = new ThreadPoolExecutor(
                NUMBER_OF_CORES,       // Initial pool size
                NUMBER_OF_CORES,       // Max pool size
                KEEP_ALIVE_TIME,
                KEEP_ALIVE_TIME_UNIT,
                mDecodeWorkQueue);
}

-------------------------------------------------------
Running Code on a Thread Pool Thread
-------------------------------------------------------

1.Run a Runnable on a Thread in the Thread Pool

pass the Runnable to ThreadPoolExecutor.execute().

public class PhotoManager {
    public void handleState(PhotoTask photoTask, int state) {
        switch (state) {
            // The task finished downloading the image
            case DOWNLOAD_COMPLETE:
            // Decodes the image
                mDecodeThreadPool.execute(
                        photoTask.getPhotoDecodeRunnable());
            ...
        }
        ...
    }
    ...
}

2.Interrupt Running Code
To stop a task, you need to interrupt the task's thread.
首先,要保存一个task的handle。使用Thread.currentThread().

注意到 Thread objects 是由系统控制的,所以别的app进程也可以修改到。
所以在 interrupt it 时,需要 synchronized block.

public class PhotoManager {
    public static void cancelAll() {
        /*
         * Creates an array of Runnables that's the same size as the
         * thread pool work queue
         */
        Runnable[] runnableArray = new Runnable[mDecodeWorkQueue.size()];
        // Populates the array with the Runnables in the queue
        mDecodeWorkQueue.toArray(runnableArray);
        // Stores the array length in order to iterate over the array
        int len = runnableArray.length;
        /*
         * Iterates over the array of Runnables and interrupts each one's Thread.
         */
        synchronized (sInstance) {
            // Iterates over the array of tasks
            for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) {
                // Gets the current thread
                Thread thread = runnableArray[taskArrayIndex].mThread;
                // if the Thread exists, post an interrupt to it
                if (null != thread) {
                    thread.interrupt();
                }
            }
        }
    }
    ...
}

通常,Thread.interrupt() 会马上停止线程。但它只能停止那些等待的线程,
而不会打断 CPU 或 network-intensive tasks.
所以,为了避免系统变慢,应该在尝试操作时,进行判断。

*
* Before continuing, checks to see that the Thread hasn't
* been interrupted
*/
if (Thread.interrupted()) {
    return;
}
...
// Decodes a byte array into a Bitmap (CPU-intensive)
BitmapFactory.decodeByteArray(
        imageBuffer, 0, imageBuffer.length, bitmapOptions);

------------------------------------------------
Communicating with the UI Thread
------------------------------------------------
要把数据从后台线程返回到UI,使用运行在 UI 上的 Handler。

1.Define a Handler on the UI Thread

当你以一个 Looper 实例来实例化一个 Handler 时,Handler 就会和 Looper 运行在同一线程上。
对特定(运行在同一Thread上)的 Thread 所有的 Handler Objects 都是接收同样的 message。

private PhotoManager() {

// Defines a Handler object that's attached to the UI thread
mHandler = new Handler(Looper.getMainLooper()) {
   ...
/*
         * handleMessage() defines the operations to perform when
         * the Handler receives a new Message to process.
         */
        @Override
        public void handleMessage(Message inputMessage) {
            // Gets the image task from the incoming Message object.
            PhotoTask photoTask = (PhotoTask) inputMessage.obj;
            ...
        }
    ...
    }
}

2.Move Data from a Task to the UI Thread

1) Store data in the task object


如:有个后台运行的 Runnable 线程,它解码 Bitmap, 同时把它存储到它的父对象 PhotoTask。
同时也保存了个状态码,DECODE_STATE_COMPLETED.

// A class that decodes photo files into Bitmaps
class PhotoDecodeRunnable implements Runnable {
    ...
    PhotoDecodeRunnable(PhotoTask downloadTask) {
        mPhotoTask = downloadTask;
    }
    ...
    // Gets the downloaded byte array
    byte[] imageBuffer = mPhotoTask.getByteBuffer();
    ...
    // Runs the code for this task
    public void run() {
        ...
        // Tries to decode the image buffer
        returnBitmap = BitmapFactory.decodeByteArray(
                imageBuffer,
                0,
                imageBuffer.length,
                bitmapOptions
        );
        ...
        // Sets the ImageView Bitmap
        mPhotoTask.setImage(returnBitmap);
        // Reports a status of "completed"
        mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
        ...
    }
    ...
}

PhotoTask 拥有要显示 Bitmap 的 ImageView 对象的引用,尽管在同一个对象里(PhotoTask)拥有 Bitmap 和 ImageView
的引用,但不能把 Bitmap赋值给 ImageView, 因为你当前并没运行在 UI Thread 上。

2) Send status up the object hierarchy

PhotoTask 拥有Bitmap 和 ImageView 的引用,同时,接收了来之 PhotoDecodeRunnable 的状态码,
并把它传给一个拥有 线程池 和 与 UI 线程关联的 Handler 的对象(PhotoManager).

public class PhotoTask {
    ...
    // Gets a handle to the object that creates the thread pools
    sPhotoManager = PhotoManager.getInstance();
    ...
    public void handleDecodeState(int state) {
        int outState;
        // Converts the decode state to the overall state.
        switch(state) {
            case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
                outState = PhotoManager.TASK_COMPLETE;
                break;
            ...
        }
        ...
        // Calls the generalized state method
        handleState(outState);
    }
    ...
    // Passes the state to PhotoManager
    void handleState(int state) {
        /*
         * Passes a handle to this task and the
         * current state to the class that created
         * the thread pools
         */
        sPhotoManager.handleState(this, state);
    }
    ...
}

3)Move data to the UI
PhotoManager 接收了来自 PhotoTask 的数据 (PhotoTask object 和 status code)
创建一个 Message 发送个 Handler。
public class PhotoManager {
    ...
    // Handle status messages from tasks
    public void handleState(PhotoTask photoTask, int state) {
        switch (state) {
            ...
            // The task finished downloading and decoding the image
            case TASK_COMPLETE:
                /*
                 * Creates a message for the Handler
                 * with the state and the task object
                 */
                Message completeMessage =
                        mHandler.obtainMessage(state, photoTask);
                completeMessage.sendToTarget();
                break;
            ...
        }
        ...
    }

至此,Message 中的 PhotoTask 对象拥有Bitmap 和 ImageView,而Handler.handleMessage() 又是运行在 UI Thread 上,
所以,可以安全的 move the Bitmap to the ImageView.

private PhotoManager() {
        ...
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message inputMessage) {
                    // Gets the task from the incoming Message object.
                    PhotoTask photoTask = (PhotoTask) inputMessage.obj;
                    // Gets the ImageView for this task
                    PhotoView localView = photoTask.getPhotoView();
                    ...
                    switch (inputMessage.what) {
                        ...
                        // The decoding is done
                        case TASK_COMPLETE:
                            /*
                             * Moves the Bitmap from the task
                             * to the View
                             */
                            localView.setImageBitmap(photoTask.getImage());
                            break;
                        ...
                        default:
                            /*
                             * Pass along other messages from the UI
                             */
                            super.handleMessage(inputMessage);
                    }
                    ...
                }
                ...
            }
            ...
    }
...
}


===============================================================
Keeping Your App Responsive
===============================================================

1.What Triggers ANR?

In Android, application responsiveness is monitored by the Activity Manager and Window Manager system services. 
Android will display the ANR dialog for a particular application when it detects one of the following conditions:

1)No response to an input event (such as key press or screen touch events) within 5 seconds.
2)A BroadcastReceiver hasn't finished executing within 10 seconds.

2.How to Avoid ANRs

The most effecive way to create a worker thread for longer operations is with the AsyncTask class.

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    // this method runs on the UI Thread
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }


    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

To execute this worker thread, simply create an instance and call execute():

new DownloadFilesTask().execute(url1, url2, url3);

你也有可能会自己创建 Thread 或 HandlerThread class,你必须把线程优先级设成 background priority by
Process.setThreadPriority(THREAD_PRIORITY_BAKCKGROUD)
否则,它会默认的和 UI Thread 运行在同一个优先级。

If you implement Thread or HandlerThread, be sure that your UI thread does not block while waiting for the worker thread to complete—do not call Thread.wait() or Thread.sleep()。
主线程(main thread) 应该提供一个 Handler ,为别的线程提交(post)一个完成信息。

同样,在 broadcast receiver 里,应用应该避免长时间的操作工作。
如果有个长时间运行的action需要返回给 intent broadcast ,应启动一个 IntentService。

Tip: You can use StrictMode to help find potentially long running operations such as network or database operations that you might accidentally be doing your main thread.

3.Reinforcing(加强) Responsiveness

使用 ProgressBar 之类的显示,说明UI并不是没响应。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值