1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Android多線程技術(shù)選型最全指南

        共 5085字,需瀏覽 11分鐘

         ·

        2020-08-12 18:27

        作者:qing的世界

        鏈接:https://juejin.im/post/5d1eb4acf265da1bb003de71



        前段時(shí)間在組內(nèi)做了一下現(xiàn)有的代碼分析,發(fā)現(xiàn)很多以前的legacy code多線程的使用都不算是最佳實(shí)踐,而且壞事的地方在于,剛畢業(yè)的學(xué)生,因?yàn)闆](méi)有別的參照物,往往會(huì)復(fù)制粘貼以前的舊代碼,這就造成了壞習(xí)慣不停的擴(kuò)散。所以本人就總結(jié)分析了一下Android的多線程技術(shù)選型,還有應(yīng)用場(chǎng)景。借著和組內(nèi)分享的機(jī)會(huì)也在簡(jiǎn)書上總結(jié)一下。因?yàn)樽约旱募夹g(shù)水平有限,有不對(duì)的地方還希望大家能多多指正。(代碼的例子方面,肯定不能用我們自己組內(nèi)產(chǎn)品的源代碼,簡(jiǎn)書上的都是我修改過(guò)的)

        這篇文章我會(huì)先分析一些大家可能踩過(guò)的雷區(qū),然后再列出一些可以改進(jìn)的地方。

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



        new Thread(new Runnable() {      @Override      public void run() {
        } }).start();


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

        //runnable->presenter->viewpublic 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{}}

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


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


        2. 頻繁使用HandlerThread

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

        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再構(gòu)建一個(gè)handler Handler handler = new Handler(thread.getLooper()); handler.post(new Runnable() { @Override public void run() { //do something } });}
        綜上所述,HandlerThread最屌的地方就在于,只要你還有它的句柄,你可以隨時(shí)拿到在該線程下創(chuàng)建的Looper對(duì)象,用于生成一個(gè)Handler。之后post的所有runnable都可以在該HandlerThread下運(yùn)行。然而。。

        在實(shí)際的開(kāi)發(fā)中,我們好像很難找到這么一個(gè)需求,要在指定的一個(gè)線程下執(zhí)行某些任務(wù)。注意了是指定的一個(gè),不是一些(線程池)。唯一比Thread厲害的地方恐怕就是可以取消未執(zhí)行的任務(wù),減少內(nèi)存泄漏的情況了吧。不過(guò)個(gè)人觀點(diǎn)是線程池好像也可以做到。所以并沒(méi)有察覺(jué)?HandlerThread有任何的優(yōu)勢(shì)。而且其實(shí)實(shí)現(xiàn)也很簡(jiǎn)單,我們可以隨時(shí)手寫一個(gè)簡(jiǎn)陋版的HandlerThread.
        public static class DemoThread extends Thread{    private LinkedBlockingQueue 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ā)一個(gè)消息 Runnable r = new Runnable() { @Override????????public?void?run()?{ } }; thread.post(r); //不想執(zhí)行了。。。。刪掉 thread.clearOneMessage(r); }


        看分分鐘完成HandlerThread能做到的一切。。。。是不是很簡(jiǎn)單。


        3.直接使用AsyncTask.execute()

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

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

        @MainThreadpublic static void execute(Runnable runnable) {     sDefaultExecutor.execute(runnable);?}
        果不其然,execute直接訪問(wèn)了executor。


        這樣的問(wèn)題在于,這樣使用完全喪失了AsyncTask本身的意圖。個(gè)人的觀點(diǎn)是,AsyncTask提供了一個(gè)后臺(tái)任務(wù)切換到主線程的通道,就像RxJava的subscribeOn/observeOn一樣,同時(shí)提供cancel方法,可以取消掉切換回主線程執(zhí)行的代碼,從而防止內(nèi)存泄漏。
        AsyncTask asyncTask = new AsyncTask() {    @Override    protected Object doInBackground(Object[] objects) {?????????return?null;    }
        @Override protected void onPostExecute(Object o) { //1.提供了后臺(tái)線程切換回主線程的方法 super.onPostExecute(o); } };
        ????//2.可以隨時(shí)取消???asyncTask.cancel(true);


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


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

        這個(gè)誤區(qū)標(biāo)題起的有點(diǎn)模糊,這個(gè)沒(méi)辦法,因?yàn)槔佑悬c(diǎn)點(diǎn)復(fù)雜。讓我來(lái)慢慢解釋。

        我們以一個(gè)實(shí)際的app例子開(kāi)始,讓我們看看youtube的app退訂頻道功能:



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


        但是萬(wàn)一用戶在點(diǎn)擊退訂按鈕,但是api call還沒(méi)發(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() {                    @Override                    public void call(Subscribersuper 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() { @Override public void call(Void aVoid) { //API call成功!,在這里更新訂閱button的ui unsubscribeButton.toggleSubscriptionStatus(); }?????????????????}); } }); } @Override protected void onDestroy() { super.onDestroy(); //onDestroy 里面對(duì)RxJava stream進(jìn)行unsubscribe,防止內(nèi)存泄漏 subscription.unsubscribe(); }}


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


        BUT?。。。。。?/span>


        我們先仔細(xì)想想一個(gè)問(wèn)題。我們?cè)邳c(diǎn)擊button之后,我們的Observable

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

        會(huì)立刻執(zhí)行么?


        答案是NO。因?yàn)槲覀兊腛bservable是subscribeOn io線程池。如果該線程池現(xiàn)在非常擁擠,這段代碼,這個(gè)Observable是不會(huì)立刻執(zhí)行的。該段代碼會(huì)華麗麗的躺在線程池的隊(duì)列中,安安靜靜的等待輪到自己執(zhí)行。


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


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


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


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

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


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

        ? 開(kāi)發(fā)者全社區(qū)?

        5T技術(shù)資源大放送!包括但不限于:Android,Python,Java,大數(shù)據(jù),人工智能,AI等等。關(guān)注公眾號(hào)后回復(fù)「2T」,即可免費(fèi)獲?。?/span>!
        瀏覽 63
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            顾美玲勾引管家 | 《喜爱夜蒲》大尺度视频 | 国产AV无码成人精品毛片 | 日本A片视频 | 16—17女人毛片 超碰在线一区 | 两个男人一前一后啪啪我 | 国内揄拍国内精品少妇国语无码 | 影视先锋资源网 | 一男一女操逼视频 | 中文字幕丰满子伦 |