做一個幫你快速除錯UI引數的Android外掛

FlyingSnowBean發表於2018-04-14

本文會介紹一個幫助我們快速除錯UI引數的外掛開發過程以及開發思路,可能需要一些簡單的Idea平臺外掛開發經驗,希望對大家會有一些幫助。

外掛介紹

外掛基於Layout Inspector,強化了這個工具,故取名Layout Master

使用方式同Layout Inspector,撥出Android Studio(3.1以上)或Idea(2017.3以上)的Action皮膚,輸入Layout Master點選即可,雙擊Property,支援修改的話會彈出Popup,同Layout Inspector一樣,每次Activity重啟了就需要再次執行Layout Master才可。

image

外掛效果如下(圖中僅演示了部分屬性修改效果,支援很多屬性)

LayoutMaster

專案Github地址:github.com/wuapnjie/La…

為什麼要做這個外掛

我在平時的Android開發過程中,會經常修改一些UI的引數,比如padding,margin,color等等,有時View是通過非XML程式碼動態注入的,很多引數設定在真機除錯時才能看到(而且我是那種一定要看真機跑效果的人),所以很多時候效果不滿意就要改引數繼續看效果,設計師們也會經常讓我們改一些UI上的引數,這樣每次都要重新編譯執行一次程式碼,或者Instant Run一下,專案小還好,專案一大,這個重新編譯執行的時間成本就會很大,大大降低了開發效率。於是我決定開發這個外掛,快速看到UI引數改變的效果。

外掛的簡單原理介紹

不同於React Native和Flutter這些框架實現的熱載入(哈哈,其實我也不知道這些框架怎麼實現的),這個外掛對View的引數實時設定都是通過Java反射呼叫View自身的setXXX()方法實現的,所以只能看效果,程式碼本質上沒有改變,需要達到滿意效果後再去修改,但這還是大大節省了時間,至少對我來說是。

那要怎麼樣做到從電腦端(IDE端)呼叫APP上View的setXXX()方法呢?很簡單,讓手機與電腦之間進行一個Socket長連線,定義一些命令協議,就可以實現電腦端對手機端的控制。

實現思路與過程

最初的思考

首先要實現想要的功能,第一步就是建立手機端與電腦端的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,但大致原理已經介紹了,如果你感興趣或有什麼想法,可以去研究研究。

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包

做一個幫你快速除錯UI引數的Android外掛

外掛專案Github地址:github.com/wuapnjie/La… 歡迎Star和PR

希望這篇文章可以對你有什麼幫助,我也會繼續努力~

做一個幫你快速除錯UI引數的Android外掛

相關文章