Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c4239ac4 authored by Eric Holk's avatar Eric Holk
Browse files

Prototype XML view compiler

This is an initial step towards a tool for pre-compiling layout XML files.  It
accepts an XML file and produces a Java language class called CompiledView with
a static method, inflate. Calling CompiledView.inflate should then return a view
object that is equivalent to calling LayoutInflater.inflate on the same
resource.

There are still several important limitations, but this works well enough to do
some experimentation. The limitations include:
* Currently only one layout can be compiled at a time.
* `merge` and `include` nodes are not supported.
* View compilation is a manual process that requires code changes in the
  application.
* The tests in this CL do not yet exercise any interesting behavior.

Bug: 111895153
Change-Id: I3e6880b08c52087d24ae7486495bd7fa282f4ff7
parent 53f6d1b0
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

cc_library_host_static {
    name: "libviewcompiler",
    srcs: [
        "java_lang_builder.cc",
        "util.cc",
    ],
    static_libs: [
        "libbase"
    ]
}

cc_binary_host {
    name: "viewcompiler",
    srcs: [
        "main.cc",
    ],
    static_libs: [
        "libbase",
        "libtinyxml2",
        "libgflags",
        "libviewcompiler",
    ],
}

cc_test_host {
    name: "view-compiler-tests",
    srcs: [
        "util_test.cc",
    ],
    static_libs: [
        "libviewcompiler",
    ]
}
+25 −0
Original line number Diff line number Diff line
# View Compiler

This directory contains an experimental compiler for layout files.

It will take a layout XML file and produce a CompiledLayout.java file with a
specialized layout inflation function.

To use it, let's assume you had a layout in `my_layout.xml` and your app was in
the Java language package `com.example.myapp`. Run the following command:

    viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java

This will produce a `CompiledView.java`, which can then be compiled into your
Android app. Then to use it, in places where you would have inflated
`R.layouts.my_layout`, instead call `CompiledView.inflate`.

Precompiling views like this generally improves the time needed to inflate them.

This tool is still in its early stages and has a number of limitations.
* Currently only one layout can be compiled at a time.
* `merge` and `include` nodes are not supported.
* View compilation is a manual process that requires code changes in the
  application.
* This only works for apps that do not use a custom layout inflater.
* Other limitations yet to be discovered.
+7 −0
Original line number Diff line number Diff line
{
  "presubmit": [
    {
      "name": "view-compiler-tests"
    }
  ]
}
+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "java_lang_builder.h"

#include "android-base/stringprintf.h"

using android::base::StringPrintf;
using std::string;

void JavaLangViewBuilder::Start() const {
  out_ << StringPrintf("package %s;\n", package_.c_str())
       << "import android.content.Context;\n"
          "import android.content.res.Resources;\n"
          "import android.content.res.XmlResourceParser;\n"
          "import android.util.AttributeSet;\n"
          "import android.util.Xml;\n"
          "import android.view.*;\n"
          "import android.widget.*;\n"
          "\n"
          "public final class CompiledView {\n"
          "\n"
          "static <T extends View> T createView(Context context, AttributeSet attrs, View parent, "
          "String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {"
          "\n"
          "  if (factory2 != null) {\n"
          "    return (T)factory2.onCreateView(parent, name, context, attrs);\n"
          "  } else if (factory != null) {\n"
          "    return (T)factory.onCreateView(name, context, attrs);\n"
          "  }\n"
          // TODO: find a way to call the private factory
          "  return null;\n"
          "}\n"
          "\n"
          "  public static View inflate(Context context) {\n"
          "    try {\n"
          "      LayoutInflater inflater = LayoutInflater.from(context);\n"
          "      LayoutInflater.Factory factory = inflater.getFactory();\n"
          "      LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n"
          "      Resources res = context.getResources();\n"
       << StringPrintf("      XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n",
                       package_.c_str(),
                       layout_name_.c_str())
       << "      AttributeSet attrs = Xml.asAttributeSet(xml);\n"
          // The Java-language XmlPullParser needs a call to next to find the start document tag.
          "      xml.next(); // start document\n";
}

void JavaLangViewBuilder::Finish() const {
  out_ << "    } catch (Exception e) {\n"
          "      return null;\n"
          "    }\n"  // end try
          "  }\n"    // end inflate
          "}\n";     // end CompiledView
}

void JavaLangViewBuilder::StartView(const string& class_name) {
  const string view_var = MakeVar("view");
  const string layout_var = MakeVar("layout");
  std::string parent = "null";
  if (!view_stack_.empty()) {
    const StackEntry& parent_entry = view_stack_.back();
    parent = parent_entry.view_var;
  }
  out_ << "      xml.next(); // <" << class_name << ">\n"
       << StringPrintf("      %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n",
                       class_name.c_str(),
                       view_var.c_str(),
                       parent.c_str(),
                       class_name.c_str())
       << StringPrintf("      if (%s == null) %s = new %s(context, attrs);\n",
                       view_var.c_str(),
                       view_var.c_str(),
                       class_name.c_str());
  if (!view_stack_.empty()) {
    out_ << StringPrintf("      ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n",
                         layout_var.c_str(),
                         parent.c_str());
  }
  view_stack_.push_back({class_name, view_var, layout_var});
}

void JavaLangViewBuilder::FinishView() {
  const StackEntry var = view_stack_.back();
  view_stack_.pop_back();
  if (!view_stack_.empty()) {
    const string& parent = view_stack_.back().view_var;
    out_ << StringPrintf("      xml.next(); // </%s>\n", var.class_name.c_str())
         << StringPrintf("      %s.addView(%s, %s);\n",
                         parent.c_str(),
                         var.view_var.c_str(),
                         var.layout_params_var.c_str());
  } else {
    out_ << StringPrintf("      return %s;\n", var.view_var.c_str());
  }
}

const std::string JavaLangViewBuilder::MakeVar(std::string prefix) {
  std::stringstream v;
  v << prefix << view_id_++;
  return v.str();
}
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef JAVA_LANG_BUILDER_H_
#define JAVA_LANG_BUILDER_H_

#include <iostream>
#include <sstream>
#include <vector>

// Build Java language code to instantiate views.
//
// This has a very small interface to make it easier to generate additional
// backends, such as a direct-to-DEX version.
class JavaLangViewBuilder {
 public:
  JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout)
      : package_(package), layout_name_(layout_name), out_(out) {}

  // Begin generating a class. Adds the package boilerplate, etc.
  void Start() const;
  // Finish generating a class, closing off any open curly braces, etc.
  void Finish() const;

  // Begin creating a view (i.e. process the opening tag)
  void StartView(const std::string& class_name);
  // Finish a view, after all of its child nodes have been processed.
  void FinishView();

 private:
  const std::string MakeVar(std::string prefix);

  std::string const package_;
  std::string const layout_name_;

  std::ostream& out_;

  size_t view_id_ = 0;

  struct StackEntry {
      // The class name for this view object
      const std::string class_name;

      // The variable name that is holding the view object
      const std::string view_var;

      // The variable name that holds the object's layout parameters
      const std::string layout_params_var;
  };
  std::vector<StackEntry> view_stack_;
};

#endif  // JAVA_LANG_BUILDER_H_
Loading