Адаптер не подключен; Пропуск макета при нажатии кнопки «Назад» и повторном запуске приложения - PullRequest
0 голосов
/ 23 июня 2018
Android Studio 3.2 Canary 18
kotlin_version = 1.2.50

У меня есть простое приложение, которое использует переработчик и адаптер. При запуске приложения загружаются все данные. Однако, когда я нажимаю кнопку «Назад» и снова запускаю приложение. Это не будет отображать данные (пусто). Если я очищаю приложение из памяти и запускаю приложение. Данные будут загружены как обычно.

Я загружаю данные из sqlite, и данные загружаются каждый раз. как он заполняет insectDataModelList.

После входа в исходный код RecyclerView.java причина в том, что mAdapter - ноль. Тем не менее, у меня есть проверил, что адаптер правильный, когда я установил его в программе просмотра.

void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        ...
}

My MainActivity.java - это Java

public class MainActivity extends AppCompatActivity {
    private RecyclerView rvInsects;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        rvInsects = (RecyclerView)findViewById(R.id.recycler_view);

        DatabaseManager databaseManager = DatabaseManager.getInstance(this);
        databaseManager.queryAllInsects("friendlyName");
    }

    private void setupAdapter(List<InsectDataModel> insectDataModelList) {
        final LayoutManager layoutManager = new LinearLayoutManager(
                this, LinearLayoutManager.VERTICAL, false);
        rvInsects.setLayoutManager(layoutManager);
        rvInsects.setHasFixedSize(true);
        final InsectAdapter insectAdapter = new InsectAdapter(insectDataModelList);
        rvInsects.setAdapter(insectAdapter);
        insectAdapter.notifyDataSetChanged();
    }

    /* Callback from database */
    public void loadAllInsects(final Cursor cursor) {
        InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
        final List<InsectDataModel> insectDataModelList = insectInteractorMapper.map(cursor);
        /* data loaded with 24 items */
        setupAdapter(insectDataModelList);
    }
}

InsectAdapter.kt - это Kotlin.

class InsectAdapter(private val insectList: MutableList<InsectDataModel>)
    : RecyclerView.Adapter<InsectAdapter.CustomInsectHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomInsectHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.insect_row_item, parent, false)
        return CustomInsectHolder(view)
    }

    override fun onBindViewHolder(holder: CustomInsectHolder, position: Int) {
        holder.tvFriendlyName.text = insectList[position].friendlyName
        holder.tvScientificName.text = insectList[position].scientificName
    }

    override fun getItemCount(): Int {
        return insectList.size
    }

    class CustomInsectHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val ivDangerLevel: DangerLevelView = itemView.findViewById(R.id.ivDangerLevel)
        val tvFriendlyName: TextView = itemView.findViewById(R.id.tvFriendlyName)
        val tvScientificName: TextView = itemView.findViewById(R.id.tvScientificName)
    }
}

База данных, которую я использую для выполнения запроса rxjava2

public class DatabaseManager {
    private static DatabaseManager sInstance;
    private MainActivity mainActivity;
    private BugsDbHelper mBugsDbHelper;

    public static synchronized DatabaseManager getInstance(MainActivity context) {
        if (sInstance == null) {
            sInstance = new DatabaseManager(context);
        }

        return sInstance;
    }

    private DatabaseManager(MainActivity context) {
        mBugsDbHelper = new BugsDbHelper(context);

        mainActivity = context;
    }

    @SuppressLint("CheckResult")
    public void queryAllInsects(String sortOrder) {
        final InsectStorageInteractorImp insectStorageInteractorImp
                = new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

        insectStorageInteractorImp.getAllSortedInsects(sortOrder)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<Cursor>() {
                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                    }

                    @Override
                    public void onSuccess(Cursor cursor) {
                        mainActivity.loadAllInsects(cursor);
                        disposable.dispose();
                    }

                    @Override
                    public void onError(Throwable e) {
                        disposable.dispose();
                    }
                });
    }
}

Все работает, как и ожидалось, когда приложения устанавливаются в первый раз. И если вы очистите его из памяти. Однако, только когда вы нажмете кнопку «Назад», а затем попытаетесь запустить приложение, он не будет загружать любые данные из-за того, что mAdapter является нулевым в классе RecyclerView.

Когда я нажимаю кнопку «Назад», а затем снова запускаю приложение. Все, что я получаю, это пустой экран, т.е.

enter image description here

Обновлен класс DatabaseManager, который удаляет синглтон и использует слабую ссылку, чтобы обеспечить сборку мусора экземпляром MainActivity.

public class DatabaseManager {
    private WeakReference<MainActivity> mainActivity;
    private BugsDbHelper mBugsDbHelper;

    public DatabaseManager(MainActivity context) {
        mBugsDbHelper = new BugsDbHelper(context);
        mainActivity = new WeakReference<>(context);
    }

    @SuppressLint("CheckResult")
    public void queryAllInsects(String sortOrder) {
        final InsectStorageInteractorImp insectStorageInteractorImp
                = new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

        insectStorageInteractorImp.getAllSortedInsects(sortOrder)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<Cursor>() {
                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                    }

                    @Override
                    public void onSuccess(Cursor cursor) {
                        mainActivity.loadAllInsects(cursor);
                        disposable.dispose();
                    }

                    @Override
                    public void onError(Throwable e) {
                        disposable.dispose();
                    }
                });
    }
}

Большое спасибо за любые предложения,

Ответы [ 5 ]

0 голосов
/ 02 июля 2018

Я предлагаю следующие изменения:

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

public class MainActivity extends AppCompatActivity {

    private List<InsectDataModel> insectDataModelList = new ArrayList<>();

    private Disposable disposable;
    private RecyclerView rvInsects;
    private InsectAdapter insectAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        setupAdapter();

        //Request Data, take advantage of RxJava to load data asynchronously
        DatabaseManager.getInstance(this)
                .queryAllInsects("friendlyName")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<List<InsectDataModel>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                    }

                    @Override
                    public void onSuccess(List<InsectDataModel> response) {
                        insectDataModelList.clear();
                        insectDataModelList.addAll(response);
                        insectAdapter.notifyDatasetChanged();
                    }

                    @Override
                    public void onError(Throwable e) {
                        Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                });;
    }

    private void setupAdapter() {
        //Setup RecyclerView Only need to be called once
        rvInsects = (RecyclerView)findViewById(R.id.recycler_view);
        LayoutManager layoutManager = new LinearLayoutManager(this);    // LinearLayoutManager is Vertical by default
        rvInsects.setLayoutManager(layoutManager);  // You don't event have to define it as RecyclerView use LinearLayoutManager.Vertical by default
        rvInsects.setHasFixedSize(true);
        insectAdapter = new InsectAdapter(insectDataModelList);
        rvInsects.setAdapter(insectAdapter);
    }


    @Override
    protected void onDestroy() {
        //Dispose observer if activity is destroyed to prevent memory leak
        if(disposable != null && !disposable.isDisposed())
            disposable.dispose();

        super.onDestroy();
    }
}

И в DatabaseManager вместо наблюдения источника данных (Курсор) иуведомить запросившего (Activity) через обратный вызов , мы получаем данные stream и передаем его вызывающей стороне на наблюдение :

public class DatabaseManager {
    private static DatabaseManager sInstance;
    private BugsDbHelper mBugsDbHelper;

    public static synchronized DatabaseManager getInstance() {
        if (sInstance == null) {
            sInstance = new DatabaseManager();
        }

        return sInstance;
    }

    private DatabaseManager() {
        // Move the actualy database initiation to application class or singleton
        mBugsDbHelper = BugsDbHelper.getInstance(); // or ApplicationController.getDbHelper();
    }

    @SuppressLint("CheckResult")
    public SingleObserver<List<InsectDataModel>> queryAllInsects(String sortOrder) {
        final InsectStorageInteractorImp insectStorageInteractorImp
                = new InsectStorageInteractorImp(new InsectStorageImp(mBugsDbHelper.getReadableDatabase()));

        insectStorageInteractorImp.getAllSortedInsects(sortOrder)
                .map(new Function<Cursor, List<Object>>() {
                    @Override
                    public List<Object> apply(Cursor cursor) throws Exception {
                        InsectInteractorMapper insectInteractorMapper = new InsectInteractorMapperImp();
                        return insectInteractorMapper.map(cursor);
                    }
                });
    }
}

сейчасрешение здесь заключается в том, чтобы положиться на RxJava, чтобы изменить шаблон callback на шаблон наблюдатель .Таким образом, вместо прохождения активность (обратный вызов) и ожидания вызова, мы получаем данные steram (наблюдаемые) и наблюдаем за ответом.Это вместе устраняет проблему утечки и повышает удобочитаемость и удобство обслуживания.

Также не забудьте переместить инициализацию Database в класс Application или экземпляр Singleton, чтобы предотвратить множественное создание экземпляров.,Более простое решение будет выглядеть так:

public class ApplicationController extends Application {

    private BugsDbHelper mBugsDbHelper;

    @Override
    public void onCreate() {
        super.onCreate();
        mBugsDbHelper = new BugsDbHelper(this);
    }

    public BugsDbHelper getDbHelper(){
        return mBugsDbHelper ;
    }
}
0 голосов
/ 02 июля 2018

Поместите эти 2 строки в onResume (), удалите из onCreate () и попробуйте.

 DatabaseManager databaseManager = DatabaseManager.getInstance(this);
 databaseManager.queryAllInsects("friendlyName");
0 голосов
/ 25 июня 2018

Скорее всего, проблема в том, что вы загружаете данные в onCreate (), а не в onResume (). Когда вы нажимаете «закрыть приложение», вы не обязательно очищаете стек пользовательского интерфейса от памяти. Вот почему, когда вы возвращаетесь в приложение, оно больше не вызывает onCreate () и не загружает ваши данные снова.

Сохраняйте все то же самое, просто переместите загрузку данных из onCreate () в onResume (). Таким образом, всякий раз, когда пользователю отображается экран, данные загружаются.

0 голосов
/ 02 июля 2018

Несколько наблюдений:

  1. Вы все еще передаете MainActivity классу BugsDbHelper, позаботьтесь о ссылке там.
  2. Вероятно, хорошей идеей будет включить «метод очистки» в классы Singleton, который должен вызываться в onStop () или onDestroy () действия. onStop () предпочтительнее, так как onDestroy () не гарантированно вызывается немедленно.
  3. «Метод очистки» в классе Singleton должен делать следующее: а) Обнулить любые ссылки на параметры, объекты, контекст или обратные вызовы, которые вы задали в качестве зависимости в конструкторе или иным образом. б) Если класс Singleton создал «новые» объекты с контекстными зависимостями, убедитесь, что в эти классы также включены похожие методы очистки.
  4. Чтобы избежать сбоев и утечки памяти во фрагментах / действиях, убедитесь, что вы очищаете свой вид / адаптер переработчика в onStop (). Обратные вызовы могут быть получены в любое время, и если это произойдет, когда ваша активность находится в фоновом режиме, вы обязательно получите «принудительное закрытие» cookie-файл состояния.
  5. Следите за жизненным циклом действия / фрагмента. Многие проблемы связаны только с игнорированием обратных вызовов жизненного цикла. Они есть по причине, используйте их.
0 голосов
/ 25 июня 2018

Когда вы нажимаете кнопку «Назад» и перезапускаете приложение, запускается новый экземпляр MainActivity.

В то же время ваш DatabaseManager является синглтоном .Его ссылка хранится как static переменная.Выживает активность отдыха.Он будет действовать до тех пор, пока процесс не будет убит.

Итак, когда вы запускаете queryAllInsects во второй раз, обратный вызов отправляется на старый экземпляр MainActivity, который не являетсявиден больше.

Вы не должны хранить ссылку на MainActivity в DatabaseManager.Это утечка памяти , потому что она не может быть сборщиком мусора .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...