Androidラーニングシリーズ(37)--Appデバッグメモリ流出のContext編(下)

8751 ワード

続いて「Androidラーニングシリーズ(36)--Appデバッグメモリ流出のContext編(上)」は分析を続けた.
5.AsyncTaskオブジェクト
私はN年前に盛大に一度試験に行ったことがあります.その時、面接官は私にAsyncTaskなどのシステムを使って自分で仕事をすることを強くお勧めしました.もちろん、間違いありません.
しかしAsyncTaskは確かに追加の注意が必要です.その漏洩原理は前のHandler,Thread漏洩の原理とあまり差がなく,そのライフサイクルはActivityと必ずしも一致しない.
解決策は、activityが終了したときにAsyncTaskのバックグラウンドタスクを終了することです.
しかし、問題はどのように終了しますか?
AsyncTaskは、対応するAPI:public final boolean cancel(boolean mayInterruptIfRunning)を提供する.
その説明には次のような言葉があります.
// Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. 
// If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

cancelは必ずしも成功するとは限りません.実行中の場合、バックグラウンドタスクが中断される可能性があります.どうしてこの話がこんなに頼りにならないと感じますか.
はい、頼りにならないだけです.
では、どうすれば頼りになるのでしょうか.公式の例を見てみましょう.
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     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
             //       ,     cancel,     
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

公式の例はよく、バックグラウンドサイクルでcancel状態を常に監視し、タイムリーに終了しないことを防止します.
皆さんに注意するために、googleはわざわざAsyncTaskの説明の中で大きな英語を捨てました.
// AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.

私の神州大陸は広大で、土地が広くて、何も欠けていません.英語の読書に敏感ではありません.
AsyncTaskは、最大数秒の短い時間の操作に適しています.長時間の操作をしたい場合は、他のjavaを使用します.util.Executor、ThreadPoolExecutor、FutureTaskなどのconcurrentパッケージのAPI.
英語をマスターして、穴を踏まないでください.
 
6.BroadcastReceiverオブジェクト
    ... has leaked IntentReceiver ... Are you missing a call to unregisterReceiver()?
これは直接言って、いろいろな原因でunregister()メソッドが呼び出されていません.
解決策は簡単です.unregister()メソッドが呼び出されていることを確認します.
ちなみに、私は仕事中に反対の状況に遭遇しました.receiverオブジェクトはregisterReceiver()に成功しませんでした(呼び出されませんでした)、unregisterのときにエラーを提示しました.
// java.lang.IllegalArgumentException: Receiver not registered ...

2つのソリューションがあります.
シナリオ1:registerReceiver()の後にFLAGを設定し、FLAGに基づいてunregister()か否かを判断する.ネットで検索した文章はほとんどこのように書かれていて、私は以前このようなバグに出会って、ずっとこのように理解していました.しかし、このコードは確かに少し醜いように見えます.
案2:私は後で何気なくある牛の注意を聞いて、Androidソースコードの中でもっと一般的な書き方を見ました.
    // just sample,        
    //           , ,    ,      ,          ,                。
    private void unregisterReceiverSafe(BroadcastReceiver receiver) {
        try {
            getContext().unregisterReceiver(receiver);
        } catch (IllegalArgumentException e) {
            // ignore
        }
    }

  
7.TimerTaskオブジェクト
TimerTaskオブジェクトは、Timerのschedule()メソッドと組み合わせて使用するとメモリが漏洩しやすくなります.
    private void startTimer(){  
        if (mTimer == null) {  
            mTimer = new Timer();  
        }  
  
        if (mTimerTask == null) {  
            mTimerTask = new TimerTask() {  
                @Override  
                public void run() {  
                    // todo
                }  
            };  
        }  
  
        if(mTimer != null && mTimerTask != null )  
            mTimer.schedule(mTimerTask, 1000, 1000);  
  
    } 

漏洩した点は,cancelがTimerとTimerTaskのインスタンスを落とすことを忘れたことである.cancelのタイミングはcursor編と話して、適当な時にcancel.
private void cancelTimer(){  
        if (mTimer != null) {  
            mTimer.cancel();  
            mTimer = null;  
        }  
        if (mTimerTask != null) {  
            mTimerTask.cancel();  
            mTimerTask = null;  
        }
    } 

 
8.Observerオブジェクト.
Observerオブジェクトの漏洩も、よく見られる、発見しやすい、解決しやすい漏洩タイプです.
まず、通常のコードを見てみましょう.
    //        ,   ContentObserver      ,              ,      
    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            // todo
        }
    };

    @Override
    public void onStart() {
        super.onStart();

        // register the observer 
        getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                xxx), false, mSettingsObserver);
    }

    @Override
    public void onStop() {
        super.onStop();

        // unregister it when stoping
        getContentResolver().unregisterContentObserver(mSettingsObserver);

    }

例を見てみましょう
    private final class SettingsObserver implements Observer {
        public void update(Observable o, Object arg) {
            // todo ...
        }   
    }

     mContentQueryMap = new ContentQueryMap(mCursor, Settings.System.XXX, true, null);
     mContentQueryMap.addObserver(new SettingsObserver());

誰がそんなに怠けて、SettingObserverを匿名の相手にして伝えたらいいの?
だから、怠け者は盗んではいけないものもあれば、文法糖は食べられないものもあります.
解決策は、deleteというObserverを必要としないときや終了するときです.
private Observer mSettingsObserver;
@Override
public void onResume() {
    super.onResume();
    if (mSettingsObserver == null) {
        mSettingsObserver = new SettingsObserver();
    }   
    mContentQueryMap.addObserver(mSettingsObserver);
}

@Override
public void onStop() {
    super.onStop();
    if (mSettingsObserver != null) {
        mContentQueryMap.deleteObserver(mSettingsObserver);
    }   
    mContentQueryMap.close();
}

注意点、異なる登録方法、異なる逆登録方法.
//     ,    
/*
addCallback             <==>     removeCallback
registerReceiver        <==>     unregisterReceiver
addObserver             <==>     deleteObserver
registerContentObserver <==>     unregisterContentObserver
... ...
*/

 
9.Dialogオブジェクト
    android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@438afa60 is not valid; is your activity running?
一般的にHandlerで発生するMESSAGEが並んでおり、Activityは終了し、HandlerはDialog関連の処理を開始した.
ポイントは、Activityが脱退したとどう判断するか、onDestroyにFLAGを設定するという人がいることです.残念なことに、この間違いはまた出てくる可能性が高いと言っています.
解決策は、Activity()を使用してActivityが終了するかどうかを判断することです.
    Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MESSAGE_1:
                // isFinishing == true,     ,    
                if (!isFinishing()) {
                    //    
                    // removeDialog()
                    // showDialog()
                }   
                break;
            default:
                break;
            }   
            super.handleMessage(msg);
        }   
    };

早く终わって早く釈放!
 
10.その他のオブジェクト
Listener対象者を中心に、「自分を入れたので、必ずタイムリーに自分を出してください」ということを覚えておきましょう.
 
11.まとめ
本論文Context編と前のCursor編を組み合わせて,多くの漏洩例を列挙し,ほとんどの根本的な原因は類似している.
これらの例を分析することで、APP層の90%のメモリ漏洩が理解できるはずです.
メモリの漏洩をどのように発見し、位置決めするかについては、もう一つの興味深い話題ですが、今はツールがある方法があるとしか言えません.