UiautomatorViewer源碼(三):提升快照速度

使用UiautomatorViewer工具快照屏幕時,感覺速度有點慢,所以這邊就想着修改下源碼來提升下速度,準確來說,應該算是換一種方式來快照屏幕。

主要的想法:新增一個按鈕,添加一些按鈕事件(保留了原本的快照功能)

按鈕事件思路:

  1. 1、創建兩個線程,線程A進行dump當前界面的層級結構數據,線程B進行屏幕截圖

  2. 2、從手機內導出線程生成的兩個文件到電腦端

  3. 3、調用工程內UiAutomatorViewer類的.setModel(model,new File(saveDirString+mobileXml), img);方法傳入兩個文件參數,隨後在PC端生成快照。

  4. ps:每次打開uiautomatorViewer工具時,會去清空PC端指定目錄下之前生成的數據。

OK,接下來,差不多到了上代碼的環節。有部分代碼是建立在UiautomatorViewer源碼(一):淺析 UiautomatorViewer源碼(二):持久化 這兩篇文章上的。

第一步:新增一個類,EasyScreenshotAction。

package com.android.uiautomator.actions;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

import com.android.ddmlib.IDevice;
import com.android.uiautomator.DebugBridge;
import com.android.uiautomator.UiAutomatorModel;
import com.android.uiautomator.UiAutomatorViewer;

public class EasyScreenshotAction extends Action {
	
	UiAutomatorViewer mViewer;
	
	
	public EasyScreenshotAction(UiAutomatorViewer viewer) {
		super("&SYNC");
		this.mViewer = viewer;
	}
	
	public ImageDescriptor getImageDescriptor() {
		return ImageHelper.loadImageDescriptorFromResource("images/screenshot.png");
	}
	
	public static String getCurrentTime(){
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");   
		return df.format(System.currentTimeMillis());   
	}
	
	public void run() {
		if(!DebugBridge.isInitialized()) {
			MessageDialog.openError(this.mViewer.getShell(), "Error obtaining Device Screenshot", "Unable to connect to adb. Check if adb is installed correctly.");
		} else {
			File saveDir=new File("C:\\TempForUiautomatorView") ;
			if (!saveDir.exists()) {
				saveDir.mkdirs();
			}
			final String saveDirString = saveDir.getAbsolutePath()+File.separator;
			final IDevice device = this.pickDevice();
			if(device != null) {
				ProgressMonitorDialog dialog = new ProgressMonitorDialog(this.mViewer.getShell());
				try {
					dialog.run(true, false, new IRunnableWithProgress() {
						public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
							String adbHead = "adb ";
							try {
								if (EasyScreenshotAction.DevicePickerDialog.serial ==null || EasyScreenshotAction.DevicePickerDialog.serial.length()<1) {
								}else {
									adbHead="adb -s "+EasyScreenshotAction.DevicePickerDialog.serial+" ";
								}
								String  mobileScreenshot=getCurrentTime()+"_screenshot.png";
								String  mobileXml=getCurrentTime()+"_xml.uix";
								//使用多線程,來達到更快的同步速度
								MyThread screenThread=new MyThread(adbHead+"shell \"/system/bin/screencap -p /data/local/tmp/"+mobileScreenshot+"\"");
								MyThread xmlThread=new MyThread(adbHead+"shell \"uiautomator dump /data/local/tmp/"+mobileXml+"\"");
								monitor.setTaskName("Obtaining device screenshot");
								monitor.subTask("Taking UI XML snapshot...");
								ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());  
								executor.execute(screenThread);
								executor.execute(xmlThread);
								//等待線程池中所有線程執行完畢後,再開始pull操作
								executor.shutdown();
								try {  
									boolean loop = true;  
									do {    //等待所有任務完成  
										loop = !executor.awaitTermination(200, TimeUnit.MILLISECONDS);  //阻塞,直到線程池裏所有任務結束
									} while(loop);  
								} catch (InterruptedException e) {  
									e.printStackTrace();  
								}  
								Runtime.getRuntime().exec(adbHead+"pull /data/local/tmp/"+mobileScreenshot+" "+saveDirString+mobileScreenshot).waitFor();
								monitor.subTask("Obtaining UI hierarchy");
								Runtime.getRuntime().exec(adbHead+"pull /data/local/tmp/"+mobileXml+" "+saveDirString+mobileXml).waitFor();
								UiAutomatorModel model = new UiAutomatorModel(new File(saveDirString+mobileXml));
								Image img = null;
								File screenshot = new File(saveDirString+mobileScreenshot);
								if(screenshot != null) {
									ImageData[] e = (new ImageLoader()).load(screenshot.getAbsolutePath());
									if(e.length < 1) {
										throw new RuntimeException("Unable to load image: " + screenshot.getAbsolutePath());
									}
									img = new Image(Display.getDefault(), e[0]);
								}
								mViewer.setModel(model,new File(saveDirString+mobileXml), img);
								//下次工具重啓後刪除目錄下之前生成的所有文件
//								final String finalMobileScreenshot = mobileScreenshot;
//								final String finalMobileXml = mobileXml;
//								new Thread(new Runnable() {
//									public void run() {
//										if(new File(saveDirString+finalMobileScreenshot).exists()){
//											new File(saveDirString+finalMobileScreenshot).delete();
//										}
//										if(new File(saveDirString+finalMobileXml).exists()){
//											new File(saveDirString+finalMobileXml).delete();
//										}
//									}
//								}).start();
							} catch (Exception var4) {
								monitor.done();
								EasyScreenshotAction.this.showError(var4.getMessage(), var4);
								return;
							}
							monitor.done();
						}
					});
				} catch (Exception var4) {
					this.showError("Unexpected error while obtaining UI hierarchy", var4);
				}
				
			}
		}
	}
	
	private void showError(final String msg, final Throwable t) {
		this.mViewer.getShell().getDisplay().syncExec(new Runnable() {
			public void run() {
				Status s = new Status(4, "Screenshot", msg, t);
				ErrorDialog.openError(EasyScreenshotAction.this.mViewer.getShell(), "Error", "Error obtaining UI hierarchy", s);
			}
		});
	}
	
	private IDevice pickDevice() {
		List<?> devices = DebugBridge.getDevices();
		if(devices.size() == 0) {
			MessageDialog.openError(this.mViewer.getShell(), "Error obtaining Device Screenshot", "No Android devices were detected by adb.");
			return null;
		} else if(devices.size() == 1) {
			return (IDevice)devices.get(0);
		} else {
			EasyScreenshotAction.DevicePickerDialog dlg = new EasyScreenshotAction.DevicePickerDialog(this.mViewer.getShell(), devices);
			return dlg.open() != 0?null:dlg.getSelectedDevice();
		}
	}
	
	private static class DevicePickerDialog extends Dialog {
		
		private final List<?> mDevices;
		private final String[] mDeviceNames;
		private static int sSelectedDeviceIndex;
		private static String serial="";
		
		
		public DevicePickerDialog(Shell parentShell, List<?> devices) {
			super(parentShell);
			this.mDevices = devices;
			this.mDeviceNames = new String[this.mDevices.size()];
			
			for(int i = 0; i < devices.size(); ++i) {
				this.mDeviceNames[i] = ((IDevice)devices.get(i)).getName();
			}
			
		}
		
		protected Control createDialogArea(Composite parentShell) {
			Composite parent = (Composite)super.createDialogArea(parentShell);
			Composite c = new Composite(parent, 0);
			c.setLayout(new GridLayout(2, false));
			Label l = new Label(c, 0);
			l.setText("Select device: ");
			final Combo combo = new Combo(c, 2056);
			combo.setItems(this.mDeviceNames);
			int defaultSelection = sSelectedDeviceIndex < this.mDevices.size()?sSelectedDeviceIndex:0;
			combo.select(defaultSelection);
			sSelectedDeviceIndex = defaultSelection;
			serial = combo.getItem(combo.getSelectionIndex());
			combo.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent arg0) {
					EasyScreenshotAction.DevicePickerDialog.sSelectedDeviceIndex = combo.getSelectionIndex();
					serial = combo.getItem(combo.getSelectionIndex());
//               System.out.println(serial);
				}
			});
			
//         System.out.println(serial);
			return parent;
		}
		
		public IDevice getSelectedDevice() {
			return (IDevice)this.mDevices.get(sSelectedDeviceIndex);
		}
	}
}

class MyThread extends Thread {
	public String cmd="";
	public MyThread(String cmdString) {
		this.cmd=cmdString;
	}
	
	public void run() {
		try {
			Runtime.getRuntime().exec(cmd);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  
		
		
	}
}

第二步:在UiAutomatorViewer類的createContents()方法中添加按鈕

protected Control createContents(Composite parent) {
      Composite c = new Composite(parent, 2048);
      GridLayout gridLayout = new GridLayout(1, false);
      gridLayout.marginWidth = 0;
      gridLayout.marginHeight = 0;
      gridLayout.horizontalSpacing = 0;
      gridLayout.verticalSpacing = 0;
      c.setLayout(gridLayout);
      GridData gd = new GridData(768);
      c.setLayoutData(gd);
      ToolBarManager toolBarManager = new ToolBarManager(8388608);
      toolBarManager.add(new OpenFilesAction(this));
      toolBarManager.add(new ScreenshotAction(this));
      //添加一個按鈕
      toolBarManager.add(new EasyScreenshotAction(this));
      ToolBar tb = toolBarManager.createControl(c);
      tb.setLayoutData(new GridData(768));
      this.mUiAutomatorView = new UiAutomatorView(c, 2048);
      this.mUiAutomatorView.setLayoutData(new GridData(1808));
      return parent;
   }

第三步:編寫按鈕事件

public void run() {
	if(!DebugBridge.isInitialized()) {
		MessageDialog.openError(this.mViewer.getShell(), "Error obtaining Device Screenshot", "Unable to connect to adb. Check if adb is installed correctly.");
	} else {
		File saveDir=new File("C:\\TempForUiautomatorView") ;
		if (!saveDir.exists()) {
			saveDir.mkdirs();
		}
		final String saveDirString = saveDir.getAbsolutePath()+File.separator;
		final IDevice device = this.pickDevice();
		if(device != null) {
			ProgressMonitorDialog dialog = new ProgressMonitorDialog(this.mViewer.getShell());
			try {
				dialog.run(true, false, new IRunnableWithProgress() {
					public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
						String adbHead = "adb ";
						try {
							if (EasyScreenshotAction.DevicePickerDialog.serial ==null || EasyScreenshotAction.DevicePickerDialog.serial.length()<1) {
							}else {
								adbHead="adb -s "+EasyScreenshotAction.DevicePickerDialog.serial+" ";
							}
							String  mobileScreenshot=getCurrentTime()+"_screenshot.png";
							String  mobileXml=getCurrentTime()+"_xml.uix";
							//使用多線程,來達到更快的同步速度
							MyThread screenThread=new MyThread(adbHead+"shell \"/system/bin/screencap -p /data/local/tmp/"+mobileScreenshot+"\"");
							MyThread xmlThread=new MyThread(adbHead+"shell \"uiautomator dump /data/local/tmp/"+mobileXml+"\"");
							monitor.setTaskName("Obtaining device screenshot");
							monitor.subTask("Taking UI XML snapshot...");
							ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());  
							executor.execute(screenThread);
							executor.execute(xmlThread);
							//等待線程池中所有線程執行完畢後,再開始pull操作
							executor.shutdown();
							try {  
								boolean loop = true;  
								do {    //等待所有任務完成  
									loop = !executor.awaitTermination(200, TimeUnit.MILLISECONDS);  //阻塞,直到線程池裏所有任務結束
								} while(loop);  
							} catch (InterruptedException e) {  
								e.printStackTrace();  
							}  
							Runtime.getRuntime().exec(adbHead+"pull /data/local/tmp/"+mobileScreenshot+" "+saveDirString+mobileScreenshot).waitFor();
							monitor.subTask("Obtaining UI hierarchy");
							Runtime.getRuntime().exec(adbHead+"pull /data/local/tmp/"+mobileXml+" "+saveDirString+mobileXml).waitFor();
							UiAutomatorModel model = new UiAutomatorModel(new File(saveDirString+mobileXml));
							Image img = null;
							File screenshot = new File(saveDirString+mobileScreenshot);
							if(screenshot != null) {
								ImageData[] e = (new ImageLoader()).load(screenshot.getAbsolutePath());
								if(e.length < 1) {
									throw new RuntimeException("Unable to load image: " + screenshot.getAbsolutePath());
								}
								img = new Image(Display.getDefault(), e[0]);
							}

                            //調用setModel方法,在工具中生成快照
							mViewer.setModel(model,new File(saveDirString+mobileXml), img);
						} catch (Exception var4) {
							monitor.done();
							EasyScreenshotAction.this.showError(var4.getMessage(), var4);
							return;
						}
						monitor.done();
					}
				});
			} catch (Exception var4) {
				this.showError("Unexpected error while obtaining UI hierarchy", var4);
			}
			
		}
	}
}

第四步:每次啓動時刪除指定文件夾下的舊文件,在UiAutomatorViewer類的Main方法中添加

public static void main(String[] args) {
	   DebugBridge.init();
	   //每次啓動時,都要去檢查是否有舊文件如果有的話,就刪除舊文件
	   File saveDir=new File("C:\\TempForUiautomatorView") ;
	   if (saveDir.exists()) {
		   File TrxFiles[] = saveDir.listFiles();
		   for(File curFile:TrxFiles ){
			   curFile.delete();  
		   }
	   }
	   try {
         UiAutomatorViewer e = new UiAutomatorViewer();
         e.setBlockOnOpen(true);
         e.open();
      } catch (Exception var5) {
         var5.printStackTrace();
      } finally {
         DebugBridge.terminate();
      }
   }

最終效果:

有益效果:

1、快照速度還是有提升的。

2、有部分Android版本這個工具會報java.lang.reflect.InvocationTargetException,使用這種方法後,可以完美規避。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章