- 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越界,而且设置以后还不这个值存储了,再次启动还要调这个方法,造成奔溃。但是这个错误只有在部分机型才会这样,有些机型不会。
-
重写View onSave异常
Make sure other views do not use the same id -
IntentService ANR
下面是IntentService的说明
<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个连接。系统对资源图片和网络图片处理不一样