作一個幫你快速調試UI參數的Android插件

本文會介紹一個幫助咱們快速調試UI參數的插件開發過程以及開發思路,可能須要一些簡單的Idea平臺插件開發經驗,但願對你們會有一些幫助。html

插件介紹

插件基於Layout Inspector,強化了這個工具,故取名Layout Masterjava

使用方式同Layout Inspector,呼出Android Studio(3.1以上)或Idea(2017.3以上)的Action面板,輸入Layout Master點擊便可,雙擊Property,支持修改的話會彈出Popup,同Layout Inspector同樣,每次Activity重啓了就須要再次運行Layout Master纔可。android

image

插件效果以下(圖中僅演示了部分屬性修改效果,支持不少屬性) git

LayoutMaster

項目Github地址:github.com/wuapnjie/La…github

爲何要作這個插件

我在平時的Android開發過程當中,會常常修改一些UI的參數,好比padding,margin,color等等,有時View是經過非XML代碼動態注入的,不少參數設置在真機調試時才能看到(並且我是那種必定要看真機跑效果的人),因此不少時候效果不滿意就要改參數繼續看效果,設計師們也會常常讓咱們改一些UI上的參數,這樣每次都要從新編譯運行一次代碼,或者Instant Run一下,項目小還好,項目一大,這個從新編譯運行的時間成本就會很大,大大下降了開發效率。因而我決定開發這個插件,快速看到UI參數改變的效果。數據庫

插件的簡單原理介紹

不一樣於React Native和Flutter這些框架實現的熱加載(哈哈,其實我也不知道這些框架怎麼實現的),這個插件對View的參數實時設置都是經過Java反射調用View自身的setXXX()方法實現的,因此只能看效果,代碼本質上沒有改變,須要達到滿意效果後再去修改,但這仍是大大節省了時間,至少對我來講是。網絡

那要怎麼樣作到從電腦端(IDE端)調用APP上View的setXXX()方法呢?很簡單,讓手機與電腦之間進行一個Socket長鏈接,定義一些命令協議,就能夠實現電腦端對手機端的控制。app

實現思路與過程

最初的思考

首先要實現想要的功能,第一步就是創建手機端與電腦端的Socket長鏈接並拿到關於Activity的View Hierarchy和View的Properties。這樣的功能我在兩個地方看到過,一個是Facebook強大的調試框架Stetho,另一個就是Android Studio自帶的Layout Inspector。這兩個工具都與手機端創建了一個Socket長鏈接,創建了本身的通訊協議。下面我會簡單介紹一下二者的區別,並解釋了爲何我選擇基於Layout Inspector作一個插件,而不是基於Stetho作一個代碼擴展。框架

Stetho

image

Stetho這個項目功能十分強大,不光能夠看到View Hierarchy,還能夠調試數據庫,監測網絡等等,實現上和我以前介紹的同樣,創建了一個Socket長鏈接,APP負責獲取須要的數據,經過Socket傳輸到Chrome DevTools,這裏Chrome DevTools有一個開發API,接收到特定的Json,會進行渲染顯示,在DevTools的操做也會Json格式包裝成特定的數據包發送給APP進行操做。因爲Stetho的代碼比較複雜,我沒有對其深刻研究,也不瞭解Chrome DevTools的API,但大體原理已經介紹了,若是你感興趣或有什麼想法,能夠去研究研究。ide

Layout Inspector

image

一樣,Layout Inspector也是經過Socket長鏈接來獲取APP的相關UI信息,因爲Idea的社區版代碼是開源的,而做爲Android插件的Layout Inspector代碼也是開源,具體能夠編譯Idea項目查看,代碼入口在android插件AndroidRunLayoutInspectorAction.java類中。

二者差異

Stetho的Socket鏈接相關代碼是寫在它的庫中的,須要調試的APP依賴這個項目,進行一些配置,侵入性較強,但功能強大。而Layout Inspector則對代碼零侵入,那它是怎麼實現Socket長鏈接的呢?其實咱們在調試時,一直有一個長鏈接鏈接着電腦,那就是ADB,ADB工具在電腦端創建了一個Socket服務端,鏈接着全部開啓了USB調試模式的手機客戶端,因此全部咱們調試的應用均可以使用Layout Inspector工具。

因此我選擇了基於Layout Inspector製做了一款插件,代碼零侵入,使用方便簡單,並且Android SDK中和Idea中已經幫我作好了不少代碼工做,實現起來簡單,接下來我會介紹。

Layout Inspector分析

要基於Layout Inspector作,勢必要對這個工具的實現過程有了解,這裏我簡單分析一下它的源碼,同時也會涉及到Android SDK中的一個類ViewDebug

Action入口

作過Idea插件開發的同窗確定都知道Idea的Action系統,不少咱們進行的快捷操做在Idea平臺中是一個個的Action

image

咱們能夠經過這個Action去快速找到它的入口類,上面也介紹了,在AndroidRunLayoutInspectorAction.java

@Override
public void actionPerformed(AnActionEvent e) {
  Project project = e.getProject();
  assert project != null;

  if (!AndroidSdkUtils.activateDdmsIfNecessary(project)) {
    return;
  }

  AndroidProcessChooserDialog dialog = new AndroidProcessChooserDialog(project, false);
  dialog.show();
  if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
    Client client = dialog.getClient();
    if (client != null) {
      new LayoutInspectorAction.GetClientWindowsTask(project, client).queue();
    }
    else {
      Logger.getInstance(AndroidRunLayoutInspectorAction.class).warn("Not launching layout inspector - no client selected");
    }
   }
}
複製代碼

從 入口代碼中能夠看出,咱們要先選一個Process,也就是下面這個界面

image

Window選擇

以後會在Background執行LayoutInspectorAction.GetClientWindowsTask,這個Task會獲取當前活躍的ClientWindow(也就是Android中的Window),若是超過一個的話,會出現對話框讓咱們選擇,這裏就不貼圖了,反正你們都用過。

Capture View

選擇了Window以後就會在Background執行LayoutInspectorCaptureTask,這個Task會獲取到須要顯示的View Hierarchy,View Properties以及一張BufferedImage(選擇Window的截圖),這些信息所有以二進制的信息儲存在.li文件中

image

顯示

而後Layout Inspector自定義了一個FileEditor以支持.li文件的顯示,也就是咱們能看到View Tree和Properties Table的主界面。具體顯示細節可參考LayoutInspectorContext

Android SDK中的響應

上面介紹了Layout Inspector在插件端的簡單流程,它想Android端要了Window信息,View的信息,相關代碼都在HandleViewDebug類,下面是這個類的一些結構

image

也就是說服務端發出了一些命令的包,那做爲客戶端的Android是在哪裏做出響應的呢?通過個人代碼查找,我在Android SDK中發現了一個DdmHandleViewDebug類和ViewDebug

image

從兩個類的structure中就能夠看出,Android端是在ViewDebug這個類獲取各類信息的,具體代碼就不分析了,你們感興趣能夠研究研究。

同時,這個類中有一個註解,叫ExportedProperty

/** * This annotation can be used to mark fields and methods to be dumped by * the view server. Only non-void methods with no arguments can be annotated * by this annotation. */
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportedProperty {
    ……
}
複製代碼

查看這個註解用的地方,能夠發現,全部Layout Inspector中顯示的Properties都被標註了這個註解。

image

經過這個註解咱們能夠給一些自定義的View暴露出想要顯示的屬性。

擴展Layout Inspector

通過上面的對Layout Inspector的分析,咱們已經足夠了解它並能夠對其作擴展了。Layout Inspector只能查看View Hierarchy和Properties,它徹底能夠作更多的事情。

HandleViewDebug類中有一個方法invokeMethod,這個方法能夠作到調用View的相關方法,目前只支持primitive arguments的方法,很惋惜,意味着咱們不能改變TextView的text。

image

觸發的方法在Android SDK的ViewDebuginvokeViewMethod方法中,能夠看到是經過反射實現的,view post出去的

/** * Invoke a particular method on given view. * The given method is always invoked on the UI thread. The caller thread will stall until the * method invocation is complete. Returns an object equal to the result of the method * invocation, null if the method is declared to return void * @throws Exception if the method invocation caused any exception * @hide */
    public static Object invokeViewMethod(final View view, final Method method, final Object[] args) {
        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Object> result = new AtomicReference<Object>();
        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();

        view.post(new Runnable() {
            @Override
            public void run() {
                try {
                    result.set(method.invoke(view, args));
                } catch (InvocationTargetException e) {
                    exception.set(e.getCause());
                } catch (Exception e) {
                    exception.set(e);
                }

                latch.countDown();
            }
        });

        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        if (exception.get() != null) {
            throw new RuntimeException(exception.get());
        }

        return result.get();
    }
複製代碼

接下來就好辦了,核心方法Idea和Android SDK都爲咱們提供好了,咱們只須要構建咱們的插件UI,寫入View的相關方法便可。

因爲咱們須要對View的Property進行操做,因爲負責顯示View Properties的控件是私有的,因此這裏我經過反射獲取了實例,併爲其添加了一個雙擊鼠標事件。

private var propertyTable: PTable

init {
  val editorReflect = Reflect.on(layoutInspectorEditor)
 
  val context = editorReflect.get<LayoutInspectorContext>("myContext")

  propertyTable = context.propertiesTable
  ...
}

...
fun hook() {

  propertyTable.addMouseListener(object : MouseAdapter() {
    ...  
  }
}

複製代碼

雙擊事後就是顯示一個Popup,不一樣的類型顯示不一樣的Popup。

不支持動畫的普通屬性

image

支持動畫的屬性

image

顏色屬性

image

Enum類型的屬性

image

Bitwise類型的屬性

image

自定義的屬性

能夠支持自定義View的自定義的屬性無疑是最棒的,實現起來也很簡單,在介紹ViewDebug類時,介紹了ExportedProperty註解,咱們只須要在自定義的View中運用這個註解就能夠了,並設置好setXXX()方法,一個簡單例子,注意這個category必定要爲custom,插件纔會作出響應,屬性名中帶有color會被認爲是顏色。

public class ColorView extends TextView {

  @ViewDebug.ExportedProperty(category = "custom", formatToHexString = true)
  private int color = Color.BLACK;

  @ViewDebug.ExportedProperty(category = "custom")
  private int number = 0;

  @ViewDebug.ExportedProperty(category = "custom")
  private boolean needShowText = true;

  public ColorView(Context context) {
    super(context);
  }

  public ColorView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
  }

  public ColorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  public void setColor(int color) {
    this.color = color;
    setBackgroundColor(color);
  }

  public void setNeedShowText(boolean needShowText) {
    this.needShowText = needShowText;
    if (!needShowText) {
      setText("");
    } else {
      setText("" + number);
    }
  }

  public void setNumber(int number) {
    this.number = number;
    setText("" + number);
  }

}

複製代碼

image

以後的細節就不具體展開了,畢竟核心原理已經介紹過了。插件代碼開源,感興趣的同窗能夠看看,不要噴我代碼寫的差就行。

結語

若是你們喜歡這個插件,能夠在Android Studio或Idea的插件中心下載使用,喜歡這篇文章能夠給個喜歡,有什麼問題能夠評論或私信我。

image-201804182208549

請給個好評,嘿嘿😜

也能夠直接在這裏下載:github.com/wuapnjie/La…

安裝時不要解壓那個zip包

插件項目Github地址:github.com/wuapnjie/La… 歡迎Star和PR

但願這篇文章能夠對你有什麼幫助,我也會繼續努力~

相關文章
相關標籤/搜索