このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com
作成 September 18, 2018 文面整理 November 22, 2019
トップページ > Android 開発トップ > C/C++ によるログ出力
line
Android 開発
line

ここでは、C/C++ で、ログファイルを出力する方法について、記録しています。

Android ネイティブアプリ開発について調査し、このサイトを作成したそもそもの理由は、情報不足でした。
自動生成されたコードにある関数の実行順、送られるメッセージの順序、途中経過の出力など、 ログが出力できないと、かなりの苦労をしなくてはなりません。

main.cpp には、LOGCAT と言われている Android のログを記録するための定義があります。

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "AndroidProject1.NativeActivity", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "AndroidProject1.NativeActivity", __VA_ARGS__))

しかし、いつの間にか統合環境から参照できなくなったり、また、端末からだと他のログもすべて混ざっているので、 自分のログを抽出するのが大変です。

Android 開発トップ にも書いていましたが、どうしてもログファイルを作成したく、 いろいろ試しましたので、このページに記録しています。

このページ、および開発関連ページは、PC向けデザインとなっております。 画面サイズの小さいスマホでは、快適な表示が得られませんので、ご了承ください。
ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。

▼ セクション一覧

まずはログファイルを作成したかった
ログ出力用のクラスを用意します
ログ出力クラスに用意した関数
ログファイル名を決定します
書き込みを行います
ログファイルを削除する関数も用意しておきます


まずはログファイルを作成したかった

このセクションは、February 16, 2018 付けで Android 開発トップに記載していたセクションと同内容です。 コードに関する部分のみでしたら必要ありませんので、スキップしていだいても構いません。

Android ネイティブアプリも、Windows と同じように、アプリにシステムからメッセージが送られて、それを処理する形で進むようです。 画面の作成に関しては、デバイスの画面サイズがいろいろあることや、1画面に1アプリであることなど(新しい Android OS ではそうとは限りません)、 画面に関する部分がかなり違いますが(OpenGLES を使って画面を描画するとか)、 まずはメッセージの流れを掴みたいと思い、標準的な出力コードで、ログを出力しました。

Android のログは logcat と呼ばれる部分に残る(見られる)ということなのですが、 システムのログも、プログラムのログもまとまってしまうため、プログラムのものだけを確認しにくいです。 しかも Visual C++ 2015 の 開発環境 ( エミュレータではなく、実機を接続しています。 ) から見えなくなることもあり、不便で仕方ありませんでした。

Windows/MFC では、 GetLocalTime で現在の時刻を取得して、CString に書き込みたい文字列を作成し、 CStdioFile を開いて SeekToEnd して、WriteString すれば、自由に簡単にファイルを作成、参照できます。
しかし Android アプリは、自由に書き込める場所が制限されています。 これをプライベートな領域と呼ぶならば、 このように、 自分の(インストール先)フォルダ直下の files フォルダ内で自由に読み書きできます。
ファイル名をフルパスで用意して、 FILE* fp = fopen(ファイル名,"rb") でファイルを開き fread で読む、同様に書く、というわけです。 ただ、この場合、ES ファイルエクスプローラーのようなアプリでも、見えません。 USB 接続したPCからも、見えません。 自由に見えなければ、ログの意味がありません。

他のアプリでは専用フォルダを作るものもあるようですが、とりあえずはログだけ見えればいいので、 Download のような、パブリックなフォルダにファイルを作成して、とりあえず見えるようにしよう、と考えました。

ネームスペースを定義し、ログファイルを出力する CjvmLog ( 先頭の C はクラスを、続く jvm は Java Virtual Machine を使用する、という意味としました。 ) という名前のクラスを作成、グローバルなオブジェクトとして定義しました。

なお、パブリックな領域にアクセスするには、Android Manifest に以下の記述が必要となるようでした。

Download フォルダなど、アプリケーション専用のフォルダ以外にアクセスしたい場合、 AndroidManifest.xml の </manifest> の前に、以下の行を書き込みます(READ_ のほうは、読み込みもしたいときに必要)。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
▲ページ先頭へ

ログ出力用のクラスを用意します

投稿 September 18, 2018

すぐ上に書いているように、Download フォルダなど、アプリケーション専用のフォルダ以外にアクセスしたい場合、 AndroidManifest.xml の </manifest> の前に、以下の行を書き込みます(READ_ のほうは、読み込みもしたいときに必要)。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

そして、ログ出力用のクラスを用意します。 私はどのファイルからもログを利用したかったので、専用クラスを用意し、グローバルなオブジェクトとして定義しました。

私の場合は、ヘッダーファイルを jvmLog.h、実装ファイルを jvmLog.cpp として、 クラス名を CjvmLog としました。 クラス名の先頭の C は Windows らしく(マイクロソフトらしく?) Class を意味し、続く jvm は、Java Virtual Machine を利用することを表すとしました。 なお、実際にはネームスペースに追加していますが、どのような形でも構いません。 ファイル名もクラス名も、当然なんでも構いません。

グローバルなオブジェクトを定義するファイルとして、globals.h として次のように記述しました。 ログを出力したいファイルでは、このヘッダーファイルをインクルードします。

#pragma once

#include "jvmLog.h" // ログ記録クラス

extern CjvmLog g_log; // ログ記録クラス

実装ファイルとしては、globals.cpp を用意し、次のように記述しました。

#include "pch.h"
#include "globals.h"

CjvmLog g_log; // ログ記録クラス
▲ページ先頭へ

ログ出力クラスに用意した関数

投稿 September 18, 2018

実装イメージとしては、init 関数で初期化し (現在は初期化する内容がなく、実装していませんので、ここでは省略しています)、 setFilename 関数でログファイル名を指定、以降はずっとここで指定したファイルに書き込まれるようにします。 add 関数に文字列を渡すと、 書き込み時刻とともにログファイルに1行追加される、という形です。

上記のように globals.cpp でオブジェクトを定義してプロジェクトに含めると、 アプリ起動時にコンストラクタが呼び出されます。 コンストラクタでは、CjvmLog クラスに protected で定義された、 ファイル名を保持する変数 char m_filename[1024] をクリアしています。

デストラクタでは、何もしていません。

▲ページ先頭へ

ログファイル名を決定します

投稿 September 18, 2018

setFilename 関数で、メンバー変数 m_filename に、ファイル名を設定しています。

void CjvmLog::setFilename(struct android_app* app, const char* filename, const char* filenamePrev)

引数 filename でファイル名を受け付けます。例えば、"myLogFile.txt" です。

基本の使い方として、アプリ起動時にログを削除し、起動中はずっと追記すると想定しました。 この場合、どこかでクラッシュするなどの場合、欲しいログが消されてしまう可能性があるため、 ひとつ前のログを別名で保存して、参照できるようにしようとしました。
引数 filenamePrev で、コピー先ファイル名を受け付けます。例えば、"myLogFilePrev.txt" です。 このファイルは、上書きされていくことになります。

開発中は、すぐにログを見たいので、Download サブフォルダにファイルを作成することとしました。 Download などのパブリックなパスを取得するためのコード例は、 JNI によるパブリックなパスの取得に記載していますので、 ここでは扱いません。 下の getDownloadFolder 関数は、Download フォルダへのパスを char* で返す関数です。

strcpy(m_filename, getDownloadFolder(app));
strcat(m_filename, "/");
strcat(m_filename, filename);

リリース時には、インストール先パスの files サブフォルダに作成されるようにします。 アプリで、ログファイルを見えるところにコピーする機能を用意するとして、通常は見えなくなります。

sprintf(m_filename, "%s/%s", getPrivateFolder(app), filename);

上の getPrivateFolder 関数は、アプリのパスの files サブフォルダへのパスを返す関数です。 実体は、以下のようになっています(実際にはネームスペースに追加しています)。

const char* getPrivateFolder(struct android_app* app)
{
	// アセットマネージャーを取得します。
	AAssetManager* mgr = app->activity->assetManager;
	if (!mgr) {
		return NULL;
	}

	//  internalDataPath には、アプリ用内部パスが格納されています。
	//    "data/data/<AppName>/files" の形式のパスになります。
	//    実際には、"Android/data/data/<AppName>/files" を指しているようです。
	return app->activity->internalDataPath;

開発中、リリース時の切り分けは、Windows 開発と同じようにするため、 プロジェクトのプロパティ(<プロジェクト名>.NativeActivity のプロパティ)で、次のように、 プリプロセッサの定義_DEBUG シンボルを定義して、 #ifndef _DEBUG のようにして、切り分けています。

プリプロセッサの定義
▲ページ先頭へ

書き込みを行います

投稿 September 18, 2018

g_log.add(app, "APP_CMD_START を受信"); のように使えるような関数を用意しました。

void CjvmLog::add(struct android_app* app, const char* text)
{
	FILE* fp;

	// ファイル指定が未実行の場合は、書き込めません。
	if (!(m_filename[0])) {
		return;
	}

	// 追記モードで開きます。
	fp = fopen(m_filename, "a");

	// ファイルを開けなければ、書き込めません。
	if (!fp) {
		return;
	}

	char str[1024];

	timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);

	time_t t = ts.tv_sec;
	tm tmv;
	localtime_r(&t, &tmv);

	int msec = (int)(ts.tv_nsec / 1000000);

	sprintf(str, "%02d:%02d:%02d:%03d %s\n", tmv.tm_hour, tmv.tm_min, tmv.tm_sec, msec, text);
	size_t size = fwrite((const void*)str, sizeof(char), strlen(str), fp);

	fclose(fp);
}

コードにある、時刻の追加に関する部分の詳細は、「C/C++による現在日時の取得」に詳細があります。

▲ページ先頭へ

ログファイルを削除する関数も用意しておきます

投稿 September 18, 2018

ログファイルにずっと追記していると、どんどんサイズが大きくなります。 アプリ起動時に(前回の)ログファイルを削除するための関数を用意しておきます。

void CjvmLog::clear(void)
{
	if (m_filename[0]) {
		remove(m_filename);
	}
}

このほか、
AAssetManager* mgr = app->activity->assetManager;
のように「アセットマネージャー」を取得すると、ファイルが存在するかを確認したり、 フォルダに存在するファイルを列挙したりできますが、ここでは触れません。

また、アプリの起動時間が長くなるようなら、ログファイルが肥大化する可能性もありますので、 ファイルサイズを返す関数を用意するか、書き込み時に自動でサイズを調整する機能があると良いかもしれません。

▲ページ先頭へ
line
関連トピックス
line

JNI によるパブリックなパスの取得

JNI を利用して、Download などのパスを取得する方法を検討しています。

C/C++ による現在日時の取得

C/C++ で現在時刻を取得する方法を検討しています。

line
その他のおすすめ
line

おすすめ記事はありません。

JavaScriptが無効です
▲ページ先頭へ


© 2017-2019 StraightApps.com 無断転載を禁じます。No reproduction without permission.