整理下维护别人项目遇到的Bug或者错误写法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zh_qianwei/article/details/68944996
  • Fragment already added
    异常 java.lang.IllegalStateException: Fragment already added
    修改前代码如下
 FragmentTransaction trx = getSupportFragmentManager().beginTransaction();
        for (int i = 0; i < fragments.length; i++) {
            if (i != index) {
                if (fragments[i].isAdded()) {
                    trx.hide(fragments[i]);
                }
            } else {
                if (!fragments[i].isAdded() ) {
                    trx.add(R.id.fragment_container, fragments[i], fragmentTags[i]);
                }
                trx.show(fragments[i]);
            }
        }
  trx.commit();

代码中在add之前已经进行了isAdded()判断,但是依然出现这个错误。推断add以后fragment不是立即改变的,如果联系快速切换上一次的状态没有改变再次添加就会异常。所以网上另外一种解决办法添加tag也是不行的。
官方注释

Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.

修改后代码

 FragmentTransaction trx = getSupportFragmentManager().beginTransaction();
        for (int i = 0; i < fragments.length; i++) {
            if (i != index) {
                if (fragments[i].isAdded()) {
                    trx.hide(fragments[i]);
                }
            } else {
                if (!fragments[i].isAdded() ) {
                    trx.add(R.id.fragment_container, fragments[i], fragmentTags[i]);
                }
                trx.show(fragments[i]);
            }
        }
   trx.commitAllowingStateLoss();
   getSupportFragmentManager().executePendingTransactions();
  • NotificationManager

异常现象:在receiver中收到第三方推送的消息,点击系统notice,有一款锤子的坚果手机没有反应
下面是原来的实现的主要代码

   //获取到消息的处理,在Notice里面放一个广播
    Intent intent= new Intent(context, NotificationReceiver.class);
    intent.putExtra("push", pushBean);
    PendingIntent pendingIntent = PendingIntent.
        getBroadcast(context, pushCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
   //NotificationReceiver 里面代码
    Intent mainIntent = new Intent(context, MainActivity.class);
    mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    Bundle bundle = new Bundle();
    bundle.putInt("action", pushBean.action);
    bundle.putString("actionParam", pushBean.param);
    mainIntent.putExtra(PUSH_EXTRA_BUNDLE, bundle);
    context.startActivity(mainIntent);

修改后代码,可以正常点击的代码

    //注意 部分手机需要跳转到一个中间Activity,然后在跳转到MainActivity,这里没做处理
    Intent intent= new Intent(context, MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP);
    Bundle bundle = new Bundle();
    bundle.putInt("action", pushBean.action);
    bundle.putString("actionParam", pushBean.param);
    intent.putExtra("push_extra_bundle", bundle);
    PendingIntent pendingIntent = PendingIntent.
        getActivity(context, pushCode, intent,PendingIntent.FLAG_UPDATE_CURRENT);
  • 文件扫描根目录问题

在扫描本地文件时,按back键时拦截系统事件并返回上级菜单,但是在根目录时未作处理就会出现操作异常

  public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
      backParentView();
      return true;
    }
    return super.onKeyDown(keyCode, event);
  }
  backParentView主要代码
  File f = new File(_currentPath);
 String parentPath = f.getParent();

下面是修改后的代码

 if (_currentPath.equalsIgnoreCase(FileUtil.getSDPath())) {
     scrollToFinishActivity();//如果是根目录直接返回
    } else {
    ...//以前的逻辑
  }
  • 启动service错误

启动Service

//错误日志 部分截图 大概意识就是启动service是异常,造成程序Crash
Caused by java.lang.SecurityException: Unable to start service Intent { cmp=packname/.service.OneService }: 

这个原因也是奇葩,从后台数据开都是OPPO手机,后再他们官网论坛找到了类似问题
修改后代码如下
官方回复 http://bbs.coloros.com/thread-174655-1-1.html

>//处理OPPO a53m, r7, a33M手机在锁屏后恢复是 打开service奔溃问题
> try{
      startService(new Intent(this, MyService.class));
    }catch (RuntimeException exception){
 }
  • 读取用户权限异常

在后台统计中有一个读取设备信息的异常,需要用到android.permission.READ_PHONE_STATE
这个主要是在Android6.0+读取系统权限时没有申请直接使用的原因,但是在代码中程序第一次启动就有相应处理。进一步分析发现,在以后需要这设备信息时都是直接读取系统信息,如果用户把该权限的授权关闭就会出现异常
这里修改设置了一个缓存,主要逻辑如下

  • 第一次读取System信息时,创建一个cache存储这个值,并把数据存储在文件系统内。
  • 以后再用到这些值是先判断cache是否存在,如果不存在就去读取自己记录的文件,并同时把值保持在cache内。这样无论用户权限怎么变化,只要第一次允许,以后就不会对自己程序造成任何影响
  • 这里加了一层文件存储,同时解决了Activity、Application、Static object。被系统回收以后变量被重置问题。关于这部分内容不是本文重点,这里不再详细描述了。
  • AlertDialog异常

    在修改代码中发现项目中大量用到AlertDialog,而且有2个异常第一个会在日志体现,这个异常会直接造成奔溃,下面的异常截取

    Fatal Exception: android.view.WindowManager$BadTokenException:
    

    主要原因是在Activity不在running状态进行了Dialog相关操作
    第一二个异常是在Activity回收前调用了Dialog.dismiss,当Activity再次回复的时候会造成后续的交互逻辑无法继续进行

修改方案
使用DialogFragment替换AlertDialog,DialogFragment好处大家自己动动手吧
下面是封装的带有简单的loadingDialogFragment,支持显示内容动态更新、用户back事件监听、自定义Tag标记、onSaveInstanceState逻辑实现。有按钮和选择的实现等整理完毕再上传。
这里需要注意一下如果在Fragment里面弹出DialogFragment,那么接口回调会在对应的Activity里面

下面这段可以写在BaseActivity里面,必须继承LoadingDialogFragment.LoadingDialogInteractionListener接口
/**
 * 显示loading对话框
 * @param message 提示内容
 */
 public void showLoadingDialog(String message,boolean isCancelable) {
     this.showLoadingDialog(message,null,isCancelable);
 }
 /**
   * 显示loading对话框
   * @param message 提示内容
   * @param userTag 用户自定义Tag 用于在一个窗口多种同类型提示区分
   */
 public void showLoadingDialog(String message,String userTag,boolean isCancelable) {
     LoadingDialogFragment dialogFragment = (LoadingDialogFragment) getSupportFragmentManager().findFragmentByTag(Constant.DIALOG_LOADING);
     if(dialogFragment == null)
     {
        dialogFragment = LoadingDialogFragment.newInstance(message,userTag,isCancelable);
        
       dialogFragment.show(getSupportFragmentManager(), Constant.DIALOG_LOADING);
     }else{
        dialogFragment.updateInfo(message,userTag,isCancelable);
     }
}


    /**
     *  隐藏提示
     */
    public void pdDismisLoadingDialog() {
        DialogFragment dialogFragment = (LoadingDialogFragment) getSupportFragmentManager().findFragmentByTag(Constant.DIALOG_LOADING);
        if (dialogFragment != null){
            dialogFragment.dismiss();
        }
    }
    
     /**
     *  用户返回取消事件  
     * @param tag
     */
    public void cancelEvent(String  tag) {
    }
    
 子类的使用代码如下
 showLoadingDialog("正在登录...",true);
 
 如果需要用户取消事件可以重载cancelEvent方法

下面是LoadingDialogFragment 类

/**
 *  注意:注释部分是另外一种实现方式,二者不要一起用
 * 简单提示对话框没有交互,默认有一个progresspar和一个可以设置的TextView
 */
public class LoadingDialogFragment extends DialogFragment {

    private static final String ARG_INFO = "info";
    private static final String ARG_USERTAG = "usertag";
    private static final String ARG_CANCEL= "cancel";
    private String info;
    private String userTag;
    private boolean isCancelable;
    private TextView infoTV;
    LoadingDialogInteractionListener mListener;
    public static LoadingDialogFragment newInstance(String param,String userTag) {
        LoadingDialogFragment fragment = new LoadingDialogFragment();
        Bundle args = new Bundle();
        args.putString(ARG_INFO, param);
        args.putString(ARG_USERTAG, userTag);
        fragment.setArguments(args);
        return fragment;
    }
    public static LoadingDialogFragment newInstance(String param,String userTag,boolean isCancelable) {
        LoadingDialogFragment fragment = new LoadingDialogFragment();
        Bundle args = new Bundle();
        args.putString(ARG_INFO, param);
        args.putString(ARG_USERTAG, userTag);
        args.putBoolean(ARG_CANCEL, isCancelable);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            info = getArguments().getString(ARG_INFO);
            userTag = getArguments().getString(ARG_USERTAG);
            isCancelable = getArguments().getBoolean(ARG_CANCEL,false);
        }else
        {
            info = savedInstanceState.getString(ARG_INFO);
            userTag = savedInstanceState.getString(ARG_USERTAG);
            isCancelable = savedInstanceState.getBoolean(ARG_CANCEL,false);
        }
    }
//    public Dialog onCreateDialog(Bundle savedInstanceState) {
//        View contentView = getActivity().getLayoutInflater().inflate(R.layout.view_tips_loading, null);
//        contentView.setVisibility(View.VISIBLE);
//        infoTV = ViewHolderUtils.getViewHolderView(contentView,R.id.tips_loading_msg);
//        infoTV.setText(info);
//        AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
////        b.setCancelable(isCancel);
//        b.setView(contentView);
////        b.setCanceledOnTouchOutside(false);
//
//        return b.create();
//    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        getDialog().setCancelable(isCancelable);
        getDialog().setCanceledOnTouchOutside(false);
        View v = inflater.inflate(R.layout.view_tips_loading, container, false);
        infoTV = (TextView)v.findViewById(R.id.tips_loading_msg);
        infoTV.setText(info);
        return v;
    }
    public void updateInfo(String info,String tag,boolean isCancelable){
        this.info = info;
        infoTV.setText(info);
        this.userTag = tag;
        this.isCancelable = isCancelable;
        getDialog().setCancelable(isCancelable);
    }
    public void updateInfo(String info,String tag) {
       this.updateInfo(info,tag,isCancelable);
    }
    public void updateInfo(String info) {
        this.updateInfo(info,userTag,isCancelable);
    }

    public void updateInfo(int infoResId) {
        this.info = getString(infoResId);
        infoTV.setText(infoResId);
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putString(ARG_INFO, this.info);
        outState.putString(ARG_USERTAG, this.userTag);
        outState.putBoolean(ARG_CANCEL, this.isCancelable);
        super.onSaveInstanceState(outState);
    }

    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);

    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);

    }
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof LoadingDialogFragment.LoadingDialogInteractionListener) {
            mListener = (LoadingDialogFragment.LoadingDialogInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnListFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
    public interface LoadingDialogInteractionListener {
        void cancelEvent(String userTag);
    }
}
  • SharedPreferences
    下面是存储到本地的数据内容
<string name="name">张三</string>
<int name="age" value="20">

下面是出错代码
sharedPreferences.getString(“age”, defaultValue);
有些机型或正确返回20
但是有些会提示类型不匹配,直接出错。所以这个地方一定要按照存储的格式类读取

  • Service内使用Retrofit
    在Activity中一般代码如下
 movieService.getTopMovie(start, count)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())

但是在Service中这种写法有可能造成ANR,发现这个问题来源一个不大好的需求
因为以前项目中没有IM,但是新的需求一定时间更新一下数据,这边不希望用第三方推送
所以就只能本地轮询
我的实现是在Service里面启动定时服务,然后调用获取数据的接口。为了方便调试我把时间设置在100ms一次。结果发生了预想不到的问题。
在没启动Service准确说没有发生请求之前 ,以前都是正常的。一单service调用了一次请求界面就会卡顿,然后一会就会报各种receiver异常。经过一系列排查最终确定是调用接口引起的(注意这个接口在Activity是正常的),推测是因为在subscribeOn指定在io操作内执行,但是Service不是io操作,在事件执行完毕没法实现相应的锁定,造成ANR,并不是receiver的异常
后经测试最终代码如下

 movieService.getTopMovie(start, count)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
  • Dialog内Edittext动态修改高度问题
    输入框的一些需求实现
    这个问题源于上面的需求,开始用DialogFragment来实现,调试的时候发现在弹出键盘的时候系统会调整dialog窗体,造成设置的值不准确。同时测试发现用activity的dialog同样存在类似问题。
  • ** Settings.System设置SCREEN_OFF_TIMEOUT**
    错误类型java.lang.IllegalArgumentException: Invalid value: -69312512 for setting: screen_off_timeout
    这是一个十分低价的错误,但是又和机型相关
    代码如下
                           screenOffTime * 60 * 1000); 

以前逻辑中某种设置把这个值设置的比较大,在601000直接造成int越界,而且设置以后还不这个值存储了,再次启动还要调这个方法,造成奔溃。但是这个错误只有在部分机型才会这样,有些机型不会。

<p>All requests are handled on a single worker thread -- they may take as
 * long as necessary (and will not block the application's main loop), but
 * only one request will be processed at a time.

一般这个就是来解决ANR的替代方案。
在代码优化在我们这边同事直接把以前的service替换成了IntentService
把以前的业务直接移到onHandleIntent 方法里面

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) return;
        new Thread(new Runnable() {

在测试版本偶尔会发送这个页面卡顿,当时查找了好多逻辑,找到上面Service内使用Retrofit 异常
,直接把main里面请求放在了service。修改完毕以后卡主的概率降低了好多,但是有一款机器还是报这个问题
后来查到资料
Intent Service 会不会ANR
并不满足我们的情况

继续查找原因发现异步请求以后会调用SharedPreferences,这个也会导致ANR
SharedPreferences造成ANR
系统在commit里面有这样的注释

 * Finishes or waits for async operations to complete.
 * (e.g. SharedPreferences$Editor#startCommit writes)
 *
 * Is called from the Activity base class's onPause(), after
 * BroadcastReceiver's onReceive, after Service command handling,
 * etc. (so async work is never lost)

我们代码用到了service的contenxt,和上面像。修改使用Application

修改以后问题并没有改善那款机器还是会出问题
后来找到https://stackoverflow.com/questions/17356584/anr-for-my-intentservice

发现他的代码和我们的很像,我们是直接改以前的service,所以没有修改newThread,就造成了在onHandleIntent里面新启动了线程
super.onBackPressed();
错误日志如下

Fatal Exception: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
       at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1533)
       at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:603)
       at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:179)
       at com.zhulang.reader.ui.read.ReadPageActivity.onBackPressed(ReadPageActivity.java:3536)
       at android.app.Activity.onKeyUp(Activity.java:2494)
       at android.view.KeyEvent.dispatch(KeyEvent.java:2667)
       at android.app.Activity.dispatchKeyEvent(Activity.java:2751)
       at com.android.internal.policy.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:2310)
      .......

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
这个错误多数发送在fragment onSava对fragment进行操作,但是这里代码定位只有一句
super.onBackPressed();并没有操作fragment
支持查看源码异常来源
因为我们activity继承自FragmentActivity,查到相应代码

     */
    @Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

继续查看popBackStackImmediate,只是一个接口,我们查看具体的实现代码如下

   @Override
    public boolean popBackStackImmediate() {
        checkStateLoss();
        executePendingTransactions();
        return popBackStackState(mHost.getHandler(), null, -1, 0);
    }

这里面有一个checkStateLoss()方法,这个和上面说的onSava以后右操作fragment类似

   private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
    }

这里判断了一个状态mStateSaved,如果当前activity已经onSava直接就抛出异常

知道了原因我们可以用finish代替super.onBackPressed();因为到这一步的时候你的拦截业务异常处理完毕
释放是在onDestory,不会有其他影响。随便链接下别人的处理方法,和我们上面分析的一致。
解决 IllegalStateException: Can not perform this action after onSaveInstanceState

listview右侧文字遮挡问题
发现机型锤子手机 版本v1.5.3 android4.4.4

异常现象在这个手机显示滚动条的时候回遮挡最右侧文字,原因用了 android:scrollbarStyle="outsideInset"属性,会在最右侧添加一层view遮挡下面的文字。

java.lang.ExceptionInInitializerError
这个错误是说变量初始化出现问题,通常出现在静态变量尤其是单例模式。这种问题往往是初始化顺序不对造成的
ExceptionInInitializerError
这个例子里面单例写法不严谨
应修改

public static Example getInstance()  { 
   if(null == example) {
      example = new example();
   }
	 return example;  
} 

第三方库可以不加try catch。一般只在问题都是异步线程造成的,可以在调用的地方同步
private static Object object = new Object();

synchronized (object) {
//调用第三方 的代码

}

MediaBrowserServiceCompat
最近要项目中要添加音频播放,最后决定使用google sample示例代码提到的support的MediaBrowserServiceCompat

在代码中使用发现这个demo有一些坑
1,Activity+fragment 显示列表页面 点击二级及其播放都是正常的,但是如果打开了全屏播放的Activity,在返回上个activity的时候需要刷新数据这个使用调用mMediaFragmentListener.getMediaBrowser().subscribe(mMediaId, mSubscriptionCallback);
对应的MusicService里面onLoadChildren方法不会触发,造成现象就是无法显示列表内容
最后确定原因是
mMediaBrowser.connect();demo里面写着onStart ,改成在onCreate调用
同时把onStart代码移到onDestory里面

   public void onDestroy() {
        if (mMediaBrowser != null) {
            mMediaBrowser.unsubscribe(mMediaBrowser.getRoot());
            mMediaBrowser.disconnect();
        }
        if (MediaControllerCompat.getMediaController(this) != null) {
            MediaControllerCompat.getMediaController(this).unregisterCallback(mMediaControllerCallback);
        }
        super.onDestroy();
    }

2 getSupportMediaController()方法不存在问题,这个我们在移植到自己项目中会发现这个FragmentActivity不存在这个方法。查看原demo源码如下

       /**
     * Retrieves the current {@link MediaControllerCompat} for sending media key and volume events.
     *
     * @return The controller which should receive events.
     * @see #setSupportMediaController(MediaControllerCompat)
     * @see #getMediaController()
     * @deprecated Use {@link MediaControllerCompat#getMediaController} instead. This API will be
     * removed in a future release.
     */
    @Deprecated
    final public MediaControllerCompat getSupportMediaController() {
        return MediaControllerCompat.getMediaController(this);
    }
   

如果发现这个问题 我们只需要换成下面代码即可

MediaControllerCompat.getMediaController(this);

同时还有setSupportMediaController 换成 MediaControllerCompat.setMediaController(this, mediaController);
3 在全屏播放页面拖动seekbar奔溃问题
这个是因为上个页面使用了cardview,这个又加载了fragment作为列表页面的播放控制器。
在父类的activity里面因为上面1中改了调度方式,在拖动进度的时候加了监听的activity就会得到状态,然后来控制fragment的显示和隐藏。但是如果activity不可见时,在进行这种操作就会造成奔溃。
调度源码如下

       @Override
                public void onMetadataChanged(MediaMetadataCompat metadata) {
                    //播放的媒体数据发生变化时的回调
                    if (shouldShowControls()) {
                        showPlaybackControls();
                    } else {
                        LogHelper.d(TAG, "mediaControllerCallback.onMetadataChanged: " +
                                "hiding controls because metadata is null");
                        hidePlaybackControls();
                    }
                }

修改方案我们可以在onResume 和onStop的时候记录acvitiy状态isRunning
然后在showPlaybackControls和hidePlaybackControls 执行开始加一下判断即可

  if (!isRunning) {
            return;
        }

at android.view.GLES20Canvas.clipPath(GLES20Canvas.java:417)
在绘图的使用使用了canvas.clipPath(mPath, Region.Op.REPLACE);这样的代码。在4.0.x系统就会出现这个异常,查看网上的解决办法都是说硬件加速导致,需要关闭Activity或者view硬件加速。android:hardwareAccelerated=“false”
mview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
但是修改后项目的错误并没有消除。

后查看源码发现4.0.4与4.0.3这两个版本中GLES20Canvas,clipPath的实现均为如下代码的

public boolean clipPath(Path path, Region.Op op) {  
         throw new UnsupportedOperationException();  
 }  

在4.1.1以后的代码实现如下

@Override  
public boolean clipPath(Path path, Region.Op op) {  
         // TODO: Implement  
         path.computeBounds(mPathBounds, true);  
        return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top,  
                mPathBounds.right, mPathBounds.bottom, op.nativeInt);  
     }  

在canvas.clipPath(mPath, Region.Op.REPLACE)加速try catch就可以处理这个异常

启动OOM问题
最近要发布一个版本,在测试时发现程序启动后到首页面,什么都不做情况下,和以前比内存多了很多。
分析最近提交代码没有发现异常,只能分析内存分配
http://www.dzwanli.com.cn/?p=1464
http://www.jianshu.com/p/cf8c1c43bbae
对比调用Gc前后前后数据,发现内存变化明显
分配最多的对象是String(包括StringBuffer ,StringBuilder等),这里可以看到具体的string 分配的内容,所以可以找到关联的代码

经过分析发现这些string绝大多数是在字符串解析,日志拼接,调试日志造成的
以前代码
示例
日志记录:
String log = baseInfo+actionName+actionCode+content
调试代码

LogUtils.debug(getTest());

getTest(){
	String s = "";
  for(){
  s += "i ="+i+" name="+xx+"\n"
  }
  .....
}
依赖LogUtils, 里面if(debug)判断来调用系统日志打印

日志记录生成的数据因为最后一个字段会带上当时时间和其他信息,造成log这个对象每次都不一样,系统需要一直分配新地址和堆栈位置
调试代码虽然log没有输出,但是这个函数被调用了,作为临时变量传递也需要分配资源

修改方案:日志用StringFormat
调试代码直接注释

进过修改再次测试发现略微有改善但是还是很高。
分析启动app,主要是application初始化各种配置,起始页面初始化数据
分别注销相应代码依然没有大的变化

猜测可能是资源问题造成的,对比资源文件除了修改xml以为,还有启动页面背景图换了。
测试不加载这个背景图,内存瞬间降低了很多,加上内存就多了很多,根据计算(图片宽4)远小于增加的内存,查阅资料
http://blog.csdn.net/smileiam/article/details/68946182
http://blog.csdn.net/a704755096/article/details/46342689

在对比项目,发现这个图片直接放到了drawable里面了,并没有放在我们常用的xxh文件下,造成图片放大
(宽(手机密度/文件夹对应密度)^2 *色彩格式每一个像素占用字节数),
drawable默认对应160,用xxh密度的手机是480 。图片内存量直接多了9倍。

修改意见:
启动的默认背景大图如果可以,切分模块来显示,后根据下载图片来显示
放在xxh下面,多数机型不需要转换。(但需考虑对低端机型的影响)
默认首次打开不放背景图,用logo代替,下次根据下载图片显示
不直接使用src和drawable,使用第三方的库比如Glide来加载资源
这里备注一下:网络图片或在用Glide是一种模式,具体原因参见上面的2个连接。系统对资源图片和网络图片处理不一样

文章目录

猜你喜欢

转载自blog.csdn.net/zh_qianwei/article/details/68944996