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;}
到这儿,整个过程的分析就结束了,以代码为主,可能过程比较枯燥。但是成长毕竟要经历这个过程。
- Android手动卸载usb从上层到底层源码分析
- 电池电量分析 从上层到底层
- Camera从上层APP到底层分析
- Camera从上层APP到底层分析
- [Android] AudioEffect架构:从上层调用到底层音效驱动
- Android call setting 源码分析 从顶层到底层
- 【收藏】Android call setting 源码分析 从顶层到底层
- CAMERA流程:从上层到底层
- Android call setting 源码分析 从顶层到底层(上)
- Android call setting 源码分析 从顶层到底层(下)
- Android call setting 源码分析 从顶层到底层(上)
- Android call setting 源码分析 从顶层到底层(下)
- FM函数从上层到底层调用过程(回顾)
- alarm从上层到底层完整调用流程
- IT从上层到底层是怎样的学习过程
- 从上层APP到底层驱动的调用过程
- [Android源码分析]inquiry result引起的上层变化分析
- 从Android源码层分析acticity的启动顺序
- 关于最新的intel skylake scalable processor 和8路服务器的问题
- Beyond Compare在新窗口比较子文件夹的方法,你了解多少?
- SpringMVC+Spring4+Mybatis3 Maven整合教程
- http get请求发送失败,返回304错误
- 摄像头基础介绍
- Android手动卸载usb从上层到底层源码分析
- Kattis Doors
- 240. Search a 2D Matrix II(divide and conquer)
- Java 源码解析-ThreadLocal
- 缺少jar包报错
- mysql索引总结----mysql 索引类型以及创建
- 持续集成①安装部署jenkins从git获取代码
- 直接运行内存中的代码
- java2