android aidl相关学习

客户端服务端配置

1.客户端和服务端需要配置相同的.aidl文件、文件名、包名路径都需要一致
在这里插入图片描述
首先在main路径下创建一个aidl的文件夹,里面再放aidl文件
客户端、服务端配置:

package com.myaildtest;
//为了能正常通信,Android 要求客户端和服务端的 aidl 文件包名和文件名必须保持一致
interface IMyAidlService {
    int getName();
    void addName(in String s);  //in代表输入方向
}

设置完毕文件后,点击as的build,使其生成对应的IMyAidlService.java文件,代码如下

public interface IMyAidlService extends android.os.IInterface
{
  /** Default implementation for IMyAidlService. */
  public static class Default implements com.myaildtest.IMyAidlService
  {
    @Override public int getName() throws android.os.RemoteException
    {
      return 0;
    }
    @Override public void addName(java.lang.String s) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.myaildtest.IMyAidlService
  {
    private static final java.lang.String DESCRIPTOR = "com.myaildtest.IMyAidlService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.myaildtest.IMyAidlService interface,
     * generating a proxy if needed.
     */
    public static com.myaildtest.IMyAidlService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.myaildtest.IMyAidlService))) {
        return ((com.myaildtest.IMyAidlService)iin);
      }
      return new com.myaildtest.IMyAidlService.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_getName:
        {
          data.enforceInterface(descriptor);
          int _result = this.getName();
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
        case TRANSACTION_addName:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          this.addName(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.myaildtest.IMyAidlService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public int getName() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getName();
          }
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addName(java.lang.String s) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(s);
          boolean _status = mRemote.transact(Stub.TRANSACTION_addName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addName(s);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.myaildtest.IMyAidlService sDefaultImpl;
    }
    static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.myaildtest.IMyAidlService impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.myaildtest.IMyAidlService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public int getName() throws android.os.RemoteException;
  public void addName(java.lang.String s) throws android.os.RemoteException;
}

可以看到定义的两个方法getAge()和addName()已经是throws android.os.RemoteException的状态了
代码具体解释我们下文再讲
下一步就是客户端和服务端怎么通信了

2.相同的包名下,服务端设置service

package com.myaildtest

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class MyAidlTestService : Service() {

    override fun onBind(intent: Intent): IBinder {
    	Log.d("aidl TAG","onBind()")
        return ContactManagerBinder()  //返回对象给客户端,即返回IMyAidlService.Stub()
    }

    //返回的是一个binder
    private class ContactManagerBinder : IMyAidlService.Stub() {
        override fun getName(): Int {
            return 114514
        }

        override fun addName(s: String?) {
            Log.d("aidl TAG","name:$s")
            //todo 
        }
    }
}

我们定义一个Bindler,继承IMyAidlService.Stub,可以看到

public static abstract class Stub extends android.os.Binder implements com.myaildtest.IMyAidlService

Stub类本质就是一个Binder,并实现了我们的aidl接口,我们在这里重写方法,客户端可以调用次方法

3.客户端通过aidl通信,并调用服务端api
客户端定义ServiceConnnection,并startService

private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            val mIContactsManager = IMyAidlService.Stub.asInterface(service)
            Log.d("aidl TAG", "connnected:${mIContactsManager.name},addName:${mIContactsManager.addName("jack")} ")
        }

        override fun onServiceDisconnected(name: ComponentName) {
            Log.d("aidl TAG", "onServiceDisconnected: ")
        }
    }


	val intent = Intent()
	intent.setComponent(ComponentName(
    	"com.example.myapplication",   //包名
        "com.myaildtest.MyAidlTestService"   //service名
    ))
    bindService(intent, serviceConnection, BIND_AUTO_CREATE)
    

看下调用方式:
在客户端,我们触发bindService()逻辑后,连接到了服务端的Service,触发服务端的onBInder(),并返回给客户端ContactManagerBinder()对象,客户端的onServiceConnected()回调,通过
IMyAidlService.Stub.asInterface(service)得到ContactManagerBinder()对象
在这里插入图片描述
我们可以根据log发现,跨进程通信正常了,且和预期的调用顺序一样。那么我们现在就根据IMyAidlService.java源码来了解一下,这个底层逻辑是啥,它到底是什么进行通信的呢?

aidl.java源码分析

aidl.java中最重要的就是Stub类的实现
我看来看逻辑调用顺序,首先就是,客户端的onServiceConnected()中的service:Ibinder参数,我们看上文的代码其实就知道,这个service:Ibinder就是服务端的IMyAidlService.Stub(),得到此参数后,开始调用IMyAidlService.Stub.asInterface(service)

public static com.myaildtest.IMyAidlService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  //查询本地有没有这个接口
      if (((iin!=null)&&(iin instanceof com.myaildtest.IMyAidlService))) {
        return ((com.myaildtest.IMyAidlService)iin);
      }
      return new com.myaildtest.IMyAidlService.Stub.Proxy(obj);  //没有的话就用远程接口
    }

那么我们接下来看com.myaildtest.IMyAidlService.Stub.Proxy(obj)

private static class Proxy implements com.myaildtest.IMyAidlService
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;  //此时这个remote就是服务端的IMyAidlService.Stub()
      }
      @Override public android.os.IBinder asBinder()
      {
      	//asBinder()得到服务端的IMyAidlService.Stub(),即ContactManagerBinder()
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public int getName() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          //这里客户端调用transact,传入_reply参数,服务端回调onTranscat(),此时客户端挂起,等待服务端结果返回
          boolean _status = mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
          	//调用失败就用本地接口的getName()
            return getDefaultImpl().getName();
          }
          //读取结果
          _reply.readException(); 
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addName(java.lang.String s) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(s);
          //这里客户端调用transact,传入_reply,服务端回调onTranscat(),此时客户端挂起,等待服务端结果返回
          boolean _status = mRemote.transact(Stub.TRANSACTION_addName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            //调用失败就用本地的addName()
            getDefaultImpl().addName(s);
            return;
          }
          //读取结果
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.myaildtest.IMyAidlService sDefaultImpl;
    }

那么得到Proxy类了,那么下一步的addName()和getName()方法其实在上面的代码也可以看到最终走到了mRemote.transact,此方法最终调用到服务端的onTranscat()

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_getName:
        {
          data.enforceInterface(descriptor);
          int _result = this.getName();  //调用本地方法(服务端)得到结果
          reply.writeNoException();  //写入无异常
          reply.writeInt(_result);   //结果回传给客户端
          return true;
        }
        case TRANSACTION_addName:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();  //读取参数
          this.addName(_arg0);  //调用本地方法(服务端)得到结果
          reply.writeNoException();  //写入无异常
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }

以下图的类图表示IMyAidlService.java中所有类关系
在这里插入图片描述

本质上Proxy是一个实现了IMyAidlService接口的类,此代理类中核心是mRemote成员,主要操作远程服务端操作,实现的getName()和addName()主要用于调用服务端的接口。
本质上Stub是一个Binder,并且实现了IMyAidlService接口(实际上因为是抽象类,所以没有实现,具体实现是继承了Stub的服务端的类来实现),主要的功能asInterface,会将服务端传过来的binder赋值给Proxy的mRemote。
具体的流程一步步我们用下面的流程图来展示

异常

假设我们在服务端写了一个异常逻辑,那么客户端调用会报错吗?

Serverprivate class ContactManagerBinder : IMyAidlService.Stub() {
        override fun getName(): Int {
            Log.d("aidl TAG","getName start")
            val s = intArrayOf(1,2,3,4,5)
            s[12]
            Log.d("aidl TAG","getName end")
            return 114514
        }

        override fun addName(s: String?) {
            Log.d("aidl TAG","name:$s")
            //todo
        }
    }

客户端代码不动,我们可以发现,应用没有闪退,但是getName()返回了0,查看源码可以发现,这时候调用transact是失败的,最终走到了getDefaultImpl().getName()默认方法,即0

@Override public int getName() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          //会调用失败
          boolean _status = mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getName();  //返回默认实现即0
          }
          _reply.readException(); //先读取异常,有异常的话readException方法里面会直接抛出
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

同时可以看到堆栈报错信息:

*** Uncaught remote exception! Exceptions are not yet supported across processes. Client PID17734 UID 10339. 
java.lang.ArrayIndexOutOfBoundsException: length=5; index=12
	at com.myaildtest.MyAidlTestService$ContactManagerBinder.getName(MyAidlTestService.kt:20)
	at com.myaildtest.IMyAidlService$Stub.onTransact(IMyAidlService.java:65)
	at android.os.Binder.execTransactInternal(Binder.java:1561)
	at android.os.Binder.execTransact(Binder.java:1505)

可以看到这个remote exception不能捕获,因为exceptions是不支持跨进程
ok到这里了可能大家都觉得到此为止了,既然服务端exception不能捕获那么就算了,但是实际上Parcel是支持传输exception的
Parcel是支持传输exception的
Parcel是支持传输exception的

查看Parcel我们可以看到,它支持如下异常

public static int getExceptionCode(@NonNull Throwable e) {
        int code = 0;
        if (e instanceof Parcelable
                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
            // We only send Parcelable exceptions that are in the
            // BootClassLoader to ensure that the receiver can unpack them
            code = EX_PARCELABLE;
        } else if (e instanceof SecurityException) {
            code = EX_SECURITY;
        } else if (e instanceof BadParcelableException) {
            code = EX_BAD_PARCELABLE;
        } else if (e instanceof IllegalArgumentException) {
            code = EX_ILLEGAL_ARGUMENT;
        } else if (e instanceof NullPointerException) {
            code = EX_NULL_POINTER;
        } else if (e instanceof IllegalStateException) {
            code = EX_ILLEGAL_STATE;
        } else if (e instanceof NetworkOnMainThreadException) {
            code = EX_NETWORK_MAIN_THREAD;
        } else if (e instanceof UnsupportedOperationException) {
            code = EX_UNSUPPORTED_OPERATION;
        } else if (e instanceof ServiceSpecificException) {
            code = EX_SERVICE_SPECIFIC;
        }
        return code;
    }

只支持上述异常,所以我设置的越界异常,捕获不到,那么我们改成空指针异常来试试看呢?
服务端修改:

override fun getName(): Int {
            Log.d("aidl TAG","getName start")
            val s:String?= null
            s!!.length
            return 114514
        }

嗯。空指针异常被捕获到了,然后闪退

FATAL EXCEPTION: main
Process: com.example.otherprocess, PID: 20637
java.lang.NullPointerException
	at android.os.Parcel.createExceptionOrNull(Parcel.java:3262)
	at android.os.Parcel.createException(Parcel.java:3240)
	at android.os.Parcel.readException(Parcel.java:3223)
	at android.os.Parcel.readException(Parcel.java:3165)
	at com.myaildtest.IMyAidlService$Stub$Proxy.getName(IMyAidlService.java:111)
	at com.example.otherprocess.MainActivity$serviceConnection$1.onServiceConnected(MainActivity.kt:67)
	at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:2333)
	at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:2372)
	at android.os.Handler.handleCallback(Handler.java:1014)
	at android.os.Handler.dispatchMessage(Handler.java:102)
	at android.os.Looper.loopOnce(Looper.java:250)
	at android.os.Looper.loop(Looper.java:340)
	at android.app.ActivityThread.main(ActivityThread.java:9882)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957)

这时候就需要客户端添加try catch保护了

AIDL进阶

看了上述的分析其实大概了解通信原理了,实际上就是通过远程的binder,写入data、replay参数后调用transact()后再调用到onTransact(),在onTransact()中进行计算最后再把结果通过replay.write写入,客户端就可以通过这个Parcelable参数读取到结果了。那么我们可以自定义写一个binder通信,不用aidl实现

Serverclass MyBinder: Binder(), MathMethod {

    companion object {
        const val PLUS = 0
        const val MINUS = 1
    }

    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        when(code) {
            PLUS -> {
                data.enforceInterface("plus")
                val p1 = data.readInt()
                val p2 = data.readInt()
                val result = this.plus(p1,p2)
                reply?.writeInterfaceToken("plus")  //一定要放在writeNoException()之前
                reply?.writeNoException()
                reply?.writeInt(result)
                return true
            }
            MINUS -> {
                data.enforceInterface("minus")
                val p1 = data.readInt()
                val p2 = data.readInt()
                val result = this.minus(p1,p2)
                reply?.writeInterfaceToken("minus")
                reply?.writeNoException()
                reply?.writeInt(result)
                return true
            }
        }
        return super.onTransact(code, data, reply, flags)
    }

    override fun plus(a: Int, b: Int):Int {
        return a+b
    }

    override fun minus(a: Int, b: Int): Int {
        return a-b
    }
}

interface MathMethod{
    fun plus(a:Int, b:Int):Int
    fun minus(a:Int, b:Int):Int
}

首先实现一个自己的binder,不需要再继承Stub()类,在方法中我们自己实现plus和minus方法,在onTransact方法中读取参数,然后进行操作,结果通过replay.write写入。
我们再来看客户端核心代码:

client端
private val serviceConnection2: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.d("aidl TAG", "serviceConnected2 ")
            val data = Parcel.obtain()
            val reply = Parcel.obtain()
            data.writeInterfaceToken("plus")
            data.writeInt(10)
            data.writeInt(20)
            val status = service.transact(PLUS,data,reply,0)
            reply.enforceInterface("plus")   //一定要放在readException()之前
            reply.readException()
            Log.d("aidl TAG", "plus result:${reply.readInt()}")
        }

        override fun onServiceDisconnected(name: ComponentName) {
            Log.d("aidl TAG", "onServiceDisconnected: ")
        }
    }

我们拿到service的IBinder对象后,data写入参数,然后核心通过service.transact调用服务端逻辑,然后服务端运算完毕后,通过replay.read得到结果。

那么如果我们需要传递的参数并不是普通参数呢?这该怎么办???
代码如下,只要稍微做一下改动即可,因为传入的参数是Parcelable的对象,那么需要这么写:

client端
val p = Person("xiaoming",40,"boy")
p.writeToParcel(data,0)   //核心方法,使用writeToParcel,引用对象是新建的对象类
val status = service.transact(114514,data,reply,0)
reply.enforceInterface("person")
reply.readException()
val newp = Person.createFromParcel(reply)   //核心方法,使用 Person.createFromParcel,将返回的replay中数据读取,生成新对象
server端
val p = Person.createFromParcel(data)  //核心方法,传入的data参数提取对象p
val newP = getPerson(p)
reply?.writeInterfaceToken("person")
reply?.writeNoException()
newP.writeToParcel(reply!!,0)  //核心方法,将对象写入到reply中
return true

额外补充小知识

1.客户端调用proxy.getName(),最后走到实际上走的是binder线程,所以服务端不能做耗时操作

client端
private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            //main线程
        }

        override fun onServiceDisconnected(name: ComponentName) {
            //main线程
        }
    }
server端
    private class ContactManagerBinder : IMyAidlService.Stub() {
        override fun getName(): Int {
        	//binder线程,客户端的主线程,所以这里不能做耗时操作,会卡主客户端
            return 114514
        }

        override fun addName(s: String?) {
            //binder线程,客户端的主线程
        }
    }

如果要做耗时操作,那么可以给aidl文件的参数方法名加oneway前缀进行异步调用,参考文章https://juejin.cn/post/7471652290460696576

2.有时候异常堆栈有caused by但有时候又没有,这是什么原因呢?因为catch层层包装了一下,在catch中又throw了异常,一般用于纠正问题根本点

    private fun testCatch3() {
        try {
            testCatch2()
        } catch (e: MyIllegalArgumentException) {
            throw MyIndexOutException("index out bound",e)
        }
    }

    private fun testCatch2() {
        try {
            testCatch1()
        } catch (e: MyRuntimeException) {
            throw MyIllegalArgumentException("arg is error",e)
        }
    }

    private fun testCatch1(){
        var s:String? = null
        throw MyRuntimeException("s is null ,error")
    }

可以看到testCatch1抛出来MyRuntimeException,然后别testCatch2捕获,并抛出来一个MyIllegalArgumentException,最终被testCatch3捕获,抛出来MyIndexOutException。查看log日志台

FATAL EXCEPTION: main
Process: com.example.otherprocess, PID: 20097
com.example.otherprocess.trycatch.TryCatchActivity$MyIndexOutException: index out bound
	at com.example.otherprocess.trycatch.TryCatchActivity.testCatch3(TryCatchActivity.kt:36)
	at com.example.otherprocess.trycatch.TryCatchActivity.onCreate$lambda$1(TryCatchActivity.kt:28)
	at com.example.otherprocess.trycatch.TryCatchActivity.$r8$lambda$6AI6dCd7QLyrN-ZTF5C4dKTqkYk(Unknown Source:0)
	at com.example.otherprocess.trycatch.TryCatchActivity$$ExternalSyntheticLambda1.onClick(Unknown Source:2)
	at android.view.View.performClick(View.java:8140)
	at android.view.View.performClickInternal(View.java:8117)
	at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
	at android.view.View$PerformClick.run(View.java:32132)
	at android.os.Handler.handleCallback(Handler.java:1014)
	at android.os.Handler.dispatchMessage(Handler.java:102)
	at android.os.Looper.loopOnce(Looper.java:250)
	at android.os.Looper.loop(Looper.java:340)
	at android.app.ActivityThread.main(ActivityThread.java:9882)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957)
Caused by: com.example.otherprocess.trycatch.TryCatchActivity$MyIllegalArgumentException: arg is error
	at com.example.otherprocess.trycatch.TryCatchActivity.testCatch2(TryCatchActivity.kt:44)
	at com.example.otherprocess.trycatch.TryCatchActivity.testCatch3(TryCatchActivity.kt:34)
	... 15 more
Caused by: com.example.otherprocess.trycatch.TryCatchActivity$MyRuntimeException: s is null ,error
	at com.example.otherprocess.trycatch.TryCatchActivity.testCatch1(TryCatchActivity.kt:50)
	at com.example.otherprocess.trycatch.TryCatchActivity.testCatch2(TryCatchActivity.kt:42)
	... 16 more

可以看到有三种异常,我们不必慌,其实异常是层层往上抛的,只要关注最底层的Caused by即可,这就是抛出问题的最原始位置啦。
注意:写自定义Exception的没必要继承已有的空指针、数组越界异常,没有意义,需要自己继承RuntimeException,然后回抛的时候,参数cause一定要带上,例如

throw MyIllegalArgumentException("arg is error",e)

否则caused by不会打印

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C_lea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值