因为有个 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;
}
到这儿,整个过程的分析就结束了,以代码为主,可能过程比较枯燥。但是成长毕竟要经历这个过程。