Android REST Clients - WeakReference/AsyncTask Pattern

我们之前在很多方案都会提到该方案有AsyncTask和Activity LifeCycle的问题,但是之前都没有详细展开说,今天我们就讨论一下到底AsyncTask和Activity配合使用时会发生什么问题。

Activity Leaks

我们参照Retrofit的示例代码写一个简单的Demo,这个Demo用来获取GitHub中square用户下的retrofit项目的所有贡献者的昵称,主要代码如下:

public class MainActivity extends ActionBarActivity {

    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.tv_content);
        // the AsyncTask will cause memory leaks
        new ShowContributorsMemoryLeak().execute();

    }

    private class ShowContributorsMemoryLeak extends AsyncTask<Void, Void, List<Contributor>> {

        @Override
        protected List<Contributor> doInBackground(Void... params) {
            RestAdapter restAdapter = new RestAdapter.Builder()
                    .setEndpoint("https://api.github.com")
                    .build();
            return restAdapter.create(GitHub.class).contributors("square", "retrofit");
        }

        @Override
        protected void onPostExecute(List<Contributor> contributors) {
            super.onPostExecute(contributors);
            StringBuilder stringBuilder = new StringBuilder();
            for (Contributor contributor : contributors) {
                stringBuilder.append(contributor.login).append(System.getProperty("line.separator"));
            }
            mTextView.setText(stringBuilder.toString());
        }
    }
}

代码很简单,我们实现了一个AsyncTask的子类ShowContributorsMemoryLeak,在该类内部请求网络数据,并将结果处理然后展示到界面上。该代码运行效果良好,但是,可能会造成Activity对象泄漏。
原因很简单,我们都知道内部类会一直保持一个父类的实例的引用,所以AsyncTask中的Thread运行多长时间他就会保存其父对象多长时间。如果用户频繁关闭、打开该界面或者频繁旋转屏幕,可能会造成程序Crash。

知道了问题之后,那么如何解决呢?

锁定屏幕方向?嗯,这是个好实践,如果你不觉得旋转屏幕能给用户提供更好的体验的话,确实应该锁死屏幕。不过这个解决用户频繁打开关闭的问题。
设置请求超时?你不是说AsyncTask运行多长时间就保存多长时间父对象嘛,我让AsyncTask运行时间短点不就行了。但是其实这仍然只是个治标不治本的方案,虽然这样做可能程序Crash几率降低很多,但是仍然有一段时间你的程序会占用很多额外内存,考虑到Java GC中的generations的话,这一段时间可能会很长。

其实这个问题有个比较简单的方案,就是WeakReference,修改原有代码,
1. 将AsyncTask的子类设置为静态。
2. 将TextView传递进去,并使用WeakReference<TextView>保存起来。
修改如下:

public class MainActivity extends ActionBarActivity {

    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mTextView = (TextView) findViewById(R.id.tv_content);

        // the AsyncTask will not cause memory leak
        new ShowContributorsNoMemoryLeak(mTextView).execute();
    }


    static class ShowContributorsNoMemoryLeak extends AsyncTask<Void, Void, List<Contributor>> {

        public WeakReference<TextView> mWeakTextView;

        public ShowContributorsNoMemoryLeak(TextView textView) {
            this.mWeakTextView = new WeakReference<TextView>(textView);
        }


        ...

        @Override
        protected void onPostExecute(List<Contributor> contributors) {
            ...
            TextView textView = mWeakTextView.get();
            if (textView !=null) {
                textView.setText(stringBuilder.toString());
            }
        }
    }


}

其中省略了大多数代码

以上代码就不会造成Activity泄露了。嗯,只是不会造成Activity的泄露。

AsyncTask对象

其实上面的代码中,AsyncTask对象还没被销毁。那AsyncTask怎么办呢?其实AsyncTask对象比Activity对象小很多,AsyncTask留存一段时间也无所谓的……
但是AsyncTask在android4.0以后所采用的线程池改为顺序执行,也就是说你的APP同一时间只能有一个线程在运行。假如用户打开这个Activity之后立马旋转了一下屏幕,造成的结果就是
1. 运行了两次AsyncTask,第一次是完全浪费了。
2. 用户看到的是第二次请求的结果,而且第二次请求必须在第一次请求完成之后才会开始。
Google Developer文档中有详细介绍如何处理这种情况,详细介绍参见Handling Runtime Change,按照文档我们修改一下代码如下:

public class MainActivity extends ActionBarActivity {

    TextView mTextView;
    private RetainedFragment dataFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (RetainedFragment) fm.findFragmentByTag("data");

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new RetainedFragment();
            fm.beginTransaction().add(dataFragment, "data").commit();
            // load the data from the web
            dataFragment.setData(new ShowContributorsNoMemoryLeakAndSaveInstance(this));
            dataFragment.getData().execute();
        } else {
            ShowContributorsNoMemoryLeakAndSaveInstance task = dataFragment.getData();
            if (task.mContributors == null) {
                showContributors(task.mContributors);
            } else {
                task.changeActivity(this);
            }
        }
    }

    private void showContributors(String contributor) {
        mTextView.setText(contributor);
    }


    static public class RetainedFragment extends Fragment {

        // data object we want to retain
        private ShowContributorsNoMemoryLeakAndSaveInstance data;

        // this method is only called once for this fragment
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // retain this fragment
            setRetainInstance(true);
        }

        public void setData(ShowContributorsNoMemoryLeakAndSaveInstance data) {
            this.data = data;
        }

        public ShowContributorsNoMemoryLeakAndSaveInstance getData() {
            return data;
        }
    }

    static class ShowContributorsNoMemoryLeakAndSaveInstance extends AsyncTask<Void, Void, List<Contributor>> {
        private String mContributors;
        private WeakReference<Activity> mWeakActivity;



        public ShowContributorsNoMemoryLeakAndSaveInstance(Activity activity) {
            this.mWeakActivity = new WeakReference<Activity>(activity);
        }

        public void changeActivity(Activity activity) {
            mWeakActivity = new WeakReference<Activity>(activity);
        }

       ...
        @Override
        protected void onPostExecute(List<Contributor> contributors) {
            super.onPostExecute(contributors);
            StringBuilder stringBuilder = new StringBuilder();
            for (Contributor contributor : contributors) {
                stringBuilder.append(contributor.login).append(System.getProperty("line.separator"));
            }
            mContributors = stringBuilder.toString();
            Activity activity = mWeakActivity.get();
            if (activity !=null) {
                ((MainActivity)activity).showContributors(stringBuilder.toString());
            }
        }
    }
}

其中省略了部分代码。呃呃,确实即使省略了很多代码,仍然还有一大坨代码啊,不过好在还是解决了我们之前提到的问题。

总结

在Activity中使用AsyncTask确实会造成很多问题,虽然我们讨论了如何解决这些问题,但是实际上解决这些问题需要额外写一大坨代码,好在在实践中我们有很多lib能减轻不少这些工作。下一篇我们看看我们最终的网络请求框架是什么样子的。

_以上程序涉及到的代码参见NetworkSample

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值