[原]Android 插件 根据布局xml自动生成ViewHolder 技术分享(二)

小鸥有感上篇分享写太罗嗦, 这篇直接的, 来干货.


编写一个能实现Android布局xml自动转ViewHolder的插件, 我们需要

  1. 一个eclipse插件工程入口. 这里baidu,google. 可以参考EasyExploror插件, 下载下来反编译下. 

    小鸥也是第一次编写eclipse插件, 难度不大. 小鸥捣鼓这个自动生成ViewHolder的插件花了大半天. 好在最近好闲.


  2. 我们需要读取布局xml里的元素, View和它对应的id, 用的dom4j.

  3. 我们需要根据当前工程的AndroidManifest.xml, 读取package.

  4. 代码的自动生成, 简单的点, 就用freeMarker吧. 遵照Android规则, 把文件生成到gen文件夹吧.


上代码- -


如果我们有了工程的目录和当前选择的文件, 那么执行这个接口.

/**
 * @author zju_wjf
 *
 */
public interface IActionHandler {
	/**
	 * @param projectDir
	 * @param filePath
	 */
	public void handle(String projectDir, String filePath);
}


上面接口的实现

public class XML2JFileHandler implements IActionHandler {

	private final static String AndroidManifest_Tag = "AndroidManifest.xml";
	private final static String AndroidGen_Tag = "gen";
	private final static String XML_Subfix_Tag = ".xml";
	private final static String Java_Subfix_Tag = ".java";

	private final IPlugLogger plugLogger;

	public XML2JFileHandler(IPlugLogger logger) {
		this.plugLogger = logger;
	}

	@Override
	public void handle(String projectDir, String filePath) {
		if (checkValid(projectDir, filePath)) {
			final String genPath = getGenPath(projectDir);
			final String mainPackage = getMainPackage(projectDir);

			final String layoutName = getLayoutName(filePath);
			final String className = getClassName(filePath);

			final File outputDir = new File(new File(genPath), mainPackage.replace(".", "//"));
			outputDir.mkdirs();
			
			final File outFile = new File(outputDir, className + Java_Subfix_Tag);

			try {
				LayoutJFileWriter.writeJFile(new FileInputStream(filePath), new FileOutputStream(outFile), className, layoutName, mainPackage);

				plugLogger.log("Convert To ViewHolder Successfully!");

			} catch (FileNotFoundException e) {
				e.printStackTrace();
				plugLogger.log(e.getMessage());
			} catch (IOException e) {
				e.printStackTrace();
				plugLogger.log(e.getMessage());
			} catch (TemplateException e) {
				e.printStackTrace();
				plugLogger.log(e.getMessage());
			}

		} else {
			plugLogger.log("Convert To ViewHolder Failed!");
		}
	}

	private String getGenPath(String projectDir) {
		if (projectDir != null) {
			final File file = new File(new File(projectDir), AndroidGen_Tag);
			if (file.exists()) {
				return file.getAbsolutePath();
			}
		}
		return null;
	}

	private String getLayoutName(final String filePath) {
		if (filePath != null) {
			final File file = new File(filePath);
			if (file.exists()) {
				final String name = file.getName();
				return name.substring(0, name.length() - XML_Subfix_Tag.length());
			}
		}
		return null;
	}

	private String getClassName(final String filePath) {
		final String layoutName = getLayoutName(filePath);
		return StringUtils.toUpperCase(layoutName, 0);
	}

	private String getMainPackage(String projectDir) {
		final String androidManifestPath = getAndroidManifest(projectDir);
		if (androidManifestPath != null) {
			/***
			 * <manifest
			 * xmlns:android="http://schemas.android.com/apk/res/android"
			 * package="com.yangfuhai.asimplecachedemo" android:versionCode="1"
			 * android:versionName="1.0" >
			 */
			try {
				return LayoutXMLReader.readMainPackage(new FileInputStream(androidManifestPath));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	private boolean checkValid(final String projectDir, String filePath) {
		if (projectDir != null && filePath != null) {
			final File dir = new File(projectDir);
			final File file = new File(filePath);
			if (dir.isDirectory() && file.isFile()) {
				if (isAndroidProject(projectDir)) {
					if (filePath.endsWith(XML_Subfix_Tag) && filePath.startsWith(projectDir)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private String getAndroidManifest(final String projectDir) {
		if (projectDir != null) {
			final File file = new File(new File(projectDir), AndroidManifest_Tag);
			if (file.exists()) {
				return file.getAbsolutePath();
			}
		}
		return null;
	}

	private boolean isAndroidProject(final String projectDir) {
		if (getAndroidManifest(projectDir) == null) {
			plugLogger.log("Not Found AndroidManifest!");
			return false;
		}
		if (getMainPackage(projectDir) == null) {
			plugLogger.log("Not Found Main Package!");
			return false;
		}
		if (getLayoutName(projectDir) == null) {
			plugLogger.log("Not A Vaild Layout XML!");
			return false;
		}
		if (getClassName(projectDir) == null) {
			plugLogger.log("Not A Valid Class Name!");
			return false;
		}
		if (getGenPath(projectDir) == null) {
			plugLogger.log("Not A Valid gen Directory!");
			return false;
		}
		return true;
	}

}



我们需要根据xml文件读取我们感兴趣的信息

/**
 * @author zju_wjf
 * 
 */
public class LayoutXMLReader {
	private static final String Android_Id_Tag = "id";
	private static final String Id_Start_Tag = "@+id/";
	private static final String Package_Tag = "package";

	/**
	 * @param inputStream
	 * @return
	 */
	public static String readMainPackage(final InputStream inputStream) {

		SAXReader saxReader = new SAXReader();
		Document document = null;
		try {
			document = saxReader.read(inputStream);
			final Element element = document.getRootElement();
			final String mainPackage = element.attributeValue(Package_Tag);

			return mainPackage;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * @param inputStream
	 * @return
	 */
	public static List<ViewPairBean> readLayoutXML(final InputStream inputStream) {
		final LinkedList<ViewPairBean> pairs = new LinkedList<ViewPairBean>();

		SAXReader saxReader = new SAXReader();
		Document document = null;
		try {
			document = saxReader.read(inputStream);
			final Element element = document.getRootElement();

			readElement(element, pairs);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return pairs;
	}

	/**
	 * 读取当前Element及其所有只节点的信息
	 * 
	 * @param element
	 * @param pairs
	 */
	private final static void readElement(final Element element, final List<ViewPairBean> pairs) {
		if (element != null) {
			readElementOne(element, pairs);

			@SuppressWarnings("unchecked")
			List<Element> elements = element.elements();
			for (Element childElement : elements) {
				readElement(childElement, pairs);
			}
		}
	}

	/**
	 * 只读取当前Element信息
	 * 
	 * @param element
	 * @param pairs
	 */
	private final static void readElementOne(final Element element, final List<ViewPairBean> pairs) {
		if (element != null) {

			final String id = element.attributeValue(Android_Id_Tag);
			if (id != null && id.startsWith(Id_Start_Tag)) {
				final ViewPairBean newPair = new ViewPairBean();

				final String eleName = element.getName();

				// 首字母大写
				newPair.setViewType(StringUtils.toUpperCase(eleName, 0));
				newPair.setViewId(id.substring(Id_Start_Tag.length()));

				if (newPair.getViewType() != null && newPair.getViewId() != null) {
					pairs.add(newPair);
				}
			}
		}
	}
}



有了信息, 我们用freemarker生成java文件

public class LayoutJFileWriter {
	
	private final static String Package_Tag = "package";
	private final static String Class_Tag = "class";
	private final static String Layout_Tag = "layout";
	private final static String ImportList_Tag = "importList";
	private final static String PairList_Tag = "pairList";
	
	private final static String TemplateDir = "template/";
	private final static String TemplateFile = "ViewSetTemplate.tl";

	public static void writeJFile(final InputStream layoutStream, final OutputStream jFielStream, final String className, final String layoutName, final String mainPackage) throws IOException, TemplateException {

		Configuration cfg = new Configuration();
		cfg.setClassForTemplateLoading(LayoutJFileWriter.class, TemplateDir);
		cfg.setObjectWrapper(new DefaultObjectWrapper());

		Template temp = cfg.getTemplate(TemplateFile);

		final String packageValue = mainPackage;

		final List<String> importList = new LinkedList<String>();

		final String classValue = className;

		final List<ViewPairBean> pairList = LayoutXMLReader.readLayoutXML(layoutStream);
		
		final String layoutValue = layoutName;

		Map<String, Object> root = new HashMap<String, Object>();
		root.put(Package_Tag, packageValue);
		root.put(Class_Tag, classValue);
		root.put(Layout_Tag, layoutValue);

		root.put(ImportList_Tag, importList);
		root.put(PairList_Tag, pairList);

		Writer out = new OutputStreamWriter(jFielStream);
		temp.process(root, out);
		out.flush();
		out.close();
	}
}



我们的freeMarker模板文件

package ${package};

import com.androidtools.viewholder.AbstractViewHolder;
import com.androidtools.viewholder.ViewHolderInject;
<#list importList as one>
import ${one};
</#list>

public class ${class} extends AbstractViewHolder{

  <#list pairList as prop>
  	@ViewHolderInject(id = R.id.${prop.viewId})
	public ${prop.viewType} ${prop.viewId};
  </#list>
	
	public final int getId() {
		return R.layout.${layout};
	}
}



好了, 最后上git地址, 大家可以自定义修改,或者就直接用我生成的jar, 放在eclipse/plugins/下直接使用.

https://github.com/zjuwjf/AndroidTool.git


你可能感兴趣的:([原]Android 插件 根据布局xml自动生成ViewHolder 技术分享(二))