Unknown class in Interface Builder file 解决方案

本文介绍了一种在iOS开发过程中遇到的问题:删除main.storyboard及其配置文件info.plist后导致项目无法运行的情况。作者通过全局搜索发现Storyboard中存在未使用的ViewController类,并在移除该类的继承关系后成功解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我在控制台,看到了如下日志:

我的情况是:
删除main.storyboard,删除info.plist的配置

运行结果是运行失败。


于是我全局搜索了一下“ViewController”这个类,发现未找到,但是在Main.storyboard中,有个继承ViewController的故事版(storyboard),这应该是在新建项目时自带的,开发中,没有使用到“ViewController”这个类,便被删除,出现了这样的情况。去除在Main.storyboard中ViewController的继承,就ok了。
android 15中报错:// CRASH: com.android.mms (pid 32001) // Short Msg: java.lang.NullPointerException // Long Msg: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference // Build Label: SONIM/X53001/X530:15/X53.0-01-15.0-99.01.11/jenkins:userdebug/test-keys // Build Changelist: jenkins // Build Time: 1754634422000 // java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference // at com.android.mms.ui.AttachmentViewActivity.onDataLoadFinished(AttachmentViewActivity.java:281) // at com.android.mms.ui.BaseAttachmentViewActivity$GetDataAsyncTask.onPostExecute(BaseAttachmentViewActivity.java:222) // at com.android.mms.ui.BaseAttachmentViewActivity$GetDataAsyncTask.onPostExecute(BaseAttachmentViewActivity.java:190) // at android.os.AsyncTask.finish(AsyncTask.java:771) // at android.os.AsyncTask.-$$Nest$mfinish(Unknown Source:0) // at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788) // at android.os.Handler.dispatchMessage(Handler.java:107) // at android.os.Looper.loopOnce(Looper.java:232) // at android.os.Looper.loop(Looper.java:317) // at android.app.ActivityThread.main(ActivityThread.java:8794) // at java.lang.reflect.Method.invoke(Native Method) // at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) // at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:892) ,具体代码如下:AttachmentViewActivity.java:/* * Sonim Technologies, Inc. CONFIDENTIAL * Copyright (c) 2017 All Rights Reserved. * * The source code contained or described herein and all documents * related to the source code ("Material") is property of Sonim * Technologies, Inc. and its affiliates, may be covered by U.S. * or Foreign Patents or patents in process, and is protected by trade * secret or copyright law. No part of the Material may be used, * copied, reproduced, modified, published, uploaded, posted, * transmitted, distributed, or disclosed in any way without * prior written permission by Sonim Technologies, Inc. * * Unless required by applicable law or agreed to in writing, * the Materials are provided "AS IS," and WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. No license * under any patent, copyright, trade secret or other * intellectual property right is granted to or conferred upon you * by disclosure or delivery of the Material, either expressly, by * implication, inducement, estoppel or otherwise. Any license * under such intellectual property rights must be express and * approved by Sonim Technologies, Inc. in writing. * */ package com.android.mms.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.text.Html; import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.Menu; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.android.mms.R; import com.android.mms.model.MediaModel; import com.android.mms.model.MediaWithTextModel; import com.android.mms.ui.multipleviews.BaseViewFragment; import com.android.mms.ui.multipleviews.VideoViewFragment; import com.google.android.mms.ContentType; import java.util.ArrayList; public class AttachmentViewActivity extends BaseAttachmentViewActivity { private static final String TAG = "AttachmentViewActivity"; static final int SAVE_SLIDER_ATTACHMENT_PERMISSION_REQUEST_CODE = 2017; private static final int DEFAULT_VISIBLE_LIST_COUNT = 3; private static final int DEFAULT_RECYCLER_VIEW_HEIGHT = 0; private static final int RECYCLER_VIEW_DEVICE_PORTION_ON_DEVICE_HEIGHT = 4; private static final boolean CONTAINER_FULLVIEW_MODE_VISIBLE = true; private static final boolean CONTAINER_FULLVIEW_MODE_INVISIBLE = false; private static final String VIEW_VCARD = "VIEW_VCARD_FROM_MMS"; private static final String KEY_PACKAGE_NAME = "calling_package_name"; private static final String PACKAGE_NAME = "com.android.mms"; private static final String EXTRA_SINGLE_ITEM = "SingleItemOnly"; private LinearLayout mDotsLayout; private TextView[] mDotsTextview; private SlidesAdapter mSlidesAdapter; private RecyclerView mSlidesRecyclerView; /** * Default recycler view height will be zero. Based on Device height it will be calculate the * recyclerview height dynamically. */ private int mRecyclerViewHeight = 0; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (MessageUtils.checkPermissionsIfNeeded(this)) { return; } setContentView(R.layout.attachment_view_activity); new GetDataAsyncTask(this).execute(); } private void initView() { mSelectedAttachmentContainer = (LinearLayout) findViewById(R.id.frameLayout_container); mDotsLayout = (LinearLayout) findViewById(R.id.dots_layout); mSlidesRecyclerView = (RecyclerView) findViewById(R.id.slides_recycler_view); calculateRecyclerViewHeight(); resetRecyclerViewHeight(mRecyclerViewHeight); RecyclerView.LayoutManager layoutManager = new LinearLayoutManagerWrapper(getApplicationContext(), LinearLayoutManager.HORIZONTAL, false); mSlidesRecyclerView.setLayoutManager(layoutManager); mSlidesRecyclerView.setItemAnimator(new DefaultItemAnimator()); mSlidesAdapter = new SlidesAdapter(mMediaWithTextModelList, this); mSlidesRecyclerView.setAdapter(mSlidesAdapter); // << [DNL-7826] 20210422 JunYiLin >> mSlidesAdapter.setOnDataSetChangedListener(mDataSetChangedListener); // << [DNL-7080] 20210330 JunYiLin << mSlidesRecyclerView.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { updateNavBar(getResources().getStringArray(R.array.setNavBar_highlight_attachments_view_in_slideshow)); } } }); // [DNL-7080] >> } /** * prepare display dots layout */ private void displayDotsLayout() { if (mDotsTextview == null) { if (mMediaWithTextModelList.size() % DEFAULT_VISIBLE_LIST_COUNT == 0) { mDotsTextview = new TextView[mMediaWithTextModelList.size() / DEFAULT_VISIBLE_LIST_COUNT]; } else { mDotsTextview = new TextView[ (mMediaWithTextModelList.size() / DEFAULT_VISIBLE_LIST_COUNT) + 1]; } for (int i = 0; i < mDotsTextview.length; i++) { TextView dotTextView = new TextView(this); mDotsTextview[i] = dotTextView; mDotsTextview[i].setText(Html.fromHtml(getResources(). getString(R.string.dots_text_design))); mDotsTextview[i].setTextSize(getResources().getDimension(R.dimen.dot_text_design_size)); mDotsTextview[i].setTextColor(getResources().getColor(R.color.dot_inactive)); Log.v(TAG, "mDotsTextview text: "+mDotsTextview[i].getText()); mDotsLayout.addView(mDotsTextview[i]); } changeDotsColor(0); } Log.d(TAG,"fxj 147"); // Message UABT-1662 Haifei.Liao remove the dot layout BEGIN mDotsLayout.setVisibility(View.GONE); // Message UABT-1662 Haifei.Liao remove the dot layout END } @Override public void onRequestPermissionsResult(final int requestCode, final String permissions[], final int[] grantResults) { if (requestCode == SAVE_SLIDER_ATTACHMENT_PERMISSION_REQUEST_CODE) { if (MessageUtils.hasStoragePermission()) { //TODO: Check for permission enabled or not } else { Toast.makeText(this, R.string.no_permission_save_attachment_to_storage, Toast.LENGTH_SHORT).show(); } } } /** * change dots color whenever focus going to change left to right or right to left * @param currentPage */ private void changeDotsColor(int currentPage) { if (mDotsTextview == null) { return; } int focus = currentPage / DEFAULT_VISIBLE_LIST_COUNT; if (focus > 0) { mDotsTextview[focus - 1].setTextColor(getResources().getColor(R.color.dot_inactive)); } if (focus < mDotsTextview.length - 1) { mDotsTextview[focus + 1].setTextColor(getResources().getColor(R.color.dot_inactive)); } mDotsTextview[focus].setTextColor(getResources().getColor(R.color.dot_active)); } @Override public void onBackPressed() { /** * When click on back button check for is it in full view mode or not. If it is in full view mode * adjust to normal mode without close application. If it is in normal mode close this activity */ if (mSlidesRecyclerView.getLayoutParams() != null && mSlidesRecyclerView.getLayoutParams().height == DEFAULT_RECYCLER_VIEW_HEIGHT) { if (isSingleSlideMode()) { finish(); } else { resetRecyclerViewHeight(CONTAINER_FULLVIEW_MODE_INVISIBLE); } } else { super.onBackPressed(); } } @Override public void onItemClick(int position) { /** * If seleted mediaModel is Vcard or Vcal open there respective actions * else adjust current view as full view. */ MediaModel mediaModel = mMediaWithTextModelList.get(position).getMediaModel(); if (mediaModel != null) { if (mediaModel.isVcard()) { Intent intent = new Intent(Intent.ACTION_VIEW); // we need open the saved part. intent.setDataAndType(mediaModel.getUri(), ContentType.TEXT_VCARD.toLowerCase()); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // distinguish view vcard from mms or contacts. intent.putExtra(VIEW_VCARD, true); Intent openInChooser = Intent.createChooser(intent, getString(R.string.action_header)); startActivity(openInChooser); } else if (mediaModel.isVcal()) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(EXTRA_SINGLE_ITEM, true); // So we don't see "surrounding" images in Gallery intent.putExtra(KEY_PACKAGE_NAME, PACKAGE_NAME); String contentType; contentType = mediaModel.getContentType(); intent.setDataAndType(mediaModel.getUri(), contentType); startActivity(intent); } else { resetRecyclerViewHeight(CONTAINER_FULLVIEW_MODE_VISIBLE); } } } @Override public void onLoadContainerView(int position) { super.onLoadContainerView(position); updateCurrentPosition(position); Log.v(TAG, "Attachment no fragment loaded till now onLoadContainerView"); changeDotsColor(position); } /** * Update mediaWithTextModel on mediaModel and text available * @param mediaModel * @param message */ @Override public void onDataUpdate(final MediaModel mediaModel, final String message) { runOnUiThread(new Runnable() { @Override public void run() { if(mActualNumberOfSlides == mNumberOfSlidesLoaded) { onDataInsert(mediaModel, message); } else { MediaWithTextModel mediaWithTextModel = mMediaWithTextModelList.get(mNumberOfSlidesLoaded); mediaWithTextModel.setMediaModel(mediaModel); mediaWithTextModel.setMessageText(message); mMediaWithTextModelList.set(mNumberOfSlidesLoaded, mediaWithTextModel); mSlidesAdapter.notifyItemChanged(mNumberOfSlidesLoaded); mNumberOfSlidesLoaded = mNumberOfSlidesLoaded + 1; } } }); } /** * Method for once data loads done this method will trigger. Handle on Main Thread */ @Override public void onDataLoadFinished() { Log.v(TAG, "Attachment onDataLoadFinished media list size is " + mMediaWithTextModelList.size()); removeEmptySlideObjects(); displayDotsLayout(); } @Override public void setInitialSlideSize(int size) { super.setInitialSlideSize(size); mMediaWithTextModelList = new ArrayList<MediaWithTextModel>(size); for(int i = 0; i < size; i ++) { mMediaWithTextModelList.add(new MediaWithTextModel(null,null)); } runOnUiThread(new Runnable() { @Override public void run() { initView(); } }); } @Override public void onDataInsert(final MediaModel mediaModel, final String message) { runOnUiThread(new Runnable() { @Override public void run() { Log.v(TAG, "Attachment onDataInsert"); MediaWithTextModel mediaWithTextModel = new MediaWithTextModel(message, mediaModel); mMediaWithTextModelList.add(mediaWithTextModel); mSlidesAdapter.notifyItemInserted(mMediaWithTextModelList.size() - 1); } }); } @Override public void onDataRemove(final int position) { mSlidesAdapter.notifyItemRemoved(position); } /** * sometimes slidemodel will return empty mediamodel list */ private void removeEmptySlideObjects() { //check for number of slides reached or not. If not reached then remove dummy viewholder from recyclerview runOnUiThread(new Runnable() { @Override public void run() { if(mActualNumberOfSlides > mNumberOfSlidesLoaded) { mMediaWithTextModelList.remove(mNumberOfSlidesLoaded); mNumberOfSlidesLoaded = mNumberOfSlidesLoaded + 1; onDataRemove(mMediaWithTextModelList.size()); removeEmptySlideObjects(); } } }); } public static class LinearLayoutManagerWrapper extends LinearLayoutManager { public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } @Override public boolean supportsPredictiveItemAnimations() { return false; } } /** * Calculate recycler view height using the device height * eg: if device height is 400 then recycleViewHeight = 400/4 * (Here 4 is portion of value). finally recyclerViewHeight = 100 */ private void calculateRecyclerViewHeight() { DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int height = displayMetrics.heightPixels; mRecyclerViewHeight = (int) (height / RECYCLER_VIEW_DEVICE_PORTION_ON_DEVICE_HEIGHT ); } public void resetRecyclerViewHeight(boolean isViewEntered) { Log.d(TAG, "base resetRecyclerViewHeight isEnter = " + isViewEntered); BaseViewFragment baseFragment = (BaseViewFragment) getFragmentManager() .findFragmentById(R.id.selected_attachment_container); if (isViewEntered) { resetRecyclerViewHeight(DEFAULT_RECYCLER_VIEW_HEIGHT); mDotsLayout.setVisibility(View.GONE); if (baseFragment != null) { baseFragment.onPlayMedia(); } } else { resetRecyclerViewHeight(mRecyclerViewHeight); mDotsLayout.setVisibility(View.VISIBLE); if (baseFragment != null) { baseFragment.onStopMedia(); } } mSlidesRecyclerView.requestLayout(); } private void resetRecyclerViewHeight(int height) { mSlidesRecyclerView.getLayoutParams().height = height; mSlidesRecyclerView.requestLayout(); } /** * When click play all menu item readjust view from full view to normal */ @Override public void onPlayAllMenuItemSelect() { if (mSlidesRecyclerView.getLayoutParams().height == DEFAULT_RECYCLER_VIEW_HEIGHT) { resetRecyclerViewHeight(CONTAINER_FULLVIEW_MODE_INVISIBLE); } } @Override public void playMediaInFullView() { resetRecyclerViewHeight(CONTAINER_FULLVIEW_MODE_VISIBLE); } @Override public boolean dispatchKeyEvent(KeyEvent event) { BaseViewFragment baseFragment = (BaseViewFragment) getFragmentManager() .findFragmentById(R.id.selected_attachment_container); if(baseFragment instanceof VideoViewFragment) { VideoViewFragment videoViewFragment = (VideoViewFragment) baseFragment; videoViewFragment.onKeyEvent(); } return super.dispatchKeyEvent(event); } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); invalidateOptionsMenu(); return true; } // [DNL-7080] 20210330 JunYiLin << @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_MENU: saveAllAttachment(); break; case KeyEvent.KEYCODE_MULTIFUNC_CENTER: playAllSlideShow(); break; case KeyEvent.KEYCODE_MULTIFUNC_RIGHT: saveAttachment(); break; } return super.onKeyUp(keyCode, event); } @Override protected void onResume() { super.onResume(); updateNavBar(this.getResources().getStringArray(R.array.setNavBar_highlight_attachments_view_in_slideshow)); } // [DNL-7080] >> },BaseAttachmentViewActivity.java:/* * Sonim Technologies, Inc. CONFIDENTIAL * Copyright (c) 2017 All Rights Reserved. * * The source code contained or described herein and all documents * related to the source code ("Material") is property of Sonim * Technologies, Inc. and its affiliates, may be covered by U.S. * or Foreign Patents or patents in process, and is protected by trade * secret or copyright law. No part of the Material may be used, * copied, reproduced, modified, published, uploaded, posted, * transmitted, distributed, or disclosed in any way without * prior written permission by Sonim Technologies, Inc. * * Unless required by applicable law or agreed to in writing, * the Materials are provided "AS IS," and WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. No license * under any patent, copyright, trade secret or other * intellectual property right is granted to or conferred upon you * by disclosure or delivery of the Material, either expressly, by * implication, inducement, estoppel or otherwise. Any license * under such intellectual property rights must be express and * approved by Sonim Technologies, Inc. in writing. * */ package com.android.mms.ui; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.ContentValues; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.provider.Telephony; import android.util.Log; import android.os.Environment; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; // << [DNL-795] 20210113 JunYiLin >> import android.view.KeyEvent; import android.webkit.MimeTypeMap; import android.widget.LinearLayout; import android.widget.Toast; import com.android.mms.MmsApp; import com.android.mms.R; import com.android.mms.drm.DrmUtils; import com.android.mms.model.MediaWithTextModel; import com.android.mms.model.MediaModel; import com.android.mms.model.SlideModel; import com.android.mms.model.SlideshowModel; import com.android.mms.model.SmilHelper; import com.android.mms.transaction.MessagingNotification; import com.android.mms.ui.multipleviews.AudioViewFragment; import com.android.mms.ui.multipleviews.BaseViewFragment; import com.android.mms.ui.multipleviews.IAttachmentSelection; import com.android.mms.ui.multipleviews.ImageViewFragment; import com.android.mms.ui.multipleviews.VcardVcalViewFragment; import com.android.mms.ui.multipleviews.VideoViewFragment; import com.android.mms.util.Utils; import com.google.android.mms.ContentType; import com.google.android.mms.MmsException; import com.google.android.mms.util.SqliteWrapper; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.List; public abstract class BaseAttachmentViewActivity extends Activity implements IAttachmentSelection { private static final String TAG = BaseAttachmentViewActivity.class.getSimpleName(); private static final int SINGLE_SLIDE_MODEL_SIZE = 1; private static final String NO_SPACE_ERROR = "ENOSPC"; private static boolean mNoSpace = false; public static final int MENU_PLAY = 1; public static final int MENU_PAUSE = 2; public static final int MENU_RESUME = 3; public static final int MENU_PLAY_ALL = 4; public static final int MENU_STOP = 5; public static final int MENU_SAVE_ATTACHMENT = 6; public static final int MENU_SAVE_ALL_ATTACHMENTS = 7; public LinearLayout mSelectedAttachmentContainer; public List<MediaWithTextModel> mMediaWithTextModelList; private boolean mSingleSlideMode = false; private boolean mPlayAllMode = false; private boolean mEditSlideShowMode = false; private int mCurrentMediaPosition = 0; public int mActualNumberOfSlides, mNumberOfSlidesLoaded; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } private void markAsReadIfNeed() { Intent intent = getIntent(); boolean unread = intent.getBooleanExtra("unread", false); Uri uri = intent.getData(); if (unread) { ContentValues values = new ContentValues(2); values.put(Telephony.Mms.SEEN, MessageUtils.MESSAGE_SEEN); values.put(Telephony.Mms.READ, MessageUtils.MESSAGE_READ); SqliteWrapper.update(this, getContentResolver(), uri, values, null, null); MessagingNotification.blockingUpdateNewMessageIndicator( this, MessagingNotification.THREAD_NONE, false); } } @Override public void onLoadContainerView(int position) { MediaWithTextModel mediaWithTextModel = mMediaWithTextModelList.get(position); overrideContainer(mediaWithTextModel); } @Override public void onLoadContainerView(MediaWithTextModel customMediaModel) { overrideContainer(customMediaModel); } // When switching between the fragments update mCurrentMediaPosition // with selected media position public void updateCurrentPosition(int currentMediaPosition) { mCurrentMediaPosition = currentMediaPosition; } private void overrideContainer(MediaWithTextModel mediaWithTextModel) { if (!isFinishing() && !isDestroyed()) { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); MediaModel mediaModel = mediaWithTextModel.getMediaModel(); switch (mediaModel.getTag()) { case SmilHelper.ELEMENT_TAG_TEXT: fragmentTransaction.replace(R.id.selected_attachment_container, ImageViewFragment.newInstance(mediaWithTextModel), null); break; case SmilHelper.ELEMENT_TAG_IMAGE: fragmentTransaction.replace(R.id.selected_attachment_container, ImageViewFragment.newInstance(mediaWithTextModel), null); break; case SmilHelper.ELEMENT_TAG_AUDIO: fragmentTransaction.replace(R.id.selected_attachment_container, AudioViewFragment.newInstance(mediaWithTextModel), null); break; case SmilHelper.ELEMENT_TAG_VIDEO: fragmentTransaction.replace(R.id.selected_attachment_container, VideoViewFragment.newInstance(mediaWithTextModel), null); break; case SmilHelper.ELEMENT_TAG_REF: if (mediaModel.getContentType().toLowerCase().equals(ContentType.TEXT_VCARD. toLowerCase()) || mediaModel.getContentType().toLowerCase(). equals(ContentType.TEXT_VCALENDAR.toLowerCase())) { fragmentTransaction.replace(R.id.selected_attachment_container, VcardVcalViewFragment.newInstance(mediaWithTextModel), null); } break; default: Log.v(TAG,"default attachment type case"); } fragmentTransaction.commit(); } } public boolean isSingleSlideMode() { return mSingleSlideMode; } public void setSingleSlideMode(boolean mSingleSlideMode) { this.mSingleSlideMode = mSingleSlideMode; } static class GetDataAsyncTask extends AsyncTask<Void, Void, Void> { private WeakReference<BaseAttachmentViewActivity> attachmentViewActivityWeakReference; public GetDataAsyncTask(BaseAttachmentViewActivity baseAttachmentViewActivity) { attachmentViewActivityWeakReference = new WeakReference(baseAttachmentViewActivity); } @Override protected Void doInBackground(Void... voids) { Log.v(TAG, "GetDataAsyncTask doInBackground"); BaseAttachmentViewActivity activity = attachmentViewActivityWeakReference.get(); if (activity != null) { try { Intent intent = activity.getIntent(); Uri uri = intent.getData(); SlideshowModel.createFromMessageUri(activity, uri); } catch (MmsException e) { Log.e(TAG, "Cannot present the slide show.", e); } activity.markAsReadIfNeed(); } else { Log.v(TAG, "GetDataAsyncTask Activity is null "); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); BaseAttachmentViewActivity activity = attachmentViewActivityWeakReference.get(); if (activity != null) { activity.onDataLoadFinished(); } } } /** * Update mediaWithTextModelList on slideModel available * @param slideModel * @param currentSlide */ @Override public void onDataUpdate(SlideModel slideModel, int currentSlide) { String message = slideModel.getText() != null ? slideModel.getText().getText() : null; if (slideModel.size() == SINGLE_SLIDE_MODEL_SIZE && slideModel.get(0).isText()) { if (mActualNumberOfSlides > mNumberOfSlidesLoaded) { onDataUpdate(slideModel.get(0), message); } else { onDataInsert(slideModel.get(0), message); } } else { for (int j = 0; j < slideModel.size(); j++) { MediaModel mediaModel = slideModel.get(j); Log.d(TAG, "Attachment prepareSlideData MediaModel contentType = " + mediaModel.getContentType()); if (mediaModel != null && !mediaModel.isText()) { Log.d(TAG, "Attachment prepareSlideData adding MediaWithTextModel "); if (mActualNumberOfSlides > mNumberOfSlidesLoaded) { onDataUpdate(mediaModel, message); } else { onDataInsert(mediaModel, message); } } } } } @Override public void setInitialSlideSize(int size) { Log.v(TAG, "Attachment setSlideShowSize size " + size); mActualNumberOfSlides = size; } public IAttachmentSelection getIAttachmentSelection() { return this; } public boolean isPlayAllMode() { return mPlayAllMode; } public void setPlayAllMode(boolean mPlayAllMode) { this.mPlayAllMode = mPlayAllMode; } @Override public void onPlayNextSlide() { } //[DNL-795] 20210220 JunYiLin << /* mark original @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); BaseViewFragment baseFragment = (BaseViewFragment) getFragmentManager(). findFragmentById(R.id.selected_attachment_container); if(baseFragment != null) { baseFragment.onMenuUpdate(menu); } if(mMediaWithTextModelList != null && mMediaWithTextModelList.size() > 1) { menu.add(0, MENU_PLAY_ALL, 0,R.string.play_all); } menu.add(0, MENU_SAVE_ATTACHMENT, 0,R.string.save_attachment); if(mMediaWithTextModelList != null && mMediaWithTextModelList.size() > 1) { menu.add(0, MENU_SAVE_ALL_ATTACHMENTS, 0, R.string.save_all_attachments); } return true; } */ // [DNL-795] >> @Override public boolean onOptionsItemSelected(MenuItem item) { BaseViewFragment baseFragment = null; switch (item.getItemId()) { case MENU_PLAY_ALL: onPlayAllMenuItemSelect(); Intent intent = new Intent(this, AttachmentPlayAllActivity.class); intent.setData(getIntent().getData()); startActivity(intent); break; case MENU_SAVE_ATTACHMENT: saveAttachment(); break; case MENU_SAVE_ALL_ATTACHMENTS: saveAllAttachment(); break; case MENU_PLAY: playMediaInFullView(); baseFragment = (BaseViewFragment) getFragmentManager() .findFragmentById(R.id.selected_attachment_container); baseFragment.onPlayMedia(); break; case MENU_PAUSE: baseFragment = (BaseViewFragment) getFragmentManager() .findFragmentById(R.id.selected_attachment_container); baseFragment.onPauseMedia(); break; case MENU_RESUME: baseFragment = (BaseViewFragment) getFragmentManager() .findFragmentById(R.id.selected_attachment_container); baseFragment.onResumeMedia(); break; default: return super.onOptionsItemSelected(item); } return true; } // [DNL-7080] 20210330 JunYiLin << public void saveAllAttachment() { /* mark original private void saveAllAttachment(){ */ // [DNL-7080] int resId; boolean isCopied = copyMedia(); if(isCopied){ resId = R.string.save_all_attachment_success; }else{ resId = mNoSpace ? R.string.space_low_message : R.string.save_attachment_fail; } Toast.makeText(BaseAttachmentViewActivity.this, resId, Toast.LENGTH_SHORT).show(); } // [DNL-795] 20210113 JunYiLin << public void saveAttachment() { /* mark original private void saveAttachment(){ */ // [DNL-795] >> int resId; boolean isCopied = copyPart(mMediaWithTextModelList.get(mCurrentMediaPosition)); if(isCopied){ resId = R.string.save_attachment_success; }else{ resId = mNoSpace ? R.string.space_low_message : R.string.save_attachment_fail; } Toast.makeText(BaseAttachmentViewActivity.this, resId, Toast.LENGTH_SHORT).show(); } /** * Copies media from an Mms to the "download" directory on the SD card. If any of the parts * are audio types, drm'd or not, they're copied to the "Ringtones" directory. */ private boolean copyMedia() { boolean result = true; if (mMediaWithTextModelList == null) { return false; } for(int i = 0; i < mMediaWithTextModelList.size(); i++) { // all parts have to be successful for a valid result. result &= copyPart(mMediaWithTextModelList.get(i)); } return result; } private boolean copyPart(MediaWithTextModel customMediaModel) { // [DNL-8241] 20210423 JunYiLin << if (MessageUtils.checkIsPhoneMemoryFull(BaseAttachmentViewActivity.this)) { mNoSpace = true; return false; } // [DNL-8241] >> mNoSpace = false; MediaModel mediaModel = customMediaModel.getMediaModel(); Uri uri = mediaModel.getUri(); String type = new String(mediaModel.getContentType()); boolean isDrm = DrmUtils.isDrmType(type); if (isDrm) { type = MmsApp.getApplication().getDrmManagerClient() .getOriginalMimeType(uri); } if (!ContentType.isImageType(type) && !ContentType.isVideoType(type) && !ContentType.isAudioType(type) && !(ContentType.TEXT_VCARD.toLowerCase().equals(type.toLowerCase())) && !(ContentType.TEXT_VCALENDAR.toLowerCase().equals(type.toLowerCase())) && !(ContentType.AUDIO_OGG.toLowerCase().equals(type.toLowerCase()))) { return true; // we only save pictures, videos, and sounds. Skip the text parts, // the app (smil) parts, and other type that we can't handle. // Return true to pretend that we successfully saved the part so // the whole save process will be counted a success. } InputStream input = null; FileOutputStream fout = null; try { input = getContentResolver().openInputStream(uri); if (input instanceof FileInputStream) { FileInputStream fin = (FileInputStream) input; String fileName = mediaModel.getSrc(); File originalFile = new File(fileName); fileName = originalFile.getName(); // Strip the full path of where the "part" is // stored down to just the leaf filename. // For every media the default saving directory should be 'Download'. String dir = Utils.getStoragePath(this) + "/" + Environment.DIRECTORY_DOWNLOADS + "/"; String extension; int index; if ((index = fileName.lastIndexOf('.')) == -1) { extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type); } else { extension = fileName.substring(index + 1, fileName.length()); fileName = fileName.substring(0, index); } if (isDrm) { extension += DrmUtils.getConvertExtension(type); } // Remove leading periods. The gallery ignores files starting with a period. if (!TextUtils.isEmpty(fileName)) { fileName = fileName.trim(); fileName = fileName.replaceAll("^\\.", ""); } File file = getUniqueDestination(dir + fileName, extension); // make sure the path is valid and directories created for this file. File parentFile = file.getParentFile(); if (!parentFile.exists() && !parentFile.mkdirs()) { Log.e(TAG, "[MMS] copyPart: mkdirs for " + parentFile.getPath() + " failed!"); return false; } fout = new FileOutputStream(file); byte[] buffer = new byte[8000]; int size = 0; while ((size=fin.read(buffer)) != -1) { fout.write(buffer, 0, size); } // Notify other applications listening to scanner events // that a media file has been added to the sd card sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); } } catch (IOException e) { // Ignore Log.e(TAG, "IOException caught while opening or reading stream", e); if(e.getMessage().contains(NO_SPACE_ERROR)){ mNoSpace = true; } return false; } finally { if (null != input) { try { input.close(); } catch (IOException e) { // Ignore Log.e(TAG, "IOException caught while closing stream", e); return false; } } if (null != fout) { try { fout.close(); } catch (IOException e) { // Ignore Log.e(TAG, "IOException caught while closing stream", e); return false; } } } return true; } private File getUniqueDestination(String base, String extension) { File file = new File(base + "." + extension); for (int i = 2; file.exists(); i++) { file = new File(base + "_" + i + "." + extension); } return file; } @Override public void onPlayAllMenuItemSelect() { } @Override public void playMediaInFullView() {} public boolean isEditSlideShowMode() { return mEditSlideShowMode; } public void setEditSlideShowMode(boolean editSlideShowMode) { this.mEditSlideShowMode = editSlideShowMode; } // << [DNL-7080] 20210330 JunYiLin << public void updateNavBar(String[] str) { if (str.length > 0) { MessageUtils.setNavigationBarName(this, str); } } public void playAllSlideShow() { Intent intent = new Intent(this, AttachmentPlayAllActivity.class); intent.setData(getIntent().getData()); startActivity(intent); } // [DNL-7080] >> // [DNL-7826] 20210422 JunYiLin << public final SlidesAdapter.OnDataSetChangedListener mDataSetChangedListener = new SlidesAdapter.OnDataSetChangedListener() { @Override public void onHighLightItem(int position) { playAudioMedia(position); } }; public void playAudioMedia(int position) { MediaModel mediaModel = mMediaWithTextModelList.get(position).getMediaModel(); if (mediaModel != null && mediaModel.isAudio()) { BaseViewFragment baseFragment = (BaseViewFragment) getFragmentManager().findFragmentById(R.id.selected_attachment_container); if (baseFragment != null) { baseFragment.onPlayMedia(); } } } // [DNL-7826] >> }以上报错,怎么在上述代码上具体修改
最新发布
08-12
<think>我们正在处理一个Android开发中的NullPointerException问题。根据用户描述,问题发生在Android 15上的AttachmentViewActivity的onDataLoadFinished方法中,具体是调用了null对象的List.size()方法。 问题分析: 在Java中,当我们尝试在一个为null的对象上调用方法时,就会抛出NullPointerException。在这个案例中,我们试图调用一个List对象的size()方法,但该List对象为null。 解决步骤: 1. 定位问题:首先需要确定为什么这个List对象为null。可能是数据加载过程中出现了错误,或者初始化未完成,或者异步加载数据时出现了异常导致没有正确赋值。 2. 检查数据加载逻辑:查看onDataLoadFinished方法被调用的上下文。通常,这个方法是在数据加载完成后被调用的回调。需要确保在数据加载成功时,传递进来的List不为null;如果加载失败,应该有相应的处理。 3. 防御性编程:在调用List.size()之前,先检查List是否为null。 4. 修复方案:在调用List.size()之前添加null检查,并处理null的情况(例如,显示空视图或错误信息)。 示例代码修复: 原始可能有问题的代码: ```java public void onDataLoadFinished(List<Attachment> attachments) { // 直接使用attachments.size(),如果attachments为null就会抛出NullPointerException int count = attachments.size(); // ... 其他代码 } ``` 修复后的代码: ```java public void onDataLoadFinished(List<Attachment> attachments) { if (attachments == null) { // 处理null的情况,例如设置一个空列表或者显示错误 attachments = Collections.emptyList(); // 或者记录错误日志,并返回 // Log.e(TAG, "attachments is null"); // return; } int count = attachments.size(); // ... 其他代码 } ``` 或者,如果数据加载可能失败,我们可以在回调中传递一个表示成功或失败的状态,以及数据。这样我们可以更清晰地处理错误。 5. 预防措施:在代码的其他地方,确保在数据加载过程中正确地初始化列表。例如,在开始加载数据时,可以先将列表初始化为空列表,这样即使加载失败,也不会是null。 6. 检查异步加载:如果数据是异步加载的,确保在加载完成后正确地更新UI线程,并且正确处理异常情况。 7. 使用注解:在Android开发中,可以使用@Nullable和@NonNull注解来标记参数,这样编译器会给出警告,帮助我们避免潜在的NullPointerException。 例如: ```java public void onDataLoadFinished(@NonNull List<Attachment> attachments) { // 使用@NonNull注解后,如果调用者传入null,编译时会有警告(但运行时仍可能为null,所以最好结合防御性编程) int count = attachments.size(); // ... } ``` 但是请注意,注解并不能在运行时阻止null的传入,所以仍然需要结合null检查。 8. 使用Java 8的Optional(如果支持的话): 如果项目支持Java 8,可以考虑使用Optional来包装可能为null的列表,但这种方式在Android中并不常用,因为可能会增加复杂性。 根据用户提供的引用,我们注意到引用[1][2][3]都是关于NullPointerException的,它们分别发生在不同的方法调用上。这些引用提醒我们,在Android开发中,NullPointerException是一个常见的错误,通常是由于没有对可能为null的对象进行检查。 特别是引用[3]中提到了一个类似的情况:在调用String.equals()方法时,字符串对象为null。这与用户的问题非常相似,都是调用了null对象的方法。 因此,我们的解决方案就是在调用List.size()之前,先检查列表是否为null。 用户的问题是在Android 15上出现的,虽然Android版本不同,但NullPointerException的处理方式是一样的。不过,需要注意的是,在较新的Android版本中,系统可能对某些API的行为有所改变,因此需要检查在Android 15中,导致数据加载返回null的原因是否与系统行为变更有关。查看Android 15的变更文档,看是否有相关API的变更导致返回null的可能性增加。 但是,由于用户没有提供更多的上下文,我们无法确定为什么在Android 15上会出现这个问题(而之前的版本没有)。因此,最直接的解决方案就是添加null检查。 相关问题: 1. 在Android开发中,如何避免NullPointerException? 2. 在回调方法中,如何处理可能为null的参数? 3. Android 15有哪些变更可能导致NullPointerException的增加? 4. 如何调试Android中的NullPointerException? 5. 使用Kotlin开发Android应用如何避免NullPointerException?(因为Kotlin有可空类型和非空类型的区分) 由于用户的问题是使用Java编写的,所以我们主要关注Java的解决方案。 总结回答: 用户的问题是在AttachmentViewActivity的onDataLoadFinished方法中,对一个可能为null的List调用了size()方法。解决方案是在使用前检查List是否为null,并处理null的情况(例如使用空列表替代)。同时,需要检查数据加载的代码,确保在数据加载失败时不会传递null,而是传递一个空列表或者错误状态。 代码示例: ```java public void onDataLoadFinished(List<Attachment> attachments) { // 处理可能的null if (attachments == null) { attachments = new ArrayList<>(); // 或者 Collections.emptyList() 如果后续不修改 } // 现在可以安全使用 int count = attachments.size(); // ... 其他逻辑 } ``` 注意:如果后续代码会修改这个列表(比如添加或删除元素),那么就不能使用Collections.emptyList(),因为它是不可变的。此时应该创建一个新的空列表(如ArrayList)。 另外,如果这个null情况是异常情况,应该记录日志以便后续排查原因。 最后,建议检查数据加载的整个流程,确保在数据加载失败时也能正确设置列表(而不是null),这样可以从根本上避免这个问题。</think>### 解决 Android 15 中的 NullPointerException:调用 List.size() 时对象为 null 在 Android 15 中遇到 `NullPointerException`,特别是在 `AttachmentViewActivity.onDataLoadFinished()` 方法中调用 `List.size()` 时出现对象为 null 的错误,通常是由于数据加载逻辑未正确处理空值或异步加载时序问题导致的。以下是完整解决方案: #### 1. **根本原因分析** - 当 `List` 对象未被正确初始化时(如数据加载失败或异步回调时序问题),调用 `list.size()` 会抛出: ```java NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference ``` - 在 Android 15 中,后台限制更严格,异步任务(如网络请求/数据库查询)更容易因生命周期问题返回 null 数据[^1][^2]。 #### 2. **修复方案** 在 `AttachmentViewActivity` 中添加防御性检查: ```java // 修改后的 onDataLoadFinished 方法 public void onDataLoadFinished(List<Attachment> attachments) { // 关键修复:添加 null 检查 if (attachments == null) { Log.e(TAG, "Loaded data is null"); // 记录错误 attachments = Collections.emptyList(); // 赋值为空列表 } int itemCount = attachments.size(); // 安全调用 updateUI(itemCount); // 更新UI } ``` #### 3. **预防措施** - **初始化默认值**:在声明变量时初始化空列表: ```java private List<Attachment> mAttachments = new ArrayList<>(); // 避免 null ``` - **异步加载保护**:检查 Activity 状态后再更新 UI: ```java if (!isFinishing() && !isDestroyed()) { // Android 15 需显式检查 updateUI(attachments); } ``` - **使用注解**:通过 `@Nullable` 和 `@NonNull` 标记参数: ```java public void onDataLoadFinished(@Nullable List<Attachment> attachments) { if (attachments == null) return; // 编译器会提示检查 } ``` #### 4. **Android 15 适配建议** - **后台限制**:确保数据加载在 `onStart()`/`onResume()` 中触发,而非 `onCreate()`(Activity 可能被后台回收)[^4]。 - **ViewModel 保存状态**:使用 `ViewModel` 缓存数据,避免因配置变更导致数据丢失: ```java public class AttachmentViewModel extends ViewModel { private MutableLiveData<List<Attachment>> attachments = new MutableLiveData<>(); public void loadData() { // 数据加载逻辑 attachments.postValue(loadedData); } } ``` #### 5. **调试建议** - 在 `onDataLoadFinished()` 调用处添加日志: ```java Log.d(TAG, "Data source: " + dataSource); // 检查数据源是否有效 ``` - 启用 `StrictMode` 检测主线程耗时操作: ```java StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectNetwork() .penaltyLog() .build()); ``` > **关键点**:Android 15 加强了后台限制,**所有异步回调必须显式检查数据 null 状态和 Activity 生命周期**,否则易触发 `NullPointerException`[^2][^4]。 --- ### 相关问题 1. Android 15 中如何处理后台服务的数据回调以避免 NullPointerException? 2. ViewModel 和 LiveData 如何帮助解决异步加载导致的空指针问题? 3. 除了空列表检查,还有哪些方法可以预防 Java 中的 NullPointerException? 4. Android 15 对后台进程有哪些新限制?这些限制如何影响数据加载逻辑? 5. 如何用 Kotlin 的空安全特性(如 `?` 和 `!!`)避免此类问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值