记录开发中遇到的 bug,不再让自己重复地被同样的 bug 折磨。
时间:2020年04月05日17:34:05
问题描述:
在 xml 里有一个 TextView
控件:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="40sp"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
在代码里,希望通过 TextView
的 setBackgroundColor()
方法设置其背景为绿色。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.setBackgroundColor(0xFF00FF00)
}
}
但是,在 0xFF00FF00
下面会出现红色的波浪线,提示信息:The integer literal does not conform to the expected Int.
解决办法:
当我把光标放在报错处,使用快捷键 ALT + Enter 后,提示我:Convert expression to Int。
执行这个操作后,代码变为:
textView.setBackgroundColor(0xFF00FF00.toInt())
为什么需要做一下转换呢?
因为 0xFF00FF00
赋值给一个变量时,是 Long
类型的:
val color: Long = 0xFF00FF00
在 Kotlin 中,我们需要显式地进行类型转换。而在 Java 中,却不用这样显式转换:
int color = 0xff00ff00;
textView.setBackgroundColor(0xff00ff00);
需要注意的是,下面这样设置颜色是不会有效果的:
textView.setBackgroundColor(0x00FF00)
参考:
Color Int of ARGB in Kotlin
Cannot use argb color int value in Kotlin?
时间:2020年05月12日17:34:05
开始使用的命令是:
ffmpeg -i "concat:seconds10.mp3|seconds10.mp3" -acodec copy seconds20.mp3
对于 .mp3 的格式,这个命令是成功的; 但是,这个命令在少数情况下,拼接了多段音乐,但是播放的时候却只有第一段,后面的几段是没有声音的。
使用命令:
ffmpeg -i music.amr -filter_complex "[0:a]afifo[a0];[0:a]afifo[a1];[a0][a1]concat=n=2:v=0:a=1[a]" -map "[a]" result.amr
得到错误:
Input #0, amr, from 'music.amr':
Duration: 00:00:15.96, bitrate: 12 kb/s
Stream #0:0: Audio: amr_nb (samr / 0x726D6173), 8000 Hz, mono, flt
Automatic encoder selection failed for output stream #0:0. Default encoder for format amr (codec amr_nb) is probably disabled. Please choose an encoder manually.
Error selecting an encoder for stream 0:0
查看文档 https://trac.ffmpeg.org/wiki/Concatenate#demuxer:
拼接相同类型的文件有两种方法
1.the concat ‘‘demuxer’’
2.the concat ‘‘protocol’’
第一种方式更加灵活——它需要相同的编解码器,但可以使用不同的容器格式;它可以被用于任何容器格式; 第二种方式仅适用于少数容器。
选用第一种方式:
新建一个 mylist.txt 的文件,写入内容:
file 'music.amr'
file 'music.amr'
file 'music.amr'
需要注意的是,文件名前后加上单引号(’), 避免文件名中有空格造成拼接错误。
输入命令:
ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.amr
可以正确输出。
时间:2020年05月12日17:34:05
在命令行下查看一个没有音频流的视频信息:
(base) wangzhichao@wangzhichao:~/视频$ ffmpeg -i nuanyangxiajingyin.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'nuanyangxiajingyin.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.29.100
comment : [p:dk][20330][HUAWEI][BTV-DL09][Android 24][i][h][f][2019-12-18_23:11:12]
copyright : 42115e51f7b367c8b8438b37ea04c12c
Duration: 00:00:27.20, start: 0.000000, bitrate: 7666 kb/s
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1088x1920, 7664 kb/s, 30 fps, 30 tbr, 1000k tbn, 2000k tbc (default)
Metadata:
handler_name : VideoHandler
At least one output file must be specified
可以看到,这个视频只有一个视频流:
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1088x1920, 7664 kb/s, 30 fps, 30 tbr, 1000k tbn, 2000k tbc (default)
但实际的需求需要给视频设置音量,如果一个视频没有音频流,再去给它设置音量,会直接崩溃。
所以,需要给没有音频流的视频添加静音音频。
参考https://stackoverflow.com/questions/42147512/ffmpeg-adding-silence-struggling-to-use-i-anullsrc-option
,使用命令:
ffmpeg -i nuanyangxiajingyin.mp4 -f lavfi -i anullsrc -c:v copy -c:a aac -shortest nuanyangxiangaudio.mp4
再查看生成的视频信息:
(base) wangzhichao@wangzhichao:~/视频$ ffmpeg -i nuanyangxiangaudio.mp4
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.29.100
comment : [p:dk][20330][HUAWEI][BTV-DL09][Android 24][i][h][f][2019-12-18_23:11:12]
copyright : 42115e51f7b367c8b8438b37ea04c12c
Duration: 00:00:27.21, start: 0.000000, bitrate: 7670 kb/s
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1088x1920, 7664 kb/s, 30 fps, 30 tbr, 1000k tbn, 2000k tbc (default)
Metadata:
handler_name : VideoHandler
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
At least one output file must be specified
可以看到生成的视频里包含了音频流:Stream #0:1(und): Audio。
时间:2020年05月12日17:34:05
参考:https://blog.csdn.net/iblade/article/details/90449089
时间:2020年05月12日17:34:05
发现应用在一个性能差的手机上会报出各种 OOM,
报错指向了 ExoPlayer2
java.lang.OutOfMemoryError: Failed to allocate a 65548 byte allocation with 40232 free bytes and 39KB until OOM
at com.google.android.exoplayer2.upstream.DefaultAllocator.allocate(DefaultAllocator.java:102)
at com.google.android.exoplayer2.source.SampleDataQueue.preAppend(SampleDataQueue.java:392)
at com.google.android.exoplayer2.source.SampleDataQueue.sampleData(SampleDataQueue.java:181)
at com.google.android.exoplayer2.source.SampleQueue.sampleData(SampleQueue.java:471)
at com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.readSample(Mp4Extractor.java:552)
at com.google.android.exoplayer2.extractor.mp4.Mp4Extractor.read(Mp4Extractor.java:192)
at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:982)
at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:391)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
多次抛出 OOM:抛出这类错误,一般是由于方法重复调用、死循环引起,直至内存耗尽。
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available
报错指向 Lottie,但是我的 Lottie 资源都很小,并且不包含图片资源。
FATAL EXCEPTION: main
Process: com.koki.callshow, PID: 3574
java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 552 free bytes and 552B until OOM
at java.util.concurrent.CopyOnWriteArrayList.iterator(CopyOnWriteArrayList.java:184)
at java.util.concurrent.CopyOnWriteArraySet.iterator(CopyOnWriteArraySet.java:306)
at com.airbnb.lottie.utils.BaseLottieAnimator.notifyUpdate(BaseLottieAnimator.java:87)
at com.airbnb.lottie.utils.LottieValueAnimator.doFrame(LottieValueAnimator.java:98)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:892)
at android.view.Choreographer.doCallbacks(Choreographer.java:696)
at android.view.Choreographer.doFrame(Choreographer.java:628)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:880)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:205)
at android.app.ActivityThread.main(ActivityThread.java:5814)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:806)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)
05-10 14:15:44.306 E/AndroidRuntime( 3574): FATAL EXCEPTION: main
05-10 14:15:44.306 E/AndroidRuntime( 3574): Process: com.koki.callshow, PID: 3574
05-10 14:15:44.306 E/AndroidRuntime( 3574): java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 552 free bytes and 552B until OOM
05-10 14:15:44.306 E/AndroidRuntime( 3574): at java.util.concurrent.CopyOnWriteArrayList.iterator(CopyOnWriteArrayList.java:184)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at java.util.concurrent.CopyOnWriteArraySet.iterator(CopyOnWriteArraySet.java:306)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at com.airbnb.lottie.utils.BaseLottieAnimator.notifyUpdate(BaseLottieAnimator.java:87)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at com.airbnb.lottie.utils.LottieValueAnimator.doFrame(LottieValueAnimator.java:98)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:892)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.view.Choreographer.doCallbacks(Choreographer.java:696)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.view.Choreographer.doFrame(Choreographer.java:628)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:880)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.os.Handler.handleCallback(Handler.java:815)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.os.Handler.dispatchMessage(Handler.java:104)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.os.Looper.loop(Looper.java:205)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at android.app.ActivityThread.main(ActivityThread.java:5814)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at java.lang.reflect.Method.invoke(Native Method)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:806)
05-10 14:15:44.306 E/AndroidRuntime( 3574): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)
报错指向给 ImageView
设置图片,但是图片并不是大图片
FATAL EXCEPTION: main
java.lang.OutOfMemoryError: Failed to allocate a 129612 byte allocation with 110080 free bytes and 107KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:651)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:486)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:1085)
at android.content.res.Resources.loadDrawableForCookie(Resources.java:2935)
at android.content.res.Resources.loadDrawable(Resources.java:2822)
at android.content.res.Resources.getDrawable(Resources.java:939)
at android.content.Context.getDrawable(Context.java:478)
at androidx.core.content.ContextCompat.getDrawable(ContextCompat.java:454)
at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:144)
at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:132)
at androidx.appcompat.content.res.AppCompatResources.getDrawable(AppCompatResources.java:104)
at androidx.appcompat.widget.AppCompatImageHelper.loadFromAttributes(AppCompatImageHelper.java:59)
at androidx.appcompat.widget.AppCompatImageView.(AppCompatImageView.java:78)
at androidx.appcompat.widget.AppCompatImageView.(AppCompatImageView.java:68)
at androidx.appcompat.app.AppCompatViewInflater.createImageView(AppCompatViewInflater.java:187)
at androidx.appcompat.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:107)
at androidx.appcompat.app.AppCompatDelegateImpl.createView(AppCompatDelegateImpl.java:1407)
at androidx.appcompat.app.AppCompatDelegateImpl.onCreateView(AppCompatDelegateImpl.java:1457)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:746)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:835)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:798)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
at com.koki.callshow.databinding.PreviewVideoActivityBinding.inflate(PreviewVideoActivityBinding.java:151)
at com.koki.callshow.databinding.PreviewVideoActivityBinding.inflate(PreviewVideoActivityBinding.java:145)
at com.koki.callshow.ui.previewvideo.PreviewVideoActivity.onCreate(PreviewVideoActivity.java:127)
at android.app.Activity.performCreate(Activity.java:6345)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1146)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2538)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2673)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1507)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:205)
at android.app.ActivityThread.main(ActivityThread.java:5814)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:806)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:696)
我有点奇怪,为什么可以到处抛出 OOM 异常呢?
看到 JakeWharton 大神在 github 上 OutOfMemoryError thrown while trying to throw OutOfMemoryError #534 的回复:
OOMs rarely are caused by the stacktrace they report. In reality, it’s the
last straw the broke the camel’s back.
翻译一下:OOM 异常很少是由于所报出的堆栈信息引起的。事实上,所报出的堆栈信息只不过是压死骆驼的最后一根稻草而已。
我使用 AndroidStudio 的 Profiler 工具去查看内存占用,最终定位到是由于 ExoPlayer2
占用内存过多导致的 OOM。
时间:2020年05月12日17:34:05
需要卸载 app,重新安装。
时间:2020年05月18日18:36:05
报错日志:
2020-05-18 20:18:00.838 10098-10098/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.koki.callshow, PID: 10098
java.lang.NoClassDefFoundError: com.koki.callshow.call.CallerUtils
at com.koki.callshow.call.CallerUtils.show(CallerUtils.java:257)
at com.koki.callshow.call.CallService.onCallAdded(CallService.java:144)
at android.telecom.InCallService$2.onCallAdded(InCallService.java:392)
at android.telecom.Phone.fireCallAdded(Phone.java:400)
at android.telecom.Phone.internalAddCall(Phone.java:154)
at android.telecom.InCallService$1.handleMessage(InCallService.java:222)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:213)
at android.app.ActivityThread.main(ActivityThread.java:8169)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
Caused by: java.lang.ExceptionInInitializerError
at com.koki.callshow.call.CallerUtils.getCallerName(CallerUtils.java:162)
at com.koki.callshow.call.RingMusicManager.lambda$fetchRingtoneUri$0(RingMusicManager.java:81)
at com.koki.callshow.call.-$$Lambda$RingMusicManager$-DQAxOC3RpKDc-EvJIPV0C1jUw0.call(Unknown Source:2)
at io.reactivex.internal.operators.observable.ObservableFromCallable.subscribeActual(ObservableFromCallable.java:43)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:929)
Caused by: java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
at android.os.Handler.(Handler.java:241)
at android.os.Handler.(Handler.java:142)
at android.telephony.PhoneStateListener.(PhoneStateListener.java:416)
at android.telephony.PhoneStateListener.(PhoneStateListener.java:385)
at com.koki.callshow.call.CallerUtils$1.(CallerUtils.java:451)
at com.koki.callshow.call.CallerUtils.(CallerUtils.java:451)
at com.koki.callshow.call.CallerUtils.getCallerName(CallerUtils.java:162)
at com.koki.callshow.call.RingMusicManager.lambda$fetchRingtoneUri$0(RingMusicManager.java:81)
at com.koki.callshow.call.-$$Lambda$RingMusicManager$-DQAxOC3RpKDc-EvJIPV0C1jUw0.call(Unknown Source:2)
at io.reactivex.internal.operators.observable.ObservableFromCallable.subscribeActual(ObservableFromCallable.java:43)
at io.reactivex.Observable.subscribe(Observable.java:12267)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:929)
问题分析:
Caused by 越靠下的位置,越接近真正的原因。这一点不能搞反了。
我把相关的代码提取剥离出来,放在一个简单的 demo 里,来分析这个问题。
一个 Utils.java
类:
public class Utils {
private Utils() {
//no instance
}
public static void method() {
}
private static PhoneStateListener listener = new PhoneStateListener();
}
MainActivity.java
:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Utils.method();
}
}, "thread1").start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Utils.method();
}
});
}
}
运行程序,点击按钮,看到了同样的报错:
E/AndroidRuntime: FATAL EXCEPTION: thread1
Process: com.example.myapplication, PID: 5251
java.lang.ExceptionInInitializerError
at com.example.myapplication.MainActivity$1$1.run(MainActivity.java:20)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
at android.os.Handler.(Handler.java:238)
at android.os.Handler.(Handler.java:146)
at android.telephony.PhoneStateListener$1.(PhoneStateListener.java:270)
at android.telephony.PhoneStateListener.(PhoneStateListener.java:270)
at android.telephony.PhoneStateListener.(PhoneStateListener.java:240)
at com.example.myapplication.Utils.(Utils.java:18)
at com.example.myapplication.MainActivity$1$1.run(MainActivity.java:20)
at java.lang.Thread.run(Thread.java:818)
好了,到这里,我可以在 demo 里分析问题了。
需要注意的是异常是在子线程(thread1
)里抛出的。
首先看到最下面的 Caused by:
Caused by: java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
意思是说在 Looper
上读取 mQueue
发生了空指针异常。
接着往下看,看到了 Handler
,再往后看到了 PhoneStateListener
的构造方法调用。
查看 PhoneStateListener
的构造函数:
/**
* Create a PhoneStateListener for the Phone with the default subscription.
* This class requires Looper.myLooper() not return null.
*/
public PhoneStateListener() {
this(null, Looper.myLooper());
}
/**
* Create a PhoneStateListener for the Phone using the specified subscription
* and non-null Looper.
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public PhoneStateListener(Integer subId, Looper looper) {
this(subId, new HandlerExecutor(new Handler(looper)));
if (subId != null && VMRuntime.getRuntime().getTargetSdkVersion()
>= Build.VERSION_CODES.Q) {
throw new IllegalArgumentException("PhoneStateListener with subId: "
+ subId + " is not supported, use default constructor");
}
}
空参的构造方法调用了带参的构造方法,第二个参数是 Looper.myLooper()
对象,从方法说明里可以看到:Looper.myLooper()
不可以为 null
。
那么,我们的 Looper.myLooper()
是不是 null
?如果为 null
,又会有什么影响呢?
在带参的构造方法中,可以看到会调用 Handler
的构造方法:
/**
* Use the provided {@link Looper} instead of the default one.
* @param looper The looper, must not be null.
*/
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
如果 Looper.myLooper()
为 null
,那么 looper.mQueue
就会报出空指针异常。
为什么 Looper.myLooper()
是 null
?
因为 Utils
里的静态成员 listener
是在必要的时候才初始化的。在我们的代码里,是在调用 Utils.method();
才去初始化 listener
。所以,代码里的 listener
是在子线程初始化的。
而子线程里调用 Looper.myLooper()
,会从线程本地变量 sThreadLocal
里获取 Looper
对象,但是获取到的是 null
。这是因为没有调用 Looper.prepare()
来给 sThreadLocal
设置 Looper
对象,
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
解决办法:
让 Utils
类的成员及早地在主线程完成初始化,因为主线程是存在 Looper
对象的。
在 Utils
类中添加 init()
方法:
public static void init() {
}
在程序开启的地方调用:
Utils.init();
时间:2020年06月19日
问题描述:
布局如下:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_root"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Hello,world"
android:textColor="#00ff00"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv1"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="#00ff00"
android:text="Hello,life"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
androidx.constraintlayout.widget.ConstraintLayout>
注意,第一个 TextView
有 id,但是第二个 TextView
没有 id。
使用 ConstraintSet
来设置 ConstraintLayout
的子组件的布局参数:
val binding = MyActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
val constraintSet = ConstraintSet()
constraintSet.clone(binding.clRoot)
constraintSet.setVerticalBias(R.id.tv1, 0.2f)
constraintSet.applyTo(binding.clRoot)
报错信息:
Caused by: java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet
at androidx.constraintlayout.widget.ConstraintSet.clone(ConstraintSet.java:713)
at com.clean.master.professor.ui.mine.MyActivity.onCreate(MyActivity.kt:23)
at android.app.Activity.performCreate(Activity.java:8086)
at android.app.Activity.performCreate(Activity.java:8074)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1313)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3755)
解决办法:
给 ConstraintLayout
的所有子组件都加上 id。
在这里,就是给第二个 TextView
添加 id。
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_root"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Hello,world"
android:textColor="#00ff00"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv1"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="#00ff00"
android:text="Hello,life"
android:textSize="24sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
androidx.constraintlayout.widget.ConstraintLayout>
时间:2020年06月19日
问题描述:
编译项目时,报出这个错误,没有指示出更详细的信息。
这样的报错确实不友好,因为没有给出错误的堆栈信息。
解决办法:
这时,点击 AndroidStudio 右侧的 Grade -> app -> build 下的 build,
在 Run 面板可以看到错误信息:
在这里建议我们去运行 Run with --stacktrace
去得到异常的堆栈信息。我们可以直接点进去,就能运行了。
同样在 Run 面板下看到得到的堆栈信息:
这样就定位到了问题。
时间:2020年06月19日
问题描述:
在 debug 模式下使用 DoraemonKIt,一个页面里的EditText
去请求获取焦点并调起键盘,但是却获取不到。
而我把 DoraemonKIt 关掉之后,是可以获取到的。release 包也是可以获取到的。
解决办法:
在 debug 模式下,关闭 DoraemonKit。
参考提的 issue:https://github.com/didi/DoraemonKit/issues/569
代码出错了,关键是要仔细查看日志。能够仔细地查看日志,就离解决问题很近了。