1、LayoutInflater加载布局导致宽高失效
现象:
使用
LayoutInflater
加载布局时,第二个参数root
如果为null
,通过再addView
添加该布局,会导致布局内部设置的宽高失效;如果root
不为空,而且attachToRoot
为true
,则不会失效
原因:
if (root != null) {
//1、如果root不为null,创建布局参数(因为给addView传的参数都是params)
//所以不管attachToRoot是否为空,布局View都被设置了LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//标识1
temp.setLayoutParams(params);
}
}
if (root != null && attachToRoot) {
//2、root不为空,attachToRoot为true:调用addView,内部也会调用
//if (!checkLayoutParams(params)) {
// params = generateLayoutParams(params);
//}
//也就是将从外部传进来的params原封不动的设置给View,效果同标识1
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
//如果root为空,则不会对view设置任何布局参数
result = temp;
}
1、root为空
或者
attachToRoot 为 false:不会为View
设置任何的布局参数,此时设置view
宽高是无效的
2、root不为空同时
attachToRoot 为 false:会为View
设置一个来自ViewGroup
的LayoutParams
,属性为wrap_content
,因此此时设置view
宽高是生效的
3、root不为空同时
attachToRoot 为 true:调用addView
,而addView
内部也会为View
设置一个布局参数(来自于设置进addView
的params
,原封不动),因此此时设置view
宽高是生效的
总结:
不论是在
inflate
时设置true
,还是设置false
然后手动addView
,源码都是调用的addView
,但是是实现不同的addView
,root
为null
的话就会导致params
为null
,addView
会根据params
方法决定要不要调用ViewGroup
本身的generateDefaultLayoutParams
,这个方法生成的布局是wrap_content
的,也就能解释为什么root
为空时布局文件的宽高不生效
2、通过Service启动Activity失败
现象
通过
AIDL
从客户端启动服务端的Activity
失败
原因
Service
调用startActivity
调用的是ContextImpl
的startActivity
,而Activity
是自己重写了startActivity方法
,ContextImpl
启动Activity
必须要有Intent.FLAG_ACTIVITY_NEW_TASK
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
3、部分手机启动时会有一闪而过的白屏或者黑屏
原因
windowDisablePreview
属性代表窗口的预览动画
解决
windowDisablePreview
禁用这个属性就可以了
4、使用FragmentStatePagerAdapter导致重新加载数据
现象
使用
FragmentStatePagerAdapter
导致ViewPager
数量多内存不足时Fragment
数据重新加载
原因
FragmentPagerAdapter
的destroyItem
调用的是detach
,仅仅是在页面上让fragment
的UI
脱离Activity
的UI
,但是fragment
仍然保存在内存里,并不会回收内存。FragmentStatePagerAdapter
的destroyItem
调用的是remove
,会remove
之前加载的fragment
从而将内存释放掉。
//--------------FragmentPagerAdapter-------------------//
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
this.mCurTransaction.detach((Fragment)object);
}
//--------------FragmentStatePagerAdapter-------------------//
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
while(this.mSavedState.size() <= position) {
this.mSavedState.add((Object)null);
}
this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
this.mFragments.set(position, (Object)null);
this.mCurTransaction.remove(fragment);
}
总结
- 当
需要加载的页面较少且每个页面的数据相对变化较少
的时候应当使用FragmentPagerAdapter
- 当
需要加载的页面较多,并且每个页面的数据量比较大或者数据经常变化,占用内存较多的时候
应当使用FragmentStatePagerAdapter
5、继承了Activity并重写了onCreate发现不显示
原因
onCreate
有两个方法:
onCreate(Bundle savedInstanceState)
(这是我们平常用的)onCreate(Bundle savedInstanceState, PersistableBundle persistentState)
总结
PersistableBundle:用于保存异常状况下关闭时Activity中的数据(手机由于过热,没电或者第三方定制Rom由于卡顿而异常关机的情况),他实际上是一种数据持久化的Activity
(摘自网络)
还有另外两个生命周期也更新了这个方法:
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState)
使用:
android:persistableMode="persistAcrossReboots"
6、Broadcast中IntentFilter优先级
现象
只要设置了
setPriority
,用sendBroadcast
也可以让优先级生效(有序)
,但是不能通过abort
去截断,要截断只能用sendOrderedBroadcast
总结
所以
sendBroadcast
和sendOrderedBroadcast
的本质区别是能否中断广播的传输
7、onCreate不能更新UI
现象
在
onCreate
更新ui
不生效
原因
invalidate
和requestLayout
最终都会调用checkThread
去检查当前线程是不是创建ViewRootImpl
的线程
而ViewRootImpl
的创建是在ActivityThread # handleResumeActivity
里面的wm.addView
,也就是在onResume
后才会添加Window
和makeVisible
1、引申出另外一个问题:
子线程能不能更新UI?
我感觉应该是可以的,只要在子线程addView
,创建了ViewRootImpl
就可以
2、引申第二个问题:
为什么checkThread不会报错
因为在invalidateInternal
里面只有p!=null
才会调用p.invalidateChild
,所以根本没有调用到ViewRootImpl
里面去
3、开启硬件加速后,
onDescendantInvalidated
为什么可以注释掉checkThread
,因为RenderThread
不是主线程而是渲染线程?
8、ObjectAnimator可以使用自定义Bean修改属性
现象
定义一个
JavaBean
,可以通过ObjectAnimator去修改属性(虽然知道ObjectAnimator继承自ValueAnimator,但一直以为只有ValueAnimator才可以)
//property放具体属性
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(JavaBean,"property",0,1);
9、SharedPreference导致UI线程阻塞
之前用
sp
的时候发现有些时候有点卡顿,后来读源码才知道原来是因为getXXX
的时候,如果sp
还没读取完整个xml
文件的话,是会执行wait
的
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//主要就是在这里,开启了一个线程去读取文件
startLoadFromDisk();
}
@UnsupportedAppUsage
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@GuardedBy("mLock")
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
//在这里执行了wait
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
10、Fragment Crash
Fragment如果自己实现了带参构造,一定要有默认的空构造,否则会crash(instantiate)