Android開発の自動化







こんにちは、habrozhiteli。 私はAndroid開発者です。 もっと正確に言えば、それさえも。 私は怠け者のAndroid開発者です。 十数件のプロジェクトの過程で、私からの精神的な努力を必要としない日常的および機械的な操作が私を圧迫し始めました。 今、IDEが私のためにそれを行います。





はじめに



現時点では、Android開発者の生活を楽にする方法がいくつかあります。 私にとって最初のものは、ビューを初期化するための注釈を備えたRoboJuiceでした。 一見、すべてがうまく、読みやすさが悪くなることはありません。 問題は、必要な機能を取得するためにサードパーティからアクティビティを継承する必要が生じたときに始まりました。 しかし、すべてのアクティビティはRoboActivityを継承するため、松葉杖のセットになりました。 その後、私はそのようなライブラリに対して健全な懐疑論を開発しました。 このグループには、AndroidAnnotations、ButterKnifeなども含まれます。それらはすべて、コードの上に独自の多くを生成するか、完全に些細なことを行います。 たとえば、ButterKnifeの場合:



@InjectView(R.id.title) TextView title;
      
      





の代わりに



 title = (TextView) findViewById(R.id.title);
      
      





オートコンプリートとクイックフィックスを使用する場合、まったく同じ時間がかかります。 そして、違いがない場合-なぜプロジェクトに別のライブラリを追加するためにもっとお金を払ってください



2つ目はIDEテンプレートの使用( http://habrahabr.ru/post/183502/ )です。個人的には試していませんが、ほとんどの場合、ある程度の習慣の開発が必要です。



その他の解決策



過去1か月、私は1つの質問に苦しめられました。なぜxmlレイアウトからコードを生成するためのスクリプト/プラグイン/ %%オプションを誰もまだ作成していない理由です。 私はこのトピックでグーグルに決めた最初のもの。 私が見つけた唯一のものはhttp://code.google.com/p/android-code-generator-plugin/でした。 xmlを解析し、それに基づいて%name%Activity.javaを作成するEclipse用のシンプルなプラグイン。 開発者にはブログ記事もあります。 それ以外はすべて、ブラウザでの生成、または「どこで入手できますか?」の質問です。



何度か実行してみたところ、私には合わないことがいくつか見つかりました。

-onCreate()で初期化を直接表示します。

-一般設定での手動生成用のパッケージの処方。

-カスタムフォントはすべてのビューに設定されているわけではありません。 デフォルトでは、これはRobotoであるため、アプリケーションはAndroidのすべてのバージョンで同じように見えます。

-アクティビティレベルでのリスナーの実装。 合計数が3〜4を超えると、コードの読み取りが非常に困難になります。

-IDを持つすべてのビューの生成。 一部のコンテナ(LinearLayoutなど)がコードで使用されることはほとんどないため、理由はわかりません。

-説明されたホルダーを持つ単純なArrayAdapterの生成はありません。 物事は非常に簡単ですが、時間がかかります。

-コミットから判断すると、プロジェクトの開発は非常に遅いです。



無駄な検索を再び試みた後、私は自分でそれを書く必要があることに気付きました。



簡単な検討と同僚とのコミュニケーションの結果、次のTORがコンパイルされました。

-存在しない場合、プロジェクトマニフェストに基づいてパッケージを自動的に生成します。

-フォントまたは処理アクションを設定できるビューのみのコードを生成します。

-ビュー、フォント、リスナーを初期化するための3つの方法を選択します。 これらすべてを大規模プロジェクトのonCreateで書くと、コードの壁ができます。

-ListViewがxmlで見つかった場合、シート要素のレイアウトでxml-fileを選択してアダプターを生成するよう提案するダイアログを作成します。



上記のプロジェクトは、すでにいくつかの既製のソリューションが含まれているため、基礎として採用されました。 たとえば、このメソッドは、IDが登録されたすべてのビューをxmlから選択します。

 private NodeList getNodesWithId(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // factory.setNamespaceAware(true); // never forget this! DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(inputStream); XPathFactory pathFactory = XPathFactory.newInstance(); XPath xPath = pathFactory.newXPath(); XPathExpression expression = xPath.compile("//*[@id]"); return (NodeList) expression.evaluate(doc, XPathConstants.NODESET); }
      
      





次に、ViewGenerator、ActivityGenerator、AdapterGeneratorの3つのクラスが作成されました。

これらの各クラスには、それぞれに固有のさまざまなコードの文字列パターンが含まれています。

例:



 private final static String FIELD_PATTERN = "\tprivate %1$s %2$s;\n"; private final static String METHOD_VOID_PATTERN = "\tprivate void %1$s(){\n%2$s\t}\n";
      
      





ViewGeneratorは、xmlからのビューの初期化に関連するすべてのコード(フィールドの宣言、findViewById、setTypeFace、setListener、および必要なすべてのインポート)を生成します。 さらに、アクティビティとViewHolderアダプターの両方。 たとえば、setListenersの生成:

 private String getListeners(boolean innerClass){ StringBuilder builder = new StringBuilder(); for (WidgetResource widgetResource : widgetsTypes.keySet()) { if (BUTTON_WIDGETS.contains(widgetsTypes.get(widgetResource).getName())) { builder.append(String.format(innerClass ? ONCLICK_INNER_PATTERN : ONCLICK_PATTERN, widgetResource.getVariableName())); } } return builder.toString(); }
      
      





ActivityGeneratorは、ViewGenerator生成を使用してアクティビティコードを生成します。

 public String generate() { StringBuilder stringBuilder = new StringBuilder(); if (packageName != null && !packageName.isEmpty()) { stringBuilder.append(getPackage()); stringBuilder.append("\n"); } stringBuilder.append(getImports()); stringBuilder.append(viewGenerator.getImports()); stringBuilder.append("\n"); StringBuilder innerBuilder = new StringBuilder(); innerBuilder.append(getTag()); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getFields(false)); innerBuilder.append("\n"); innerBuilder.append(getCreateMethod()); innerBuilder.append("\n"); innerBuilder.append(getInitActionBarMethod()); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getInitViewsMethod(false)); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getSetFontsMethod(false)); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getSetListenersMethod(false)); stringBuilder.append(String.format(HEADER_PATTERN, activityResource.getVariableName(), innerBuilder.toString())); return stringBuilder.toString(); }
      
      







AdapterGeneratorはそれに応じてアダプターコードを生成します。

 public String generate() { StringBuilder stringBuilder = new StringBuilder(); if (packageName != null && !packageName.isEmpty()) { stringBuilder.append(getPackage()); stringBuilder.append("\n"); } stringBuilder.append(getImports()); stringBuilder.append("\n"); stringBuilder.append(getHeader()); stringBuilder.append("\n"); stringBuilder.append(getTag()); stringBuilder.append("\n"); stringBuilder.append(FIELDS_PATTERN); stringBuilder.append("\n"); stringBuilder.append(getConstructor()); stringBuilder.append("\n"); stringBuilder.append(getGetView()); stringBuilder.append("\n"); stringBuilder.append(getHolder()); stringBuilder.append("}"); stringBuilder.append("\n"); return stringBuilder.toString(); }
      
      







そして最後に、仕事の例:



アクティビティ



 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ListView android:id="@+id/news_list_activity_news_list" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:layout_width="fill_parent" android:layout_height="wrap_content" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" <ProgressBar android:id="@+id/articles_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/articles_exist_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/articles_exist_button" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout> </RelativeLayout>
      
      





シート要素



 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="15dp" android:paddingRight="15dp" > <TextView android:id="@+id/row_news_date_header_text" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/news_main_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" > <FrameLayout android:id="@+id/row_news_image_container" android:layout_width="96dp" android:layout_height="68dp" > <ImageView android:id="@+id/row_news_icon" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" /> </FrameLayout> <TextView android:id="@+id/row_news_title_text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/row_news_type_text" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/row_news_date_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/row_news_date_text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> <View android:id="@+id/row_news_items_divider" android:layout_width="fill_parent" android:layout_height="1dp" android:background="@drawable/divider_gray_horizontal" /> </LinearLayout>
      
      







結果



 package com.test.activity; import android.os.Bundle; import android.app.Activity; import android.widget.ProgressBar; import android.widget.TextView; import android.graphics.Typeface; import com.test.R; import android.widget.ListView; import android.widget.Button; import android.view.View.OnClickListener; import android.view.View; public class NewsListActivityActivity extends Activity { private static final String TAG = NewsListActivityActivity.class.getSimpleName(); private ProgressBar articlesProgress; private ListView newsListActivityNewsList; private TextView articlesExistTextView; private Button articlesExistButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_list_activity); initActionBar(); initViews(); setFonts(); setListeners(); } private void initActionBar(){ } private void initViews(){ articlesProgress = (ProgressBar) findViewById(R.id.articles_progress); newsListActivityNewsList = (ListView) findViewById(R.id.news_list_activity_news_list); articlesExistTextView = (TextView) findViewById(R.id.articles_exist_text_view); articlesExistButton = (Button) findViewById(R.id.articles_exist_button); } private void setFonts(){ Typeface roboto = null;//TODO init this by utils articlesExistTextView.setTypeface(roboto); } private void setListeners(){ articlesExistButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); } }
      
      





 package com.test.adapter; import com.test.R; import android.graphics.Typeface; import android.content.Context; import java.util.List; import android.widget.ArrayAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.graphics.Typeface; import android.widget.ImageView; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.view.View.OnClickListener; import android.view.View; public class NewsListActivityAdapter extends ArrayAdapter<String>{ private static final String TAG = NewsListActivityAdapter.class.getSimpleName(); private Context context; private LayoutInflater inflater; public NewsListActivityAdapter(Context context, List<String> objects) { super(context, R.layout.news_list_item, objects); inflater = LayoutInflater.from(context); this.context = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null){ convertView = inflater.inflate(R.layout.news_list_item, parent, false); holder = new ViewHolder(convertView); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } String item = getItem(position); if (item != null){ holder.populateForm(item); } return convertView; } private class ViewHolder{ private TextView rowNewsDateHeaderText; private TextView rowNewsDateText; private TextView rowNewsTypeText; private TextView rowNewsTitleText; public ViewHolder(View v){ initViews(v); setFonts(); } private void initViews(View v){ rowNewsDateHeaderText = (TextView) v.findViewById(R.id.row_news_date_header_text); rowNewsDateText = (TextView) v.findViewById(R.id.row_news_date_text); rowNewsTypeText = (TextView) v.findViewById(R.id.row_news_type_text); rowNewsTitleText = (TextView) v.findViewById(R.id.row_news_title_text); } private void setFonts(){ Typeface roboto = null;//TODO init this by utils rowNewsDateHeaderText.setTypeface(roboto); rowNewsDateText.setTypeface(roboto); rowNewsTypeText.setTypeface(roboto); rowNewsTitleText.setTypeface(roboto); } public void populateForm(String item) { } } }
      
      







使い方





1.プラグインjarファイルをEclipseのプラグインフォルダーにコピーします。

2. Eclipseを再起動します。

3.レイアウト画面の必要なxmlファイルを選択します。

4.選択したファイルを右クリック-> Android Code Generator。

5. packages%packagename%.activityおよび%packagename%.adapterから受け取ったファイルを使用します。



おわりに





これにより誰かの生活が楽になることを願っています。 プラグインはEclipse 4.2で動作します。 すべてのソースはここにあります



ご清聴ありがとうございました。



PSまず、Eclipse用のプラグインを作成することにしました。 将来的には、Android Studioでも同じことをするつもりです。



All Articles