无码人妻精一区二区三区,eeuss影院www在线观看,无码精品久久久久久人妻中字,日韩av高清在线看片

推薦新聞
Android 多線程誤區(qū),我不信你們都懂!
發(fā)布者:深藍互聯(lián)
發(fā)布時間:2019-07-22
點擊:次

誤區(qū)

1.在代碼中直接創(chuàng)建新的Thread.

new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();

以上的做法是非常不可取的,缺點非常的多,想必大部分朋友面試的時候都會遇到這種問題,分析一下為啥不可以。浪費線程資源是第一,最重要的是我們無法控制該線程的執(zhí)行,因此可能會造成不必要的內(nèi)存泄漏。在Activity或者Fragment這種有生命周期的控件里面直接執(zhí)行這段代碼,相信大部分人都知道會可能有內(nèi)存泄漏。但是就算在其他的設計模式,比如MVP,同樣也可能會遇到這個問題。

//runnable->presenter->view
public class Presenter {
    //持有view引用
    private IView view;
    public Presenter(IView v){
        this.view = v;
    }
    public void doSomething(String[] args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                ** 持有presenter引用
                **/
                //do something
            }
        }).start();
    }
    public static interface IView{}
}

比如圖中的一段代碼(我標記了引用方向),通常MVP里面的View都是一個接口,但是接口的實現(xiàn)可能是Activity。那么在代碼中就可能存在內(nèi)存泄漏了。Thread的runnable是匿名內(nèi)部類,持有presenter的引用,presenter持有view的引用。這里的引用鏈就會造成內(nèi)存泄漏了。關鍵是,就算你持有線程的句柄,也無法把這個引用關系給解除。

所以優(yōu)秀的設計模式也阻止不了內(nèi)存泄漏。。。。。

2.頻繁使用HandlerThread

雖然HandlerThread是安卓framework的親兒子,但是在實際的開發(fā)過程中卻很少能有他的適用之處。HandlerThread繼承于Thread類,所以每次開啟一個HandlerThread就和開啟一個普通Thread一樣,很浪費資源。我們可以通過使用HandlerThread的例子來分析他最大的作用是什么。

static HandlerThread thread = new HandlerThread("test");
    static {
        thread.start();
    }

    public void testHandlerThread(){
        Handler handler = new Handler(thread.getLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        });
        //如果不需要了就remove handler's message
        handler.removeCallbacksAndMessages(null);
    }

    public void test(){
        //如果我還想利用HandlerThread,但是已經(jīng)丟失了handler的句柄,那么我們利用handler thread再構建一個handler
        Handler handler = new Handler(thread.getLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        });
    }

綜上所述,HandlerThread最屌的地方就在于,只要你還有它的句柄,你可以隨時拿到在該線程下創(chuàng)建的Looper對象,用于生成一個Handler。之后post的所有runnable都可以在該HandlerThread下運行。 然而。。

在實際的開發(fā)中,我們好像很難找到這么一個需求,要在指定的一個線程下執(zhí)行某些任務。注意了是指定的一個,不是一些(線程池)。唯一比Thread厲害的地方恐怕就是可以取消未執(zhí)行的任務,減少內(nèi)存泄漏的情況了吧。不過個人觀點是線程池好像也可以做到。所以并沒有察覺 HandlerThread有任何的優(yōu)勢。而且其實實現(xiàn)也很簡單,我們可以隨時手寫一個簡陋版的HandlerThread.

public static class DemoThread extends Thread{
        private LinkedBlockingQueue<Runnable> queue  = new LinkedBlockingQueue<>();

       @Override
       public void run() {
           super.run();
           while(true){
               if(!queue.isEmpty()){
                   Runnable runnable;
                   synchronized (this){
                       runnable = queue.poll();
                   }
                   if(runnable!= null) {
                       runnable.run();
                   }
               }
           }
       }

       public synchronized void post(Runnable runnable){
           queue.add(runnable);
       }

       public synchronized void clearAllMessage(){
           queue.clear();
       }

       public synchronized void clearOneMessage(Runnable runnable){
           for(Runnable runnable1 : queue){
               if(runnable == runnable1){
                   queue.remove(runnable);
               }
           }
       }
   }

    public void testDemoThread(){
        DemoThread thread = new DemoThread();
        thread.start();
        //發(fā)一個消息
        Runnable r = new Runnable() {
            @Override
            public void run() {

            }
        };
        thread.post(r);
        //不想執(zhí)行了。。。。刪掉
        thread.clearOneMessage(r);
    }

看分分鐘完成HandlerThread能做到的一切。。。。是不是很簡單。

3.直接使用AsyncTask.execute()

AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
            }
        });

個人認為AsyncTask的設計暴露了這個接口方法谷歌做的非常不恰當。它這樣允許開發(fā)者直接使用AsyncTask本身的線程池,我們可以看看源代碼做驗證

@MainThread
    public static void execute(Runnable runnable) {
        sDefaultExecutor.execute(runnable);
    }

果不其然,execute直接訪問了executor。

這樣的問題在于,這樣使用完全喪失了AsyncTask本身的意圖。個人的觀點是,AsyncTask提供了一個后臺任務切換到主線程的通道,就像RxJava的subscribeOn/observeOn一樣,同時提供cancel方法,可以取消掉切換回主線程執(zhí)行的代碼,從而防止內(nèi)存泄漏。

AsyncTask asyncTask = new AsyncTask() {
            @Override
            protected Object doInBackground(Object[] objects) {
                return null;
            }

            @Override
            protected void onPostExecute(Object o) {
                //1.提供了后臺線程切換回主線程的方法
                super.onPostExecute(o);
            }
        };

        //2.可以隨時取消
        asyncTask.cancel(true);

But!如果直接使用execute方法的話,我們完全沒有利用到AsyncTask本身設計的初衷下的優(yōu)勢,和直接自己創(chuàng)建一個線程池沒有任何區(qū)別,還存在內(nèi)存泄漏的風險。這樣的用法,肯定不能稱之為best practice.

4.以為RxJava的unsubscribe能包治百病

這個誤區(qū)標題起的有點模糊,這個沒辦法,因為例子有點點復雜。讓我來慢慢解釋。

我們以一個實際的app例子開始,讓我們看看youtube的app退訂頻道功能:

用戶點擊退訂按鈕之后,app發(fā)出api call,告訴后臺我們停止訂閱該頻道,同時把UI更新為progress bar,當api call結束,在api的回調(diào)里面我們更新UI控件顯示已退訂UI。我們寫一個示例代碼看看:

完美!

但是萬一用戶在點擊退訂按鈕,但是api call還沒發(fā)出去之前就退出了app呢?

public class YoutubePlayerActivity extends Activity {
    private Subscription subscription;
    public void setUnSubscribeListner(){
        unsubscribeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                subscription = Observable.create(new Observable.OnSubscribe<Void>() {
                    @Override
                    public void call(Subscriber<? super Void> subscriber) {
                        try {
                            //在這里我們做取消訂閱的API, http
                            API api = new API();
                            api.unSubscribe();
                        }
                        catch (Exception e){
                            subscriber.onError(e);
                        }
                        subscriber.onNext(null);
                        subscriber.onCompleted();
                    }
                })

                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Action1<Void>() {
                            @Override
                            public void call(Void aVoid) {
                                //API call成功!,在這里更新訂閱button的ui
                                unsubscribeButton.toggleSubscriptionStatus();
                            }
                        });
            }
        });
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
       //onDestroy 里面對RxJava stream進行unsubscribe,防止內(nèi)存泄漏
        subscription.unsubscribe();
    }
}

看似好像沒啥問題,沒有內(nèi)存泄漏,可以后臺線程和主線程直接靈活切換,更新UI不會crash。而且我們使用了http://Schedulers.io()調(diào)度器,看似也沒有浪費線程資源。

BUT?。。。。。?/b>

我們先仔細想想一個問題。我們在點擊button之后,我們的Observable

API api = new API();
 api.unSubscribe();

會立刻執(zhí)行么?

答案是NO。因為我們的Observable是subscribeOn io線程池。如果該線程池現(xiàn)在非常擁擠,這段代碼,這個Observable是不會立刻執(zhí)行的。該段代碼會華麗麗的躺在線程池的隊列中,安安靜靜的等待輪到自己執(zhí)行。

那么如果用戶點擊按鈕,同時退出app,我們unubscribe了這個RxJava 的observable 我們就存在一個不會執(zhí)行api call的風險。也就是用戶點擊退訂按鈕,退出app,返回app的時候,會發(fā)現(xiàn),咦,怎么明明點了退訂,竟然還是訂閱狀態(tài)?

這就回到了一個本質(zhì)問題,來自靈魂的拷問。是不是所有異步調(diào)用,都需要和Activity或者fragment的生命周期綁定?

答案同樣是NO,在很多應用場景下,當用戶做出一個行為的時候,我們必須堅定不移的執(zhí)行該行為背后的一切操作,至于異步操作完成之后的UI更新,則視當前Activity或者fragment的生命周期決定。也就是異步操作和生命周期無關,UI更新和生命周期有關。簡單點說,很多情況下,寫操作不能取消,讀操作可以。

很多情況下,比如支付,訂閱等等這種用戶場景,需要涉及到異步操作的都是會有以上的問題。在這些場景下,我們需要遵循以下流程。

最最重點的部分,就是當用戶退出的時候雖然我們停止更新UI,但當用戶重新進入的時候,app需要主動的重新向后臺發(fā)送請求,查看當前訂閱狀態(tài)。這樣,才是一個健康的app。

所以很遺憾,RxJava并沒有很好的支持這一場景,至于怎么解決,有什么框架比較合適,下一章再介紹。

 

  • 聯(lián)系地址
    中國 深圳 龍華區(qū)龍觀西路99號順泰中晟大廈一棟14樓13A01
  • 聯(lián)系電話
    電話:13530005652 / 0755-23110995
  • 電子郵箱
    wisepu@szdbi.com
關注深藍互聯(lián)公眾號
Copyright ? 2013-2025 深藍互聯(lián) 版權所有
友情鏈接: