Android手动卸载usb从上层到底层源码分析

因为有个 bug 需要解决,所以这篇博客应用而生。网络上对底层逻辑分析比较多,但是Android 手机手动卸载usb倒是没有人去写,那我来写一下吧。
贴一张流程图,以便心中有一个大概的思路。

这里写图片描述

首先找到界面显示的位置 : Memory.java
我们可以找到点击卸载弹出dialog,代码:

Memory.java

@Override
public Dialog onCreateDialog(int id) {
    switch (id) {
    case DLG_CONFIRM_UNMOUNT:
            return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.dlg_confirm_unmount_title)
                .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        doUnmount();
                    }})
                .setNegativeButton(R.string.cancel, null)
                .setMessage(R.string.dlg_confirm_unmount_text)
                .create();
    case DLG_ERROR_UNMOUNT:
            return new AlertDialog.Builder(getActivity())
        .setTitle(R.string.dlg_error_unmount_title)
        .setNeutralButton(R.string.dlg_ok, null)
        .setMessage(R.string.dlg_error_unmount_text)
        .create();
    }
    return null;
}

我们注意到 onCreateDialog() 是重写了父类的方法,
重写了谁呢? extends SettingsPreferenceFragment ,
这个类在Android 设置源码中是非常常见的一个类,也非常重要,需要了解的童鞋可以深入的去看一下,还是很有用的。
//代码【1.0.1】

private void doUnmount() {
    // Present a toast here
    //弹出Toast,告诉用户正在 卸载 usb 设备。。。
    Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
    //获取服务 MountService 看代码 【1.0.2】
    IMountService mountService = getMountService();
    try {
        sLastClickedMountToggle.setEnabled(false);
        sLastClickedMountToggle.setTitle(getString(R.string.sd_ejecting_title));
        sLastClickedMountToggle.setSummary(getString(R.string.sd_ejecting_summary));
        // 我们看一下传过去的 sClickedMountPoint 是什么,分析流程看 代码【1.0.3】。
        // unmountVolume() 代码看【2.0.1】
        mountService.unmountVolume(sClickedMountPoint, true, false);
    } catch (RemoteException e) {
        // Informative dialog to user that unmount failed.
        showDialogInner(DLG_ERROR_UNMOUNT);
    }
}

//代码【1.0.2】
//下边这个写法是防止多次重复创建 MountService 对象,类似于单例模式

private synchronized IMountService getMountService() {
   if (mMountService == null) {
       // 千篇一律的获取服务的方法
       IBinder service = ServiceManager.getService("mount");
       if (service != null) {
           mMountService = IMountService.Stub.asInterface(service);
       } else {
           Log.e(TAG, "Can't get mount service");
       }
   }
   return mMountService;
}

//代码【1.0.3】

sClickedMountPoint = volume.getPath();//原来是挂载的路径 是个 String 类型

MountService.java

//代码【2.0.1】

public void unmountVolume(String path, boolean force, boolean removeEncryption) {
//请求权限
    validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
    waitForReady();//是一个耗时操作
    // 获取该路径下挂载的设备当前状态
    String volState = getVolumeState(path);
    if (DEBUG_UNMOUNT) {
        Slog.i(TAG, "Unmounting " + path
                + " force = " + force
                + " removeEncryption = " + removeEncryption);
    }
    //如果当前设备已经移除或卸载或不可卸载,则不做任何操作
    if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
            Environment.MEDIA_REMOVED.equals(volState) ||
            Environment.MEDIA_SHARED.equals(volState) ||
            Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
        // Media already unmounted or cannot be unmounted.
        // TODO return valid return code when adding observer call back.
        return;
    }
    //开始卸载
    //第一步注册回调函数
    UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
    //第二步,卸载 看 【2.0.2】
    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
}

//代码【2.0.2】

boolean mUpdatingStatus = false;
......//此处有代码省略
......
case H_UNMOUNT_PM_UPDATE: {
     if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
     mForceUnmounts.add(ucb);
     if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
     // Register only if needed.
     if (!mUpdatingStatus) {
if((ucb.path!=null) &&(ucb.path.equals(mExternalStoragePath)))//only scan flash but not sdcard
{
              if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
              mUpdatingStatus = true;
              mPms.updateExternalMediaStatus(false, true);
         }else{
              //根据 log 打出的信息,发现直接走了else 语句 我们继续看 【2.0.3】
          finishMediaUpdate();
         }
     }
     break;
 }

//代码【2.0.3】

public void finishMediaUpdate() {
    //发送了一个 message 让 handler 去处理 ,继续看代码 【2.0.4】
    mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
}

//代码【2.0.4】

case H_UNMOUNT_PM_DONE: {
              if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
              if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
              mUpdatingStatus = false;//false 意味着还没有完成卸载
              int size = mForceUnmounts.size();
              int sizeArr[] = new int[size];
              int sizeArrN = 0;
              // Kill processes holding references first
              ActivityManagerService ams = (ActivityManagerService)
              ServiceManager.getService("activity");
              for (int i = 0; i < size; i++) {
                  UnmountCallBack ucb = mForceUnmounts.get(i);
if((ucb.path!=null) &&(ucb.path.equals(mExternalStoragePath))){
            mUpdatingStatus = false;
}
                  String path = ucb.path;
                  boolean done = false;
                  // 传过来的值为 true ,看 【2.0.1】 第二个参数
                  if (!ucb.force) {
                      done = true;
                  } else {
                      //获取到所有有权限访问该设备的用户
                      int pids[] = getStorageUsers(path);
                      if (pids == null || pids.length == 0) {
                          done = true;
                      } else {
                          // Eliminate system process here?
                          ams.killPids(pids, "unmount media", true);
                          // Confirm if file references have been freed.
                          pids = getStorageUsers(path);
                          if (pids == null || pids.length == 0) {
                              done = true;
                          }
                      }
                  }
                  if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
                      // Retry again
                      //保证卸载的时候,没有人进行写入或读取等其他的操作
                      Slog.i(TAG, "Retrying to kill storage users again");
                      mHandler.sendMessageDelayed(
                              mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
                                      ucb.retries++),
                              RETRY_UNMOUNT_DELAY);
                  } else {
                      if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
                          Slog.i(TAG, "Failed to unmount media inspite of " +
                                  MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
                      }
                      sizeArr[sizeArrN++] = i;
                      // 到这儿时,已经没有人来访问我们的设备了
                      // 看代码 【2.0.5】
                      mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
                              ucb));
                  }
              }
              // Remove already processed elements from list.
              for (int i = (sizeArrN-1); i >= 0; i--) {
                  mForceUnmounts.remove(sizeArr[i]);
              }
              break;
          }

//代码【2.0.5】

case H_UNMOUNT_MS: {
    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
    //看代码【2.0.6】
    ucb.handleFinished();
    break;
}

//代码【2.0.6】
//这就是 ucb 对象,就是我们注册回调的那个类

class UnmountCallBack {
    final String path;
    final boolean force;
    final boolean removeEncryption;
    int retries;

    UnmountCallBack(String path, boolean force, boolean removeEncryption) {
        retries = 0;
        this.path = path;
        this.force = force;
        this.removeEncryption = removeEncryption;
    }
    //调用的就是这个方法
    void handleFinished() {
        if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
        //Unmounting /mnt/usb_storage/USB_DISK1 这是打出来的log,开始真正的卸载 usb 设备
        // 继续看 代码【2.0.7】
        doUnmountVolume(path, true, removeEncryption);
    }
}

//代码【2.0.7】
// 为了查看方便我们把参数值列出来, 第二个参数 是 true ,第三个参数 为 false ,

private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
    // 如果现在不是挂载状态,我们就不做处理
    // 这是为了防止我们在卸载过程中直接移除设备
    if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
        return VoldResponseCode.OpFailedVolNotMounted;
    }
    //AssetManager 为应用程序资源管理器,想了解的同学可以后续去深入了解
    /*
     * Force a GC to make sure AssetManagers in other threads of the
     * system_server are cleaned up. We have to do this since AssetManager
     * instances are kept as a WeakReference and it's possible we have files
     * open on the external storage.
     */
    Runtime.getRuntime().gc();

    // Redundant probably. But no harm in updating state again.
    if (path.equals(mExternalStoragePath)) //only flash
    {
       mPms.updateExternalMediaStatus(false, false);
    }
    try {
        final Command cmd = new Command("volume", "unmount", path);
        if (removeEncryption) {
            cmd.appendArg("force_and_revert");
        } else if (force) {
            cmd.appendArg("force");
        }
        // 执行卸载 继续看代码【3.0.1】
        mConnector.execute(cmd);
        // We unmounted the volume. None of the asec containers are available now.
        synchronized (mAsecMountSet) {
            mAsecMountSet.clear();
        }
        return StorageResultCode.OperationSucceeded;
    } catch (NativeDaemonConnectorException e) {
        // Don't worry about mismatch in PackageManager since the
        // call back will handle the status changes any way.
        int code = e.getCode();
        if (code == VoldResponseCode.OpFailedVolNotMounted) {
            return StorageResultCode.OperationFailedStorageNotMounted;
        } else if (code == VoldResponseCode.OpFailedStorageBusy) {
            return StorageResultCode.OperationFailedStorageBusy;
        } else {
            return StorageResultCode.OperationFailedInternalError;
        }
    }
}

NativeDaemonConnector.java

//代码【3.0.1】

public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
    //继续看代码【3.0.2】
    return execute(cmd.mCmd, cmd.mArguments.toArray());
}

//代码【3.0.2】

public NativeDaemonEvent execute(String cmd, Object... args)
        throws NativeDaemonConnectorException {
    //看代码 【3.0.3】
    final NativeDaemonEvent[] events = executeForList(cmd, args);
    if (events.length != 1) {
        throw new NativeDaemonConnectorException(
                "Expected exactly one response, but received " + events.length);
    }
    return events[0];
}

//代码【3.0.3】

public NativeDaemonEvent[] executeForList(String cmd, Object... args)
        throws NativeDaemonConnectorException {
        //参数 DEFAULT_TIMEOUT 
        //int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */ 
        // 继续看 代码 【3.0.4】
        return execute(DEFAULT_TIMEOUT, cmd, args);
}

// 代码【3.0.4】,终于,这段代码似乎做了好多事儿,让我们来一起分析一下

public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
        throws NativeDaemonConnectorException {
    final long startTime = SystemClock.elapsedRealtime();
    final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
    final StringBuilder rawBuilder = new StringBuilder();
    final StringBuilder logBuilder = new StringBuilder();
    final int sequenceNumber = mSequenceNumber.incrementAndGet();
    // 这儿就是对我们传过来的数据做一个处理
    makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);

    final String rawCmd = rawBuilder.toString();
    final String logCmd = logBuilder.toString();

    // 打出来的log
    // rawCmd := 17 volume unmount /mnt/usb_storage/USB_DISK1 force纮   
    // logCmd := 17 volume unmount /mnt/usb_storage/USB_DISK1 force
    log("SND -> {" + logCmd + "}");
    // 加锁,对写数据做一个保护
    synchronized (mDaemonLock) {
        if (mOutputStream == null) {
            throw new NativeDaemonConnectorException("missing output stream");
        } else {
            try {
                //这儿应该就是真正搞事儿的地方了
                //我们发现是通过流的方式写数据,写到哪儿了呢?我们就要去看 mOutputStream是从哪儿来的
                // 看代码及分析 【3.0.5】
                mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new NativeDaemonConnectorException("problem sending command", e);
            }
        }
    }
    // 后边就是对异常的一些处理,可以不用在意,所以在这儿我暂时省去了
    ......
}

//代码及分析 【3.0.5】
// 这一段主要告诉大家 我们上一步的 mOutputStream 是从哪儿来的,大家不要跑偏

private void listenToSocket() throws IOException {
    LocalSocket socket = null;
    try {
    //我们需要去了解的就是这几步
    //start
        socket = new LocalSocket();
        //看一下 determineSocketAddress() 代码【3.0.6】
        LocalSocketAddress address = determineSocketAddress();
        // 建立连接
        socket.connect(address);
        // 获取到 输入流
        InputStream inputStream = socket.getInputStream();
        synchronized (mDaemonLock) {
           // 获取到我们需要的输出流,写数据
           // 看代码【4.0.1】
            mOutputStream = socket.getOutputStream();
        }
    //end
    // 死循环去监听某些数据的变化
    } catch (IOException ex) {
      // 回收我们创建的一些对象等。。。
    }
}

// 代码【3.0.6】

private LocalSocketAddress determineSocketAddress() {
// 这个mSocket 是一个 String 类型的,是在创建 NativeDaemonConnector 对象的时候从  MountService.java 中传 // 过来的,值为 “vold” (注意不是 void) ,
// vold : 管理和控制Android平台外部存储设备,包括SD插拨、挂载、卸载、格式化等
    if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
        return new LocalSocketAddress(mSocket);
    } else {
        //这两个值分别为  “vold” 和 1
        return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
    }
}
// 发现这一段就是通过 jni 还有一些特定的参数 与底层建立连接
// 我们对此不再深究,毕竟我们今天的目的不再这儿。我们直接看 socket.getOutputStream();

LocalSocket.java

//代码【4.0.1】

public OutputStream getOutputStream() throws IOException {
    implCreateIfNeeded();
    // 继续追代码 【5.0.1】
    return impl.getOutputStream();
}

LocalSocketImpl.java

//代码【5.0.1】

protected OutputStream getOutputStream() throws IOException
{ 
    if (fd == null) {
        throw new IOException("socket not created");
    }
    synchronized (this) {
        if (fos == null) {
            // 找到了,我们最后调用的就是这个对象 SocketOutputStream
            // 那么这个对象在哪儿呢?我们继续往下看 【5.0.2】
            fos = new SocketOutputStream();
        }
        return fos;
    }
}

// 代码【5.0.2】 这个类就在 LocalSocketImpl.java 里边

// 看这段代码,很清楚,我们调用的 write() 方法就在这儿了
// 回头再看,我们的write 方法。
// mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
// 调用的是一个参数的 方法,我们在这儿贴出 rawCmd 的值,方便分析
// rawCmd := 17 volume unmount /mnt/usb_storage/USB_DISK1 force纮
class SocketOutputStream extends OutputStream {
    /** {@inheritDoc} */
    @Override
    public void close() throws IOException {
        LocalSocketImpl.this.close();
    }
    /** {@inheritDoc} */
    @Override
    public void write (byte[] b) throws IOException {
        write(b, 0, b.length);
    }
    /** {@inheritDoc} */
    @Override
    public void write (byte[] b, int off, int len) throws IOException {
        synchronized (writeMonitor) {
            // 重点看一下这个 fd
            FileDescriptor myFd = fd;
            if (myFd == null) throw new IOException("socket closed");
            if (off < 0 || len < 0 || (off + len) > b.length ) {
                throw new ArrayIndexOutOfBoundsException();
            }
            // 调用到了这个地方
            // jni 的常用伎俩 我们继续【5.0.3】
            writeba_native(b, off, len, myFd);
        }
    }
    /** {@inheritDoc} */
    @Override
    public void write (int b) throws IOException {
        synchronized (writeMonitor) {
            FileDescriptor myFd = fd;
            if (myFd == null) throw new IOException("socket closed");
            write_native(b, myFd);
        }
    }
    /**
     * Wait until the data in sending queue is emptied. A polling version
     * for flush implementation.
     * @throws IOException
     *    if an i/o error occurs.
     */
    @Override
    public void flush() throws IOException {
        FileDescriptor myFd = fd;
        if (myFd == null) throw new IOException("socket closed");
        while(pending_native(myFd) > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ie) {
                return;
            }
        }
    }
}

// 代码【5.0.3】

private native void writeba_native(byte[] b, int off, int len,
        FileDescriptor fd) throws IOException;

// 继续

android_net_LocalSockedImpl.cpp (/frameworks/base/core/jni/)

{"writeba_native", "([BIILjava/io/FileDescriptor;)V", (void*) socket_writeba},

//我们继续找 socket_writeba 方法【6.0.1】
//代码【6.0.1】

// 参数 buffer  b  off  0  len  b.length  fileDescriptor fd 
static void socket_writeba (JNIEnv *env, jobject object,
        jbyteArray buffer, jint off, jint len, jobject fileDescriptor)
{
    int fd;
    int err;
    jbyte* byteBuffer;
    if (fileDescriptor == NULL || buffer == NULL) {
        jniThrowNullPointerException(env, NULL);
        return;
    }
    if (off < 0 || len < 0 || (off + len) > env->GetArrayLength(buffer)) {
        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
        return;
    }
    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);// 获得对应的文件描述符
    if (env->ExceptionOccurred() != NULL) {
        return;
    }
    byteBuffer = env->GetByteArrayElements(buffer,NULL);//申请内存存放 buffer
    if (NULL == byteBuffer) {
        // an exception will have been thrown
        return;
    }
    err = socket_write_all(env, object, fd,
            byteBuffer + off, len);// 把我们需要写的值写入对应的文件(由文件描述符决定)
    // A return of -1 above means an exception is pending
    env->ReleaseByteArrayElements(buffer, byteBuffer, JNI_ABORT);//将申请的内存释放掉
}

//查看 socket_write_all() 方法

static int socket_write_all(JNIEnv *env, jobject object, int fd,
        void *buf, size_t len)
{
    // 这个方法其实就是个写入的过程
    ssize_t ret;
    struct msghdr msg;
    unsigned char *buffer = (unsigned char *)buf;
    memset(&msg, 0, sizeof(msg));
    jobjectArray outboundFds
            = (jobjectArray)env->GetObjectField(
                object, field_outboundFileDescriptors);
    if (env->ExceptionOccurred() != NULL) {
        return -1;
    }
    struct cmsghdr *cmsg;
    int countFds = outboundFds == NULL ? 0 : env->GetArrayLength(outboundFds);
    int fds[countFds];
    char msgbuf[CMSG_SPACE(countFds)];
    // Add any pending outbound file descriptors to the message
    if (outboundFds != NULL) {
        if (env->ExceptionOccurred() != NULL) {
            return -1;
        }
        for (int i = 0; i < countFds; i++) {
            jobject fdObject = env->GetObjectArrayElement(outboundFds, i);
            if (env->ExceptionOccurred() != NULL) {
                return -1;
            }
            fds[i] = jniGetFDFromFileDescriptor(env, fdObject);
            if (env->ExceptionOccurred() != NULL) {
                return -1;
            }
        }
        // See "man cmsg" really
        msg.msg_control = msgbuf;
        msg.msg_controllen = sizeof msgbuf;
        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof fds);
        memcpy(CMSG_DATA(cmsg), fds, sizeof fds);
    }
    // We only write our msg_control during the first write
    while (len > 0) {
        struct iovec iv;
        memset(&iv, 0, sizeof(iv));
        iv.iov_base = buffer;
        iv.iov_len = len;
        msg.msg_iov = &iv;
        msg.msg_iovlen = 1;
        do {
            ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
        } while (ret < 0 && errno == EINTR);
        if (ret < 0) {
            jniThrowIOException(env, errno);
            return -1;
        }
        buffer += ret;
        len -= ret;
        // Wipes out any msg_control too
        memset(&msg, 0, sizeof(msg));
    }
    return 0;
}

到这儿,整个过程的分析就结束了,以代码为主,可能过程比较枯燥。但是成长毕竟要经历这个过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值