设置可见性上的三星 G5 空指针异常

Samsung G5 nullpointerexception on setvisibility

Android 应用程序代码可在各种设备上成功运行,包括早至 API 14 到 API 19(目标)。但是,三星 G5 v4.4.4 在尝试为 activity 设置 Visibilty(true) 时抛出 NPE。此错误可能只是在最近通过从 Sprint 下载升级 G5 操作系统后才开始出现。我们审查了多种 NPE 问题和三星特定问题,但 none 似乎适用。

日志:

01-08 20:58:40.122: W/dalvikvm(7972): threadid=1: thread exiting with uncaught exception (group=0x41963da0)

01-08 20:58:40.132: W/System.err(7972): java.lang.NullPointerException

01-08 20:58:40.132: W/System.err(7972): at android.app.Activity.makeVisible(Activity.java:4355)

01-08 20:58:40.142: W/System.err(7972): at android.app.Activity.setVisible(Activity.java:4336)

01-08 20:58:40.142: W/System.err(7972): at com.taskassure.app.StartTaskActivity.setActivityVisible(StartTaskActivity.java:531)

01-08 20:58:40.142: W/System.err(7972): at com.taskassure.app.StartTaskActivity.access(StartTaskActivity.java:529)

01-08 20:58:40.142: W/System.err(7972): at com.taskassure.app.StartTaskActivity.run(StartTaskActivity.java:298)

01-08 20:58:40.142: W/System.err(7972): at android.os.Handler.handleCallback(Handler.java:733)

01-08 20:58:40.142: W/System.err(7972): at android.os.Handler.dispatchMessage(Handler.java:95)

01-08 20:58:40.142: W/System.err(7972): at android.os.Looper.loop(Looper.java:146)

01-08 20:58:40.142: W/System.err(7972): at android.app.ActivityThread.main(ActivityThread.java:5678)

01-08 20:58:40.142: W/System.err(7972): at java.lang.reflect.Method.invokeNative(Native Method)

01-08 20:58:40.152: W/System.err(7972): at java.lang.reflect.Method.invoke(Method.java:515)

01-08 20:58:40.152: W/System.err(7972): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)

01-08 20:58:40.152: W/System.err(7972): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)

01-08 20:58:40.152: W/System.err(7972): at dalvik.system.NativeStart.main(Native Method)

审查 StartTaskActivity 确认我们正在尝试在抛出异常时将可见性设置为 true。相关代码段包括:

将启动失败的活动 activity (StartTaskActivity):

/**
* The intent to open the task start confirm dialog. Put in globalspace so
* that data can be added to it from anywhere in this class.
*/
public intent      confirmActivity  = null;

/**
* Sets up the tab view showing the task details and checkpoints, as well as
* setting up the location client to get the most recent location.
*/

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.view_task_activity);
// Set up the action bar.
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

...

// initialize confirmActivity so we can add the necessary
// information from our fragments
confirmActivity = new Intent(getApplicationContext(),
    StartTaskActivity.class);
}

将启动 StartTaskActivity

的 class 中的相关方法

/** * Sends a request to start the task to the server, gets back any checkpoint * warnings that need to be overridden before we can start the task. Upon * overriding any warnings (if any) The StartTaskActivity is launched. */

private void requestTaskStart()
{

...
          try
          {
            JSONObject JsonResponse = new JSONObject(responseBody);
            JSONArray checkpoints = (JSONArray) JsonResponse
                .get("chekpoint_status");

            JSONObject userData = new JSONObject(getIntent().getExtras()
                .getString("user"));
            userData = userData.getJSONObject("user");
            confirmActivity.putExtra("training_set_size", new JSONObject(
                getIntent().getExtras().getString("user"))
                .getInt("training_set_size"));
            confirmActivity.putExtra("requestStartInfo",
                responseBody);
            confirmActivity.putExtra("user_id",
                Integer.parseInt(userData.getString("id")));
            confirmActivity.putExtra("taskId", task.getInt("id"));

            mDialog.dismiss();

            // show checkpoint override if there are any
            if ( checkpoints.length() != 0 )
            {
              // show first checkpoint dialog
              showCheckpointDialog(checkpoints, 0);
            }
            else
            {
              startActivityForResult(confirmActivity,
                  CONFIRM_TASK_START);
            }

StartTaskActivity - activity class 在三星 G5 上抛出 NPE

/**
* Activity that is shown after choosing to start a task. Shows the confirmation
* window before a task is started. This activity also handles starting face
* verification if necessary.
*/

public class StartTaskActivity extends Activity
{
    ...
/**
* Creates the task confirm screen, downloads the users photo from the server.
* Checks the task checkpoints to see if face verification needs to be done
* before starting the task. Keeps the activity invisible until all
* checkpoints are properly met.
*/
@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.start_task_confirm_layout);

    Intent launchIntent = getIntent();
    Bundle args = launchIntent.getExtras();

    try
    {
      requestTaskStartData = new JSONObject(args.getString("requestStartInfo"));
      taskCheckpoints = new JSONArray(args.getString("checkpoints"));
      taskId = args.getInt("taskId");

      ((TextView) findViewById(R.id.task_confirm_textview))
          .setText(requestTaskStartData.getString("task_summary"));

      new Thread(new Runnable()
      {
        @Override
        public void run()
        {

            ... 
            // code retrieves an image file from server on separate thread
            // depending on results, call checkVerifyIdentity for additional processing and to show view
            ... 
            checkVerifyIdentity(bmp)

  }).start();

  ((TextView) findViewById(R.id.task_password_content_textview))
      .setText(requestTaskStartData.getString("task_password"));

}
catch ( JSONException e )
{
  ((TextView) findViewById(R.id.task_password_title_textview))
      .setVisibility(TextView.INVISIBLE);

  e.printStackTrace();
}

findViewById(R.id.task_confirm_button).setOnClickListener(
    new View.OnClickListener()
    {

      @Override
      public void onClick(View v)
      {
        setResult(RESULT_OK);
        finish();
      }
    });

findViewById(R.id.task_deny_button).setOnClickListener(
    new View.OnClickListener()
    {

      @Override
      public void onClick(View v)
      {

        setResult(RESULT_CANCELED);
        finish();
      }
    });

}  // end of StartTaskActivity.onCreate

    ...

// Check some parameters, and finish setting up view for display (runs on UI thread)
private void checkVerifyIdentity(final Bitmap bmp)
{
    final Context context = this;
    StartTaskActivity.this.runOnUiThread(new Runnable()
    {
      public void run()
      {
        if ( bmp != null )
        {
          ((ImageView) findViewById(R.id.task_confirm_imageview))
              .setImageBitmap(bmp);
        }
        if ( taskCheckpoints.length() > 0 )
        {
            ... // do some processing
        }
        else
        {
          setActivityVisible();  
        }
      }
    });
}

    ...
/**
* Sets the activity as visible. Should be called once all verifications are
* properly checked.
*/
private void setActivityVisible()
{
    this.setVisible(true);

}

上面的 setVisible 行是 StartTaskActivity 的第 531 行,它最终导致三星 G5 的 NPE,但不是我们可以测试的其他 devices/versions。正如评论中所反映的那样,随后在 4.4.4 模拟器上进行的测试无法重现该错误。 到目前为止,仅在 4.4.4 的实际三星 G5 上观察到错误。

更新:

基于 labor intensive debug step process 将良好的源视图(4.4.4 模拟器)与三星不正确的源视图进行映射,我们缩小了 NPE 的原因。应用程序在调用 StartTaskActivity.setActivityVisible 时抛出 NPE,最终遇到空对象。由于跟踪过程的限制,我无法确定那个对象是什么,但我的猜测是它是窗口或视图。抛出它的代码行是“mDecor.setVisibility(View.VISIBLE)”(三星中的第 4355 行 = Activity.java 的模拟器中的第 4143 行)。因此,从技术上讲,mDecor 对象的某些部分是空的。

可能会采取不同的方法来实现我们的目标,因为我们无法确定为什么 Samsung v4.4.4 抛出 NPE 而其他 device/emulator 似乎没有抛出 NPE,包括 Samsung v4.4.2。也许即使这个问题没有得到解决,但它可能对将来的其他人有用。

花费了大量时间试图找出此问题的原因。我们无法在除 Samsung G5 (Sprint) 运行 4.4.4 之外的任何其他设备或模拟器上重现 NPE 异常。当尝试在 activity 上使用 setVisible 方法时发生崩溃,该方法从 onCreate 中启动了后台线程,然后启动了 runsOnUiThread。也许这是一种不正确的做法,出于某种原因没有受到其他系统的困扰,但三星(至少)认为这是不可接受的。

无论如何,我们通过 showing/hiding 与 activity 关联的视图(例如使用 v.setVisibility(View.VISIBLE) 和一些有价值的重构来解决这个问题。如 Android 文档,请谨慎使用 Activity#setVisible 方法。