Vraag Geen adapter aangesloten; de lay-out overslaan bij het klikken op de knop Terug en de app opnieuw starten


Android Studio 3.2 Canary 18
kotlin_version = 1.2.50

Ik heb een eenvoudige app die een recyclerview en een adapter gebruikt. Wanneer de app start, laadt u alle gegevens. Wanneer ik echter op de knop Terug klik en de app opnieuw start. Het zal de gegevens niet weergeven (blanco). Als ik de app uit het geheugen wis en de app start. De gegevens worden normaal geladen.

Ik laad de gegevens van sqlite en de gegevens worden elke keer geladen. zoals het de insectDataModelList.

Na het ingaan van de RecyclerView.java broncode de reden is de mAdapter is niets. Ik heb echter gecontroleerd of de adapter correct is wanneer ik deze op het recyclerview instel.

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

Mijn MainActivity.java is 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 is 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)
    }
}

De database Ik gebruik rxjava2 om de query uit te voeren

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();
                    }
                });
    }
}

Alles werkt zoals verwacht wanneer de apps voor de eerste keer worden geïnstalleerd. En als u het uit het geheugen opruimt. Het is echter alleen wanneer u op de knop Terug klikt en vervolgens probeert de app te starten en er worden geen gegevens geladen omdat de mAdapter nul is in de klasse RecyclerView.

Wanneer ik op de knop Terug klik en de app opnieuw start. Ik krijg alleen een leeg scherm, d.w.z.

enter image description here

DatabaseManager bijgewerkt klasse die de singleton verwijdert en een zwakke verwijzing gebruikt om ervoor te zorgen dat de instantie MainActivity vuilnis verzameld.

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();
                    }
                });
    }
}

Hartelijk dank voor alle suggesties,


10
2018-06-23 12:14


oorsprong


antwoorden:


Wanneer u op de knop Terug klikt en de app opnieuw start, wordt een nieuw exemplaar van MainActivity is begonnen.

Tegelijkertijd is uw DatabaseManager is een eenling. De referentie wordt opgeslagen als een statisch variabel. Het overleeft de activiteitrecreatie. Het zal leven totdat het proces is gedood.

Dus, als je rent queryAllInsects voor de tweede keer wordt de callback naar de oud instantie van MainActivity, wat niet meer zichtbaar is.

Je moet geen verwijzing naar houden MainActivity in DatabaseManager. Het is een geheugenlek, omdat het niet kan zijn afval verzameld.


6
2018-06-25 15:00



Het probleem is zeer waarschijnlijk dat u de gegevens laadt in uw onCreate () en niet in onResume (). Wanneer u op 'de app sluiten' drukt, wist u niet noodzakelijkerwijs de UI-stapel uit het geheugen. Dat is de reden waarom wanneer u teruggaat naar de app, dit niet opnieuw onCreate () inroept en uw gegevens niet opnieuw laadt.

Houd alles hetzelfde, verplaats uw gegevens gewoon van onCreate () naar onResume (). Op die manier worden de gegevens geladen wanneer het scherm aan de gebruiker wordt getoond.


1
2018-06-25 15:07



Weinig observaties:

  1. U geeft nog steeds de MainActivity door aan de BugsDbHelper-klasse, zorgt voor de verwijzing daar.
  2. Het is waarschijnlijk een goed idee om een ​​"reinigingsmethode" op te nemen in Singleton-klassen, die in onStop () of onDestroy () van een activiteit moeten worden aangeroepen. onStop () heeft de voorkeur omdat onDestroy () niet gegarandeerd direct wordt aangeroepen.
  3. De "reinigingsmethode" in Singleton klasse zou het volgende moeten doen: a) Nultificeer alle verwijzingen naar de parameters, objecten, context of callbacks die u hebt gevraagd als afhankelijkheid in de constructor of anderszins. b) Als de Singleton-klasse "nieuwe" objecten heeft gemaakt met contextafhankelijkheden, moet u ook in deze klassen soortgelijke opschoningsmethoden opnemen.
  4. Om crashes en geheugenlekken in fragmenten / activiteiten te voorkomen, moet u ervoor zorgen dat u de weergave / adapter van uw recycler opslaat in onStop (). De callbacks kunnen op elk moment worden ontvangen en als dat gebeurt terwijl je activiteit op de achtergrond is, moet je een 'force close' fortune cookie krijgen.
  5. Houd de activiteit / fragment levenscyclus in de gaten. Veel problemen zijn alleen vanwege het negeren van callbacks tijdens de levenscyclus. Deze zijn er voor een reden, gebruik ze.

1
2017-07-02 07:06



Zet deze 2 regels in onResume () en verwijder uit onCreate () en probeer het.

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

1
2017-07-02 10:41



Ik stel de volgende wijzigingen voor:

HoofdactiviteitHoe minder code u in de activiteit schrijft, des te beter het gedeelte voor het ophalen van gegevens naar de DatabaseManager. Stel ook de RecyclerView eens en alleen het updaten van dataset indien van toepassing:

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();
    }
}

En in DatabaseManager, in plaats van de gegevensbron (Cursor) te observeren en de aanvrager (activiteit) via op de hoogte te stellen Bel terug, we krijgen de gegevens stroom en geef het de beller door aan waarnemen:

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);
                    }
                });
    }
}

Nu is de oplossing hier om op te vertrouwen RxJava om het te veranderen Bel terug patroon voor de waarnemer patroon. Dus in plaats van het passeren van de activiteit (terugbellen) en wachten om gebeld te worden, krijgen we de gegevens steram (waarneembaar) en waarnemen het voor het antwoord. Dit elimineert het lekprobleem allemaal samen en verbetert de leesbaarheid en onderhoudbaarheid.

Vergeet ook niet om de Database  initialisatie naar de klasse Application of een instantie Singleton om meerdere instantiatie te voorkomen. De eenvoudigere oplossing zou zijn als:

public class ApplicationController extends Application {

    private BugsDbHelper mBugsDbHelper;

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

    public BugsDbHelper getDbHelper(){
        return mBugsDbHelper ;
    }
}

1
2017-07-02 10:46