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

来源:互联网 发布:数据库关系模型分析 编辑:程序博客网 时间:2024/06/06 03:21

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

这里写图片描述

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

Memory.java

@Overridepublic 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;}

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

阅读全文
0 0
原创粉丝点击