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

Commit 956791ce authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Prototype XML view compiler"

parents 20ecc639 c4239ac4
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