Android活动自我重建

21
我的应用程序通常都能正常工作,直到我在特定设备上遇到了一个奇怪的问题。该应用程序中有两个活动。在我从ActivityA中启动ActivityB后,ActivityA没有任何问题地启动。然而,当我通过按下硬件按钮或在ActivityB的closeButton中调用finish()返回ActivityA时,ActivityA重新加载自身。它会再次触发onCreate()并重新加载所有内容。而且我没有改变手机的方向。这种奇怪的行为只出现在1000次下载中的15台手机上。 这个问题只出现在Galaxy S3 Android OS 4.1.2上。这也很奇怪。 你有什么想法为什么会发生这种情况吗?
当我在按钮监听器中像这样启动一个新的活动时: ActivityA.java(MesajlarListViewActivity)
    public class MesajlarListViewActivity extends TrackedActivity {

    Context context = null;

    // contacts JSONArray
    JSONArray contacts = null;

    ArrayList<Message> productArray = new ArrayList<Message>();

    private ProductAdapter adapter;
    private ListView productList;
    private Runnable viewOrders;
    private HoloProgressIndicator profilInfoProgress = null;

    ImageView kapatButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mesajlar_list);

        context = this;

        kapatButton = (ImageView) findViewById(R.id.kapat_button);
        /* kapat button onclick listener. */
        // =================================================================================================================
        kapatButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view)
            {
                // Set vibration on touch.
                KnetGenericClass.vibratePhone(context);

                finish();
            }

        });
        // =================================================================================================================
        //Progress bar.
        profilInfoProgress = (HoloProgressIndicator) findViewById(R.id.profil_info_progress);

        // cheking internet connectivity.
        if(KnetGenericClass.checkInternetConnection(context))
        {
            // start task!
            /* internet var ise web service baglantisi kurmaya baslayabiliriz. */
            startActivityIndicatorWithThread();
        }
        else
        {
            KnetGenericClass.printErrorMessage(context, "Bağlantı Hatası",
                    "Lütfen internet bağlantınızı kontrol ediniz.");
        }

        productList = (ListView) findViewById(R.id.product_list);
        adapter = new ProductAdapter(this, R.layout.message_row, productArray);
        productList.setAdapter(adapter);

        // When user click a view on list view new page is appearing.
        productList.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id)
            {

                // Set vibration on touch.
                KnetGenericClass.vibratePhone(context);

                /* Navigate to message detay activity class with ilan ID. */
                Intent myIntent = new Intent(view.getContext(), MesajDetayActivity.class);
                myIntent.putExtra("messageID", productArray.get(position).getId());
                startActivity(myIntent);

                // setting image of clicked message null.
                RelativeLayout relativeLayout = (RelativeLayout) view;
                ImageView unreadedImageView = (ImageView) relativeLayout.findViewById(R.id.unreaded_image);
                unreadedImageView.setImageResource(0);
            }
        });
    }

    public class ProductAdapter extends ArrayAdapter<Message> {
        ArrayList<Message> items;

        public ProductAdapter(Context context, int textViewResourceId, ArrayList<Message> objects) {
            super(context, textViewResourceId, objects);
            this.items = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            if(convertView == null)
            {
                LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.message_row, null);
            }

            ImageView unreadedImageView = (ImageView) convertView.findViewById(R.id.unreaded_image);
            TextView productName = (TextView) convertView.findViewById(R.id.product_name);
            TextView productDetail = (TextView) convertView.findViewById(R.id.product_detail);
            // TextView productDate = (TextView)
            // convertView.findViewById(R.id.product_date);
            TextView sentDate = (TextView) convertView.findViewById(R.id.product_date);

            productName.setText(items.get(position).getSender());
            productDetail.setText(items.get(position).getTitle());
            // String bodyNoHTML = items.get(position).getBody();

            if(items.get(position).getIsReaded())
            {
                unreadedImageView.setImageResource(0);
            }
            else
            {
                unreadedImageView.setImageResource(R.drawable.bluedot);
            }

            String dateStr = items.get(position).getSentDate();
            try
            {
                sentDate.setText(dateStr.substring(6, 8) + "." + dateStr.substring(4, 6) + "." + dateStr.substring(0, 4)
                        +" "+dateStr.substring(8, 10)+":"+dateStr.substring(10, 12));
            }
            catch(Exception e)
            {
                sentDate.setText("");
            }


            return convertView;
        }

    }// @end of product adapter class.

    /* web service'e baglanti kurulan methodu threadin icerisinde cagiriyoruz. */
    public void startActivityIndicatorWithThread()
    {
        // ==============================================================================================
        // getting ilan details into arraylist.
        // setting up thread.
        viewOrders = new Runnable() {
            public void run()
            {
                getMessageListFromWebService();
            }
        };
        Thread thread = new Thread(null, viewOrders, "MagentoBackground");
        thread.start();
        profilInfoProgress.start();
        // ==============================================================================================
        // @end of the thread declaration.
    }

    public void getMessageListFromWebService()
    {
        // Creating JSON Parser instance
        JSONParser jParser = new JSONParser(context);

        // getting JSON string from URL
        JSONArray jsonArray = jParser.getAuthorizedInfoFromUrlToJSONArray(
                WebServiceInfo.getKnetWebServiceLink()+"/API/Member/GetInboxMessageList", MainActivity.getAccessToken());

        // if json is null then there is a problem.
        if(jsonArray == null)
        {
            // if json array is null then print error message.
            runOnUiThread(showAlertMessage);
            runOnUiThread(returnRes);
            return;
        }

        try
        {
            // Eger aranilan kritere gore ilan yok ise hata mesaji basiyoruz.
            if(jsonArray.length() == 0)
            {
                // if json array is null then print error message.
                runOnUiThread(showAlertIlanYokMessage);
                runOnUiThread(returnRes);
                return;
            }

            // looping through All Contacts
            for (int i = 0; i < jsonArray.length(); i++)
            {
                JSONObject c = jsonArray.getJSONObject(i);

                // Storing each json item in variable
                // String id = c.getString(TAG_ID);
                String id = c.getString("Id");
                String sender = c.getString("Sender");
                // String body = c.getString("Body");
                String title = c.getString("Title");
                String sentDate = c.getString("SentDate");
                Boolean isReaded = c.getBoolean("IsRead");

                Message productObject = new Message(id, sender, "", title, sentDate, isReaded);
                productArray.add(productObject);
            }
        }
        catch (Exception e)
        {
            Log.e("BACKGROUND_PROC", e.getMessage());
        }
        runOnUiThread(returnRes);
    }


    // @end of thread.
    private Runnable returnRes = new Runnable() {

        public void run()
        {
            profilInfoProgress.stop();
            adapter.notifyDataSetChanged();// refreshing data over adapter in
                                            // list view.
        }
    };

    // @end of thread.
    private Runnable showAlertMessage = new Runnable() {

        public void run()
        {
            // Bu hata genelde linkteki problemden, servera ulasilamamasindan
            // veya timeouttan meydana gelir.
            Toast.makeText(getApplicationContext(),
                    "Mesajlar alınamadı lütfen daha sonra tekrar deneyiniz.", 
                    Toast.LENGTH_LONG).show();
        }
    };

    private Runnable showAlertIlanYokMessage = new Runnable() {

        public void run()
        {
            // Bu hata aranilan kelimeye gore ilan bulunamazsa gelir.
            Toast.makeText(getApplicationContext(),
                    "Mesajlar bulunamadı.", 
                    Toast.LENGTH_LONG).show();
        }
    };

}

========================================================================

ActivityB.java (MesajDetayActivity.java)

public class MesajDetayActivity extends TrackedActivity {

    private HoloProgressIndicator profilInfoProgress = null;

    TextView titleTextView = null;
    TextView senderTextView = null;
    TextView dateTextView = null;
    WebView bodyWebView = null;

    Message messageObject = null;

    String messageID = null;

    ImageView kapatButton = null;

    Context context;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mesajdetaylari);

        context = this;

        kapatButton = (ImageView) findViewById(R.id.kapat_button);
        /* kapat button onclick listener. */
        // =================================================================================================================
        kapatButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view)
            {
                // Set vibration on touch.
                KnetGenericClass.vibratePhone(context);

                finish();
            }

        });
        // =================================================================================================================
        //Progress bar.
        profilInfoProgress = (HoloProgressIndicator) findViewById(R.id.profil_info_progress);

        Bundle extras = getIntent().getExtras();
        if(extras != null)
        {
            messageID = extras.getString("messageID");
        }

        titleTextView = (TextView) findViewById(R.id.title_textview);
        senderTextView = (TextView) findViewById(R.id.sender_textview);
        dateTextView = (TextView) findViewById(R.id.date_textview);
        bodyWebView = (WebView) findViewById(R.id.mesaj_webView);

        // Show the ProgressDialog on this thread
        profilInfoProgress.start();

        // Start a new thread that will download all the data
        new MakeItTask().execute();

    }

    // Async task.
    private class MakeItTask extends AsyncTask<String, Void, Object> {
        protected Object doInBackground(String... args)
        {
            Log.i("MyApp", "Background thread starting");

            // This is where you would do all the work of downloading your data
            // getting message detay
            /* connect to web service */
            getMessageDetayFromWebService();

            return null;
        }

        protected void onPostExecute(Object result)
        {
            // Pass the result data back to the main activity
            // TakipListeActivity.this.data = result;
            try
            {
                titleTextView.setText("Başlık: " + messageObject.getTitle());
                senderTextView.setText("Gönderen: " + messageObject.getSender());
                dateTextView.setText("Tarih: " + messageObject.getSentDate().substring(6, 8) + "."
                        + messageObject.getSentDate().substring(4, 6) + "."
                        + messageObject.getSentDate().substring(0, 4));

                if(!messageObject.getBody().contains("img"))
                {
                    bodyWebView.loadDataWithBaseURL(null, messageObject.getBody(), "text/html", "UTF-8", null);
                }

            }
            catch (Exception e)
            {
                Log.e(CONNECTIVITY_SERVICE, "Mesaj Detayi bilgileri basilamadi.");
            }

            profilInfoProgress.stop();
        }
    }

    /* web service'e baglanti kurulan methodu threadin icerisinde cagiriyoruz. */
    public void getMessageDetayFromWebService()
    {
        // Creating JSON Parser instance
        JSONParser jParser = new JSONParser(context);

        // getting JSON string from URL
        JSONObject jsonObject = jParser.getAuthorizedInfoFromUrlToJSONObject(
                WebServiceInfo.getKnetWebServiceLink()+"/API/Member/GetInboxMessage/" + messageID, MainActivity.getAccessToken());

        // if json is null then there is a problem.
        if(jsonObject == null)
        {
            return;
        }

        try
        {
            String title = jsonObject.getString("Title");
            String id = jsonObject.getString("Id");
            String sender = jsonObject.getString("Sender");
            String date = jsonObject.getString("SentDate");
            String body = jsonObject.getString("Body");

            messageObject = new Message(id, sender, body, title, date, true);

        }
        catch (Exception e)
        {
            Log.e("BACKGROUND_PROC", e.getMessage());
        }

    }// @end of getIlanDetayFromWebService.

}

编辑:不仅这两个活动存在此问题,所有在某些手机上表现出相同行为的活动也是如此。

如果ActivityA重新创建自己,会出现什么问题?这是有可能发生的。 - Zyoo
每次返回到ActivityA时,它都会重新创建。一个自动重启的应用程序可能会让用户感到烦恼。我们收到了一些用户关于这个问题的报告。 - OzBoz
我还是不明白,Activity的重建不需要那么烦人。我认为ActivityA被重新创建的唯一可能性是它之前已经被销毁了。 - Zyoo
重写“ActivityA”的“onDestroy”方法,在其中设置断点,查看是否调用该方法,并检查它是如何被调用的堆栈跟踪。 - Streets Of Boston
@StreetsOfBoston 谢谢。我在 ActivityA 中重写了 onDestroy 方法,但是在创建 ActivityB 时它会调用 onDestroy(也就是说,每当用户按下 startButton 时)。这怎么可能呢? - OzBoz
显示剩余3条评论
13个回答

49
检查是否启用了“设置”>“系统”>“开发人员选项”>“应用程序”下的“不保留活动”选项。

2
谢谢你告诉我原因。有时候深入了解事物会让你感到更加复杂。我不敢相信我怎么没看出来,浪费时间试图修复代码。 - OzBoz
谢谢您提到原因,我完全不知道原因。 - 1'hafs
它能工作,但为什么会在特定的活动中发生这种情况? - Jesus Dimrix

11
活动文档(http://developer.android.com/reference/android/app/Activity.html)对后台活动的生命周期有如下说明:

后台活动(即不可见并已暂停的活动)不再是关键性活动,因此系统可能安全地终止其进程以回收内存用于其他前台或可见进程。如果需要终止其进程,则当用户导航回到该活动时(再次在屏幕上可见),将调用其onCreate(Bundle)方法,并使用先前在onSaveInstanceState(Bundle)中提供的savedInstanceState使其能够以与用户上一次离开时相同的状态重新启动。

换句话说,在ActivityB处于活动状态时,操作系统可能会或可能不会销毁ActivityA,因此必须在代码中处理此情况。如果ActivityA已被销毁,则在用户在ActivityB中按返回按钮时将调用onCreate(Bundle)方法。

谢谢Mads,但是这种情况不会在后台活动时发生。我知道如何处理低内存杀死的活动。这种情况发生在UI出现在屏幕上时。 - OzBoz
1
我仍然认为这个答案适用。UI 在屏幕上,但它显示的是 ActivityB,对吧?如果 ActivityA 没有显示在屏幕上,它就会被暂停,并被操作系统视为后台活动,可以被销毁。 - Mads Frandsen
是的,可能是由于内存不足问题导致的。但是,一个自动重新启动的应用程序可能会让用户感到烦恼。处理 onSaveInstanceState 和 onRestoreInstanceState 不是解决我的问题的办法。因为每次返回到堆栈中的活动都会重新启动活动。 - OzBoz
2
我同意,重新启动应用程序可能会让用户感到烦恼。然而,我认为你应该在代码中处理这个问题,因为它可能发生在任何人身上,由于活动生命周期(不仅仅是那些每次都遇到这个问题的人)。对于每次返回到活动时都遇到问题的人来说,这听起来更像是他们操作系统中的一个错误,或者他们的设备内存非常低。如果应用程序像你问题中的简单示例一样工作,它绝对不应该在每次返回到活动时都发生这种情况。 - Mads Frandsen
1
@OzBoz,你可以尝试几种方法来测试这个答案。最简单的方法是在Activity中重写onLowMemory方法,并在内存不足时打印一些内容。还可以尝试在onDestroy方法中添加一些内容。你应该能够在onSaveInstanceState中保存状态,但你也可以在Android清单文件中添加android:largHeap="true"。这并不能保证什么,但可能会有所帮助。你还可以通过动画方式引入一个新的View对象,并将所有新的Activity代码移动到旧的Activity中。这不是最好的方法,但是可以解决问题。 - Phil
显示剩余2条评论

9
有一个名为“不保留活动”的Android开发者设置。该选项的描述为“用户离开时立即销毁每个活动”。这听起来像是你所看到的情况的良好描述,由于你只在少数手机上看到它,因此可能是由于非默认系统设置引起的。
理想情况下,即使效果不佳,您的应用程序仍应在此情况下工作。但是,如果此设置对您的应用程序造成问题,则您可能希望为您的用户记录此问题。

这也是正确的答案,很抱歉没能给你+50。非常感谢。 - OzBoz

7

你尝试过在Android清单文件中更改launchmode吗?尝试将以下内容添加到您的Activity声明中:

android:launchMode="singleTask"

接下来,请尝试使用startActivityForResult而不是startActivity。这将强制Activity A在Activity B完成时调用其onActivityResult(int,int,Intent)方法-这可能会跳过(有缺陷的)对onCreate的调用。然后,在Activity A中实现该方法以执行某些操作(例如打印调试语句):

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    Log.i("Test", "Did this work???");
    //TODO send notification to your server to verify this works?
}

  1. android:launchMode="singleTask" 不会改变任何东西。
  2. 是的,它可以工作,但是 Activity 仍然会重新创建。
- OzBoz
我遇到了同样的问题,使用startActivityForResult解决了它。我在第一个活动中使用了“startActivity”,然后使用“finish()”停止它。现在,在第一个活动中使用“startActivityForResult()”,并在第二个活动返回后的“onActivityResult”中使用“finish()”。看起来很好用。我认为这是有道理的,因为Android的生命周期模型假定您启动活动并返回到第一个活动。如果没有这个,并且第二个活动完成,屏幕上没有任何活动,我可以理解为什么Android会试图“纠正”通过重新启动活动来解决问题。 - Christine

5

3
你可以尝试在onCreate()中提供布局,并在onStart()中完成其余工作,如果有效的话。
 public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.show);
   }

并且

      @Override
       protected void onStart() {
       // TODO Auto-generated method stub
    super.onStart();
    Log.i(TAG, "On Start .....");

      }

请查看Activity生命周期。下面是一个示意图:enter image description here

1

它也遇到了同样的问题,并通过在manifest中的activity中使用android:launchMode="standard"解决了该问题。


我在每个activity上都加了android:launchMode="standard",并且也放在了<application>下,但问题仍然没有消失。 - OzBoz

1

在Activity A中覆盖onStart()和onResume()方法,并检查问题是否仍然存在。如果可能,请在此处提供您的Activity A和B代码。


请在两个活动中重写onResume。 - Dinesh Prajapati
我在两个Activity中重写了onResume、onStart和onStop,但没有任何变化。 - OzBoz
这里的TrackedActivity是什么?尝试将其移除,只保留带有onResume和其他方法的Activity,就像代码中一样。 - Dinesh Prajapati
TrackedActivity是Activity的子类。它是GoogleAnalytics框架的一部分,用于获取分析报告。我将其删除并在有问题的设备上重新编译,但仍然出现相同的问题。 - OzBoz

1

我认为这不是因为内存限制。

https://www.box.com/s/7pd0as03bb8wwumuc9l9

你应该测试这两个活动,并检查是否在此示例中发生。请分享你的AndroidManifest.xml文件内容,这将有助于调试。

1

或许你应该使用

Intent startIntent = new Intent(view.getContext(), ActivityB.class); 
startActivity(startIntent); 
finish() ;

Intent startIntent = new Intent(view.getContext(), ActivityA.class); 
startActivity(startIntent); 
finish() ;

每次你向前或向后移动时。

你可能误解了我的意思。我不是在问如何重新创建Activity,而是在问如何停止它自己的重新创建。 - OzBoz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接