Category Archives: Android

とあるアプリにGoogleAnalyticsを入れてAPK化しようとしたら、こんなエラーが出て続行できませんでした。

Proguard returned with error code 1. See console
Warning: com.google.analytics.tracking.android.FutureApis: can't find referenced method 'boolean setReadable(boolean,boolean)' in class java.io.File
Warning: com.google.analytics.tracking.android.FutureApis: can'
t find referenced method 'boolean setWritable(boolean,boolean)' in class java.io.File
You should check if you need to specify additional program jars.
Warning: there were 2 unresolved references to program class members.
Your input classes appear to be inconsistent.
You may need to recompile them and try again.
Alternatively, you may have to specify the option
'-dontskipnonpubliclibraryclassmembers'.
java.io.IOException: Please correct the above warnings first.
at proguard.Initializer.execute(Initializer.java:321)
at proguard.ProGuard.initialize(ProGuard.java:211)
at proguard.ProGuard.execute(ProGuard.java:86)
at proguard.ProGuard.main(ProGuard.java:492)
Proguard returned with error code 1. See console
Warning: com.google.analytics.tracking.android.FutureApis: can't find referenced method 'boolean setReadable(boolean,boolean)' in class java.io.File
Warning: com.google.analytics.tracking.android.FutureApis: can'
t find referenced method 'boolean setWritable(boolean,boolean)' in class java.io.File
You should check if you need to specify additional program jars.
Warning: there were 2 unresolved references to program class members.
Your input classes appear to be inconsistent.
You may need to recompile them and try again.
Alternatively, you may have to specify the option
'-dontskipnonpubliclibraryclassmembers'.
java.io.IOException: Please correct the above warnings first.
at proguard.Initializer.execute(Initializer.java:321)
at proguard.ProGuard.initialize(ProGuard.java:211)
at proguard.ProGuard.execute(ProGuard.java:86)
at proguard.ProGuard.main(ProGuard.java:492)

調べてみると、すでに導入していたProguardが悪さをしていたようです。

参考 Proguardでcan’t find referenced class

この記事の通りにproguard.cfgに

-dontwarn
-dontnote

を書き込もうと思ったんですが、proguard.cfgがない! 見つからない!

どうやら、ADT17.0にてproguardが色々変更されたらしく、私の環境ではproguard.cfgは存在しないようです。

で、また頑張って調べたら、プロジェクトフォルダの直下にあるproguard-project.txtがそれの代わりとなるようです。

proguard-project.txtに、下記のようなパラメータを追加します。

-dontwarn com.google.**

これで正常に動作して、APK化も上手くいきました!

ほー、良かった良かった。

参考 Android – Proguardの有効化(なんとか解決?)

新卒入社のiOS担当エンジニアさんがAndroidアプリについて解説してほしいとのことだったので、簡単な資料を作成しました。

実際にプレゼンするときは、業務で使用している本物のアプリのコードを併用して、具体例を交えながら説明しています。

簡易なものではありますが、概要くらいはつかめるかと思います。できるだけ平易なまとめ方を心がけているので、ざっくりとした知識を知りたいという方はご覧ください。

Androidアプリでカメラ機能を利用する方法のひとつに、撮影するためのActivityの自作があります。

プログラムを組むときにちょっとややこしかったので、メモ代わりに書いておこうと思います。

 

・パーミッション設定

<uses-permission android:name=”android.permission.CAMERA” />

カメラ機能を有効にするためのパーミッションです。
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />

撮影した画像をSDカードに保存するためのパーミッションです。
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />

SDカードが端末に入っているかどうかを判別するためのパーミッションです。

 

・Manifestにアクティビティ追加

<activity
android:name=”CameraActivity”
android:theme=”@android:style/Theme.Dialog”
android:exported=”false”
android:screenOrientation=”landscape”>
</activity>

注目してほしいのは、

android:theme=”@android:style/Theme.Dialog”

の記述です。

これを書き加えることで、アクティビティをダイアログの画面風にすることができます。

 

・レイアウトxmlを作成

lo_camera.xml

<?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=”fill_parent”
android:background=”@color/black”
android:orientation=”vertical” >

<LinearLayout
android:orientation=”vertical”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:gravity=”center”>
<SurfaceView
android:id=”@+id/surface_view”
android:layout_gravity=”center”
android:gravity=”center”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>

</LinearLayout>

 

・カメラ機能を呼び出すときの記述

遷移元Activity

// SDカードのチェック
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED)) {
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(
[遷移元Activity名].this);
builder.setMessage("データを保存するにはSDカードが必要です")
.setPositiveButton("OK", null).show();
} else {
Intent intent = new Intent(getApplicationContext(),
CameraActivity.class);
intent.setAction(Intent.ACTION_VIEW);
startActivityForResult(intent, 1001);
}

カメラ機能を呼び出すタイミングでSDカードの有無をチェックしています。

もしSDカードが端末にセットされていなければエラーダイアログを表示して、セットされていればカメラアクティビティをintentで呼び出しています。

 

・カメラ機能のプログラム

CameraActivity

import java.io.File;
import java.io.FileOutputStream;

import android.app.Activity;
import android.content.Intent;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.Toast;

public class CameraActivity extends Activity {
private SurfaceView mySurfaceView;
private Camera myCamera;

private SurfaceHolder.Callback mSurfaceListener =
new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
myCamera = Camera.open();
try {
myCamera.setPreviewDisplay(holder);
} catch (Exception e) {
e.printStackTrace();
}
}

public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
myCamera.release();
myCamera = null;
}

public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
Camera.Parameters parameters = myCamera.getParameters();
android.view.ViewGroup.LayoutParams lp = mySurfaceView.getLayoutParams();
int ch = parameters.getPreviewSize().height;
int cw = parameters.getPreviewSize().width;
if(ch/cw &gt; height/width){
lp.width = width;
lp.height = width*ch/cw;
}else{
lp.width = height*cw/ch;
lp.height = height;
}
mySurfaceView.setLayoutParams( lp );
myCamera.setParameters(parameters);
myCamera.startPreview();
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lo_camera);

Toast.makeText(CameraActivity.this,
"画面をタッチすると撮影します",
Toast.LENGTH_LONG).show();

Window myWin = getWindow(); //現在の表示されているウィンドウを取得
LayoutParams lp = new LayoutParams(); //LayoutParams作成
lp.screenBrightness = 1.0f; //輝度最大
lp.width = LayoutParams.WRAP_CONTENT;
lp.height = LayoutParams.WRAP_CONTENT;
myWin.setAttributes(lp); //ウィンドウにLayoutParamsを設定

mySurfaceView = (SurfaceView)findViewById(R.id.surface_view);
SurfaceHolder holder = mySurfaceView.getHolder();
holder.addCallback(mSurfaceListener);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

// シャッターが押されたときに呼ばれるコールバック
private Camera.ShutterCallback mShutterListener =
new Camera.ShutterCallback() {
public void onShutter() {
// TODO Auto-generated method stub
}
};

// JPEGイメージ生成後に呼ばれるコールバック
private Camera.PictureCallback mPictureListener =
new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
camera.startPreview();

// SDカードにJPEGデータを保存する
if (data != null) {
try {
//「/sdcard」を取得
File directory = Environment.getExternalStorageDirectory();
//SDカードに書き込める状態かチェック
if (directory.exists()){
//⇒書き込める状態の場合
if(directory.canWrite()){
//画像を保存するフォルダを作成します。
File file1 = new File(directory.getAbsolutePath() + "/[保存したいフォルダ名]");
file1.mkdir();
File file2 = new File(directory.getAbsolutePath() + "/[保存したいフォルダ名]/" + getPackageName());
file2.mkdir();
}
}
//フルパスを取得
String folderpath = directory.getAbsolutePath() + "/[保存したいフォルダ名]/" + getPackageName() + "/";

// sdcardフォルダを指定
File root = new File(folderpath);
// 日付でファイル名を作成
String filename = System.currentTimeMillis() + ".jpg";

// 保存処理開始
FileOutputStream fos = null;
fos = new FileOutputStream(new File(root, filename));
// jpegで保存
//capturedImage.compress(CompressFormat.JPEG, 100, fos);
fos.write(data);
// 保存処理終了
fos.close();

//データを作成
Intent i_data = new Intent();
i_data.putExtra("FOLDER_PATH", folderpath);
i_data.putExtra("FILE_NAME", filename);
//結果を設定
setResult(RESULT_OK, i_data);
//画面終了して遷移元に戻る
finish();

} catch (Exception e) {
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(CameraActivity.this);
builder.setMessage("撮影した画像の保存に失敗しました").setPositiveButton("OK", null).show();
}

//camera.startPreview();
}
}
};

@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (myCamera != null) {
myCamera.takePicture(mShutterListener, null, mPictureListener);
}
}
return true;
}
}

このソースの通りなので詳しい解説は書きませんが、

Window myWin = getWindow(); //現在の表示されているウィンドウを取得
LayoutParams lp = new LayoutParams(); //LayoutParams作成
lp.screenBrightness = 1.0f; //輝度最大
lp.width = LayoutParams.WRAP_CONTENT;
lp.height = LayoutParams.WRAP_CONTENT;
myWin.setAttributes(lp); //ウィンドウにLayoutParamsを設定

この部分はなぜ必要か解りづらいと思います。

試しにこの記述を外して動作させてみてください。画面が暗くなってしまうはずです。

アクティビティをダイアログ風に表示させているので、まわりの暗くしている画面に明るさが連動してしまっているようです。

LayoutParamsで明るさを明示的に設定しています。

 

・撮影した画像のパスやファイル名を受け取る

遷移元Activity

@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
// キャンセルボタンで戻ってきたときはRESULT_CANCELEDが入る
if (resultCode == RESULT_OK) {
switch (requestCode) {
case 1001:
if (intent != null) {
// 処理:値を取得
Bundle extras = intent.getExtras();
String sValue1 = extras.getString("FOLDER_PATH");
String sValue2 = extras.getString("FILE_NAME");
}
break;
default:
break;
}
}
}

CameraActivity を

startActivityForResult(intent, 1001);

で呼び出したので、finish()するとonActivityResult()に戻ってきます。

この記述で撮影した画像のパスとファイル名を受け取れるので、あとはその変数を煮るなり焼くなりお好きにしてください。

 

これを実装すると、こんな感じのカメラビューが表示されると思います。

 

 

 

 

 

 

 

 

スクリーンショットではカメラのプレビューが真っ暗ですが、本来ならここにファインダーから覗いた視界が映っています。

 

最近配布されているAndroidアプリのカメラ機能の実装のされ方を見るに、こういった自作のカメラ機能はあまり使われていないようですが、この記事がどなたかの一助になれば幸いです。

androidアプリの画面でよく見かけるのがこういう画面。

画面の下端に横一列に並ぶ、タブメニューボタン。

これは、背景となる元画像あって、その上から文字を重ねているわけですが、その元画像のサイズの縦横比を維持したままで、端末の横幅に合わせて拡大縮小させて並べる必要があります。

↑のキャプチャ画面は、NexusSでアプリを開いた場合の画面で、↓のキャプチャはNexus7で開いた場合です。

 

androidは画面の解像度が多種多様なため、端末対応が大変です。レイアウトxmlを上手いこと設定しないと、画面が崩れて残念なことになってしまいます。

ちなみに、元画像は↓こんな感じです。

これと

これです。

・レイアウトを画面下部に固定する

これは簡単です。


&amp;amp;amp;amp;lt;?xml version=&amp;amp;amp;amp;quot;1.0&amp;amp;amp;amp;quot; encoding=&amp;amp;amp;amp;quot;utf-8&amp;amp;amp;amp;quot;?&amp;amp;amp;amp;gt;
 &amp;amp;amp;amp;lt;LinearLayout xmlns:android=&amp;amp;amp;amp;quot;http://schemas.android.com/apk/res/android&amp;amp;amp;amp;quot;
 android:id=&amp;amp;amp;amp;quot;@+id/layout_main&amp;amp;amp;amp;quot;
 android:layout_width=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:orientation=&amp;amp;amp;amp;quot;vertical&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;LinearLayout
 android:layout_width=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:layout_weight=&amp;amp;amp;amp;quot;1&amp;amp;amp;amp;quot;
 android:gravity=&amp;amp;amp;amp;quot;center_vertical&amp;amp;amp;amp;quot;
 android:orientation=&amp;amp;amp;amp;quot;horizontal&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;!-- メインで表示されるレイアウト --&amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;/LinearLayout&amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;!-- 下メニュー --&amp;amp;amp;amp;gt;
 &amp;amp;amp;amp;lt;LinearLayout
 android:layout_width=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:orientation=&amp;amp;amp;amp;quot;horizontal&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;!-- 画面下部に固定で表示されるレイアウト --&amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;/LinearLayout&amp;amp;amp;amp;gt;

&amp;amp;amp;amp;lt;/LinearLayout&amp;amp;amp;amp;gt;

2つのLinearLayoutの、上に設置される方のandroid:layout_weightを1に設定します。

これで、タブメニューの高さ以外の部分は、すべて上のLinearLayoutが占有してくれます。

上のLinearLayoutの中にお好きなレイアウトを入れて、下のLinearLayoutにタブメニューのボタンを配置していきましょう。

 

・Buttonクラスを使ってみる

とりあえず、真っ先に浮かんだのがこの方法。

android:backgroundにボタン画像を指定したButtonクラスを、左から順番に4つ並べて、上述したandroid:layout_weightの使い方と同じ感覚で値を設定したものです。

このソースだと、こう表示されます。

一見良さそうに見えますが、よく見ると縦幅がちょっと伸びています。元画像と見比べてみてください。

どうやらButtonクラスは背景に指定した画像の縦横サイズなどは反映してくれないようです。

あくまで「Button」なので、背景に設定した画像の情報を持ってきていないのでしょうか。どうやっても画像の縦横比を維持したままでの拡大縮小はできませんでした。

 

・ImageButtonクラスを使ってみる

ボタン画像の縦横比が問題なので、次にImageButtonを試してみました。

これはImage画像に対してandroid:scaleTypeやandroid:adjustViewBoundsが有効なので、正確な縦横比で表示してくれるはずです。

このソースを実行してみると、こうなりました。

あれっ? 文字が表示されないよ……。

表示したいのは画像だけなので、android:background=”@null”と設定しているのですが、どうやらテキストはその部分に引っ張られるみたいですね。

ボタンの上下左右ならテキストも表示できるみたいなんですがね、重ねるのは不可能らしいです。

・結論、これしかないっぽい

FrameLayoutでImageViewとTextViewを重ねて配置します。それを横一列に並べて、4つのFrameLayoutのandroid:layout_weightの値を1にするのです。

そう、つまり力技です。

ボタン1つ分のFrameLayoutは下記のようになります。これを4つコピペして、表示される文字とかクリックリスナーに送られるビューとかを編集します。


&amp;amp;amp;amp;lt;FrameLayout
 android:layout_weight=&amp;amp;amp;amp;quot;1&amp;amp;amp;amp;quot;
 android:layout_width=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:orientation=&amp;amp;amp;amp;quot;horizontal&amp;amp;amp;amp;quot; &amp;amp;amp;amp;gt;
 &amp;amp;amp;amp;lt;ImageView
 android:src=&amp;amp;amp;amp;quot;@drawable/bt_tab_menu&amp;amp;amp;amp;quot;
 android:adjustViewBounds=&amp;amp;amp;amp;quot;true&amp;amp;amp;amp;quot;
 android:scaleType=&amp;amp;amp;amp;quot;fitXY&amp;amp;amp;amp;quot;
 android:layout_width=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;fill_parent&amp;amp;amp;amp;quot;
 android:onClick=&amp;amp;amp;amp;quot;loadCalender&amp;amp;amp;amp;quot; /&amp;amp;amp;amp;gt;
 &amp;amp;amp;amp;lt;TextView
 android:layout_width=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:layout_height=&amp;amp;amp;amp;quot;wrap_content&amp;amp;amp;amp;quot;
 android:layout_gravity=&amp;amp;amp;amp;quot;center|center_vertical&amp;amp;amp;amp;quot;
 android:clickable=&amp;amp;amp;amp;quot;false&amp;amp;amp;amp;quot;
 android:textSize=&amp;amp;amp;amp;quot;12sp&amp;amp;amp;amp;quot;
 android:textColor=&amp;amp;amp;amp;quot;#000000&amp;amp;amp;amp;quot;
 android:text=&amp;amp;amp;amp;quot;@string/calender&amp;amp;amp;amp;quot; /&amp;amp;amp;amp;gt;
 &amp;amp;amp;amp;lt;/FrameLayout&amp;amp;amp;amp;gt;

これで念願のレイアウトが完成しました。

色々と試行錯誤してて検索している人たちのお役に立てればいいなあ。

アプリのユーザー行動を分析するツールとして、Google Analyticsがあります。

Ver.2がリリースされており、こちらのバージョンは日本語で解説されているページが少なく、自分自身けっこうハマったので記事を書いておきます。

今回は、「ユーザーがどのアクティビティを起動したか」を分析するトラッキングを例に説明します。

 

・Google Analytics SDKを入手する

https://developers.google.com/analytics/devguides/collection/android/resources

こちらのページからGoogleAnalyticsAndroid.zipをダウンロードして、解凍します。

 

・プロジェクトにlibGoogleAnalyticsV2.jarをライブラリとして追加する

プロジェクトフォルダを右クリックして、[ビルド・パス]-[ビルド・パスの構成]からjarファイルをインポートします。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

・定義ファイルanalytics.xmlを作成する

[res]-[values]フォルダ内に、analytics.xmlというxmlファイルを作成してください。その中に以下のような値を書き込みます。

 

 

 

 

 

<?xml version=”1.0″ encoding=”utf-8″?>

<resources>
<string name=”ga_trackingId”>UA-○○○○○○-○</string>
<bool name=”ga_autoActivityTracking”>true</bool>
<bool name=”ga_debug”>true</bool>

<string name=”jp.co.plusr.android.okusurinote.TopActivity”>Android_TopActivity</string>
<integer name=”ga_dispatchPeriod”>30</integer>
</resources>

この、「<string name=”ga_trackingId”>」には、GoogleAnalyticsのサイトでプロパティを作成したときに生成されるトラッキング IDを入力してください。

「<string name=”jp.co.plusr.android.okusurinote.TopActivity”>Android_TopActivity</string>」の行ですが、これは実際にトラッキングしたときに、どのような文字列で表示されるかを設定します。

Activityごとにトラッカーを送って分析するわけですが、このjp.co.plusr.android.okusurinote.TopActivityでトラッカーを送ると、「Android_TopActivity」という文字列でGoogleAnalyticsのサイト上に表示されます。

後々わかってくると思うので、とりあえずは、ご自身のアプリのパッケージ名・Activity名と適当な文字列をセットにして定義してください。

 

・Activity内でトラッカーを送る

実際にトラッカーを送るのは、EasyTrackerというクラスを使います。

onStart()(またはonCreate)で

EasyTracker.getInstance().activityStart(this);

onStop()で

EasyTracker.getInstance().activityStop(this);

をそれぞれ記述してください。

 

 

 

 

 

以上でユーザーのActivity行動を分析する準備は完了です。

アプリを起動して、GoogleAnalyticsのサイトでリアルタイムのサマリーを見てみましょう。

 

 

 

 

 

 

 

 

こんな風にユーザーが起動しているActivityが、指定した文字列で表示されているはずです。

 

※注意:libGoogleAnalyticsはver.1とver.2は同時には使えません。

アナリティクスの機能がver.1とver.2で違うため、アナリティクスの画面で2つのバージョンを混ぜた結果を表示することができないので、気を付けてください。

 

※追記:パーミッションについて

パーミッションについての記述がなかったです。すいません。

GoogleAnalyticsを有効にするにはAndroidManifest.xmlにパーミッションを下記のように追加しましょう。
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”/>