Как я боролся с лагом NavigationDrawer с помощью AsyncTask

в 11:54, , рубрики: Песочница, метки: , , ,

Привет!

Меня зовут Никита. Я занимаюсь разработкой под Android. Хотел бы написать продолжение публикации «Как я с лагом Navigation Drawer боролся», где автор рассказывал, что обойти лаг Navigation Drawer при загрузке фрагмента можно через поток, который будет спать 300мс, и, соответственно, за это время должен успеть загрузиться фрагмент, а после закрыться NavigationDrawer.

Но, как я посчитал, это не наилучший метод решения проблемы, ибо для каждого устройства может понадобиться разное время, так как время работы потока зависит напрямую от характеристик устройства: чем оно слабее — тем больше времени требуется и наоборот.

Я тестировал свое приложение на двух устройствах

Samsung Galaxy Tab 2 7.0 (2 ядра — 1ГГц)
Huawei Honor 3x (8 ядер — 1.7 ГГц)

Конечно же, устройства неслабые (для тестового приложения), но одноядерного бюджетного смартфона у меня не имеется, а чтобы проверить работоспособность своего метода, добавил в фрагменты различные элементы (ImageView с картинкой в разрешении HD, TextView, TabHost и другие), потому что если фрагмент будет пустой, то AsyncTask даже не понадобится, ибо ничего загружаться не будет.

Ну, давайте приступим:

Создадим разметку главного экрана main.xml

Код

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
     tools:context=".MainActivity">

    <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- The main content view -->

        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <!-- The navigation drawer -->

        <ListView
            android:id="@+id/left_drawer"
            android:layout_width="240dp"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:background="#ff396c99"
            android:divider="@android:color/transparent"
            android:dividerHeight="0dp" />

    </android.support.v4.widget.DrawerLayout>
</RelativeLayout>

Теперь разметку фрагмента start_fragment.xml:

Код

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

        <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/photo"/>

</LinearLayout>

Теперь перейдем к MainActivity:

Код главной активности

public class MainActivity extends Activity {

    private ListView mListView;
    private DrawerLayout mDrawerLayout;
    private FrameLayout mContainer;
    private ActionBarDrawerToggle mDrawerToggle;
    CharSequence mTitle;
    MyAsyncTask mytask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mListView = (ExpandableListView) findViewById(R.id.left_drawer);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mContainer = (FrameLayout) findViewById(R.id.container);
        mTitle = getActionBar().getTitle();
        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); // установка тени к NavDrawer

        final String[] names = new String[] {
                "Фрагмент 1",
                "Фрагмент 2",
                "Фрагмент 3"
        };

        // используем адаптер данных
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,	android.R.layout.simple_list_item_1, names);

        mListView.setAdapter(adapter);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View itemClicked, int position,
                                    long id) {
                switch (position){
                    case 0:
                        mytask = new MyAsyncTask();
                        mytask.execute(new StartFragment()); //выбираете фрагмент, который хотите загрузить
                        break;
                    case 1:
                        mytask = new MyAsyncTask();
                        mytask.execute(new StartFragment2());
                        break;
                    default:;
                }
            }
        });

        mDrawerToggle = new ActionBarDrawerToggle(
                this,
                mDrawerLayout,
                R.drawable.ic_drawer,
                R.string.drawer_open,
                R.string.drawer_close
        ) {
            public void onDrawerClosed(View view) {
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu();
            }

            public void onDrawerOpened(View drawerView) {
                invalidateOptionsMenu();
            }
        };
        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);

        if (savedInstanceState == null){
            /*Здесь загрузим стартовый фрагмент, если нужно*/
        }
    }


    /*
    *** Меню
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mListView);
        return super.onPrepareOptionsMenu(menu);
    }
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
    }
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    /*
    * Собственно сам AsyncTask
    * */
    private class MyAsyncTask extends AsyncTask<Fragment, Integer, Fragment> {
        @Override
        protected Fragment doInBackground(Fragment... fragment) {
            FragmentManager fragmentManager = getFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager
                    .beginTransaction();
            fragment[0].setRetainInstance(true);
            fragmentTransaction.replace(R.id.container, fragment[0]);
            fragmentTransaction.commit();
            return fragment[0];
        }

        @Override
        protected void onPostExecute(Fragment fragment) {
            super.onPostExecute(fragment);
            mDrawerLayout.closeDrawers();
        }
    }

}

Не буду приводить пример Java кода фрагмента, ибо вы его сможете настроить сами и добавить свои элементы.

Пока каких-либо багов не обнаружено, приложение не вылетает и NavigationDrawer закрывается плавно.

Однако хочу отметить, что возможны зависания на слабых устройствах, если фрагмент действительно громоздкий, но в таком случае я бы добавил ProgressBar, который показывал бы, что приложение не зависло.

Был использован материал: «AsyncTask — сайт Александра Климова»

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js