Android デフォルトのホームアプリケーション(Launcher)起動プロセスのソースコード解析
前回は、Androidシステムの起動時にアプリがインストールされる過程を分析しました。これらのアプリがインストールされた後、それらをデスクトップに表示するためにホームアプリが必要になりますが、Androidシステムでは、このデフォルトのホームアプリがLauncherにあたります。この記事では、Launcherアプリの起動プロセスについて詳しく分析します。
書籍「"Androidソースコードシナリオ解析"」は、Attack of the Programmersのウェブサイト(
http://0xcc0xcd.com
をクリックし、お入りください。
Android用ホームアプリケーションLauncherは、PackageManagerServiceと同様に、起動時にSystemServerコンポーネントによって起動されるActivityManagerServiceによって起動されます。SystemServerコンポーネントは、まず、以前の記事で説明したように、システムのアプリケーションのインストールを担当するePackageManagerServicを起動します。 Androidアプリケーションインストール処理のソースコード解析 システムアプリケーションのインストールが完了すると、SystemServerコンポーネントはActivityManagerServiceを介してホームアプリケーションLauncherを起動します。Launcherは、起動時にPackageManagerServicを介して、インストールされたアプリケーションをデスクトップにショートカットアイコンとして表示し、ユーザーが利用できるようにします(下図参照)。
以下、各ステップについて詳しく分析する。
ステップ1.systemServer.main
この関数は、前回の記事で紹介した frameworks/base/services/java/com/android/server/SystemServer.java ファイルに定義されています。 Androidアプリケーションインストール時のソースコード解析 は、ステップ1についてです。
ステップ2.systemServer.init1
この関数は、前回の記事で紹介した frameworks/base/services/jni/com_android_server_SystemServer.cpp ファイルに実装された JNI メソッドです。 Androidアプリケーションインストール時のソースコード解析 をステップ2へ。
ステップ3. libsystem_server.system_init
system_init 関数は、libsystem_server ライブラリに実装されており、ソースコードは framework/base/cmds/system_server/library/system_init.cpp ファイルにあり、前回の記事で紹介した Androidアプリケーションインストール時のソースコード解析 をステップ3へ。
ステップ4. androidRuntime.callStatic
この関数は、前回の記事で紹介した frameworks/base/core/jni/AndroidRuntime.cpp ファイルに定義されています。 Androidアプリケーションインストール処理のソースコード解析 をクリックすると、ステップ 4 に進みます。
ステップ5.systemServer.init2
この関数は frameworks/base/services/java/com/android/server/SystemServer.java ファイルに定義されており、前回の記事で紹介した Androidアプリケーションインストール時のソースコード解析 をステップ5とします。
ステップ6.serverThread.run
この関数は、前回の記事で紹介した frameworks/base/services/java/com/android/server/SystemServer.java ファイルに定義されています。 Androidアプリケーションインストール時のソースコード解析 をクリックすると、ステップ6に進みます。
ステップ7. ActivityManagerService.mainこの関数は、frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java ファイルに定義されています。
public final class ActivityManagerService extends ActivityManagerNative
Monitor, BatteryStatsImpl.
......
public static final Context main(int factoryTest) {
AThread thr = new AThread();
thr.start();
synchronized (thr) {
while (thr.mService == null) {
try {
thr.wait();
} catch (InterruptedException e) {
}
}
}
ActivityManagerService m = thr.mService;
mSelf = m;
ActivityThread at = ActivityThread.systemMain();
mSystemThread = at;
Context context = at.getSystemContext();
m.mContext = context;
m.mFactoryTest = factoryTest;
m.mMainStack = new ActivityStack(m, context, true);
m.mBatteryStatsService.publish(context);
m.mUsageStatsService.publish(context);
synchronized (thr) {
thr.mReady = true;
thr.notifyAll();
}
m.startRunning(null, null, null, null);
return context;
}
......
}
この関数は、まずAThreadスレッドオブジェクトを介して内部でActivityManagerServiceのインスタンスを生成し、このインスタンスをそのメンバー変数mServiceに保存し、このActivityManagerServiceインスタンスをActivityManagerServiceクラスのスタティックメンバー変数mSelfに保存し、最後に他のメンバー変数を初期化して終了です。
ステップ 8. PackageManagerService.main
この関数は、前回の記事で紹介した frameworks/base/services/java/com/android/server/PackageManagerService.java ファイルに定義されています。 Androidアプリケーションインストール時のソースコード解析 この手順の後、システム内のアプリケーションの情報はすべてPackageManagerServiceに保存され、ホームアプリランチャーを起動すると、PackageManagerService内のアプリケーション情報が取り出され、デスクトップにショートカットアイコンとして表示されます。 後ほどご紹介するデスクトップ上の
ステップ9. ActivityManagerService.setSystemProcess
この関数は、frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java ファイルに定義されています。
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
......
public static void setSystemProcess() {
try {
ActivityManagerService m = mSelf;
ServiceManager.addService("activity", m);
ServiceManager.addService("meminfo", new MemBinder(m));
if (MONITOR_CPU_USAGE) {
ServiceManager.addService("cpuinfo", new CpuBinder(m));
}
ServiceManager.addService("permission", new PermissionController(m));
ApplicationInfo info =
mSelf.mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS);
mSystemThread.installSystemApplicationInfo(info);
synchronized (mSelf) {
ProcessRecord app = mSelf.newProcessRecordLocked(
mSystemThread.getApplicationThread(), info,
info.processName);
app.persistent = true;
app.pid = MY_PID;
app.maxAdj = SYSTEM_ADJ;
mSelf.mProcessNames.put(app.processName, app.info.uid, app);
synchronized (mSelf.mPidsSelfLocked) {
mSelf.mPidsSelfLocked.put(app.pid, app);
}
mSelf.updateLruProcessLocked(app, true, true);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
"Unable to find android system package", e);
}
}
......
}
この関数は、まずActivityManagerServiceのインスタンスをServiceManagerに追加してホストし、ServiceManager.getServiceインタフェースを通じて他の場所からグローバルにユニークなActivityManagerServiceインスタンスにアクセスできるようにします。そして、mSystemThread.installSystemApplicationInfoを呼び出して、アプリケーションフレームワーク層の下でandroidパッケージをロードします。mSystemThreadは、上記のステップ7で作成したActivityThread型のインスタンス変数です。mSystemThreadは、上記Step7で作成したActivityThread型のインスタンス変数で、この後、いくつかの初期化作業が行われます。
ステップ10. ActivityManagerService.systemReady
この関数は、上記Step6のServerThread.run関数でシステム内のサービス群を初期化した後に呼び出され、 frameworks/base/services/java/com/android/server/am/ ActivityManagerServcie.java ファイルで定義されています。
public final class ActivityManagerService extends ActivityManagerNative
Monitor, BatteryStatsImpl.
......
public void systemReady(final Runnable goingCallback) {
......
synchronized (this) {
......
mMainStack.resumeTopActivityLocked(null);
}
}
......
}
この関数にはもっと内容があるので、ここでは余計な部分を省略して、ホームアプリケーションを起動するロジックに注目します。ホームアプリケーションは、mMainStack.resumeTopActivityLocked関数(mMainStackはActivityStack型の変数のインスタンス)によって起動されます。
ステップ11. ActivityStack.resumeTopActivityLocked
この関数は、frameworks/base/services/java/com/android/server/am/ActivityStack.java ファイル内で以下のように定義されています。
public class ActivityStack {
......
final boolean resumeTopActivityLocked(ActivityRecord prev) {
// Find the first activity that is not finishing.
ActivityRecord next = topRunningActivityLocked(null);
......
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
if (mMainStack) {
return mService.startHomeActivityLocked();
}
}
......
}
......
}
ここで関数topRunningActivityLockedが呼ばれ、現在のシステムActivityスタックの一番上にあるActivityを返していますが、この時点ではActivityは起動していないので、戻り値はnull、つまり次の変数の値はnullなので、mService. startHomeActivityLocked文、mServiceは先のステップ7で作成したActivityManagerServiceインスタンスを指します。
ステップ 12. activityManagerService.startHomeActivityLocked
この関数は、frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java ファイル内の
public final class ActivityManagerService extends ActivityManagerNative
Monitor, BatteryStatsImpl.
......
boolean startHomeActivityLocked() {
......
Intent intent = new Intent(
mTopAction,
mTopData ! = null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
if (mFactoryTest ! = SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
ActivityInfo aInfo =
intent.resolveActivityInfo(mContext.getPackageManager(),
STOCK_PM_FLAGS);
if (aInfo ! = null) {
intent.setComponent(new ComponentName(
aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid);
if (app == null || app.InstrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,
null, null, 0, 0, 0, 0, false, false);
}
}
return true;
}
......
}
この関数は、まず CATEGORY_HOME タイプの Intent を作成し、次に Intent.resolveActivityInfo 関数を用いて PackageManagerService に Category タイプ HOME の Activity を問い合わせます。ここでは、システムだけが HOME タイプの Activity を登録した Launcher アプリケーションだけが来ていると仮定します (packages/apps/Launcher2/AndroidManifest.xml ファイルを参照) 。
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher"
android:sharedUserId="@string/sharedUserId"
>
......
<application
android:name="com.android.launcher2.LauncherApplication"
android:process="@string/process"
android:label="@string/application_name"
android:icon="@drawable/ic_launcher_home">
<activity
android:name="com.android.launcher2.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:screenOrientation="nosensor"
android:windowSoftInputMode="stateUnspecified|adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>
......
</application>
</manifest>
つまり、これはアクティビティ com.android.launcher2.Launcher を返しているのです。このActivityを起動するのは初めてなので、次に関数getProcessRecordLockedを呼び出すとProcessRecordの値がnullになるので、関数mMainStack.startActivityLockedが呼び出されてcom.android.Luncher2.Launcherが起動されます。Launcher2.Launcherは、mMainStackがActivityStack型のメンバー変数である。
ステップ13. activityStack.startActivityLocked
この関数は、frameworks/base/services/java/com/android/server/am/ActivityStack.java ファイルで定義されており、その中にあります。 Androidアプリケーション起動処理ソースコード解析 今回のシナリオでは、この関数を呼び出すと、最終的に com.android.launcher2.Launcher が起動し、その後に onCreate 関数が呼び出されます。
ステップ14.launcher.onCreate
この関数は、packages/apps/Launcher2/src/com/android/launcher2/Launcher.java ファイル内の
public final class Launcher extends Activity
OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
......
@Override
protected void onCreate(Bundle savedInstanceState) {
......
if (!mRestoring) {
mModel.startLoader(this, true);
}
......
}
......
}
ここで mModel は LauncherModel 型のメンバ変数であり、その startLoader メンバ関数を呼び出すことでアプリケーションの追加操作が行われる。
ステップ15.launcherModel.startLoader
この関数は、packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.javaファイルで次のように定義されています。
public class LauncherModel extends BroadcastReceiver {
......
public void startLoader(Context context, boolean isLaunching) {
......
synchronized (mLock) {
......
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks ! = null && mCallbacks.get() ! = null) {
// If there is already one running, tell it to stop.
LoaderTask oldTask = mLoaderTask;
if (oldTask ! = null) { if (oldTask.
if (oldTask.isLaunching()) {
// don't downgrade isLaunching if we're already running
isLaunching = true;
}
oldTask.stopLocked();
}
mLoaderTask = new LoaderTask(context, isLaunching);
sWorker.post(mLoaderTask);
}
}
}
......
}
ここでは、アプリケーションを直接読み込むのではなく、アプリケーションを読み込む操作をメッセージとして処理します。ここでのsWorkerはHandlerで、そのpostによってメッセージをメッセージキューに入れ、システムは渡されたパラメータmLoaderTaskのrun関数を呼び出してメッセージを処理します。 mLoaderTaskはLoaderTask型のインスタンスなので、以下は LoaderTaskクラスのrun関数が実行されることになります。
ステップ16 LoaderTask.run
この関数はpackages/apps/Launcher2/src/com/android/launcher2/LauncherModel.javaの以下の箇所で定義されています。
public class LauncherModel extends BroadcastReceiver {
......
private class LoaderTask implements Runnable {
......
public void run() {
......
keep_running: {
......
// second step
if (loadWorkspaceFirst) {
......
loadAndBindAllApps();
} else {
......
}
......
}
......
}
......
}
......
}
ここでは、さらなる操作のためにloadAndBindAllAppsメンバ関数が呼び出されます。
ステップ17.LoaderTask.loadAndBindAllApps
この関数はpackages/apps/Launcher2/src/com/android/launcher2/LauncherModel.javaファイルで定義されています。
public class LauncherModel extends BroadcastReceiver {
......
private class LoaderTask implements Runnable {
......
private void loadAndBindAllApps() {
......
if (!mAllAppsLoaded) {
loadAllAppsByBatch();
if (mStopped) {
return;
}
mAllAppsLoaded = true;
} else {
onlyBindAllApps();
}
}
......
}
......
}
アプリがまだロードされていないため、ここでの mAllAppsLoaded は false となり、さらに処理を行うために loadAllAppsByBatch 関数を呼び出します。
ローダータスク.loadAllAppsByBatch
この関数は、packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.javaファイル内で以下のように定義されています。
public class LauncherModel extends BroadcastReceiver {
......
private class LoaderTask implements Runnable {
......
private void loadAllAppsByBatch() {
......
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final PackageManager packageManager = mContext.getPackageManager();
List<ResolveInfo> apps = null;
int N = Integer.MAX_VALUE;
int startIndex;
int i=0;
int batchSize = -1;
while (i < N && !mStopped) {
if (i == 0) {
mAllAppsList.clear();
......
apps = packageManager.queryIntentActivities(mainIntent, 0);
......
N = apps.size();
......
if (mBatchSize == 0) {
batchSize = N;
} else {
batchSize = mBatchSize;
}
......
Collections.sort(apps,
new ResolveInfo.DisplayNameComparator(packageManager));
}
startIndex = i;
for (int j=0; i<N && j<batchSize; j++) {
// This builds the icon bitmaps.
mAllAppsList.add(new ApplicationInfo(apps.get(i), mIconCache));
i++;
}
final boolean first = i <= batchSize;
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
final ArrayList<ApplicationInfo> added = mAllAppsList.added;
mAllAppsList.added = new ArrayList<ApplicationInfo>();
mHandler.post(new Runnable() {
public void run() {
final long t = SystemClock.uptimeMillis();
if (callbacks ! = null) { if (first)
if (first) {
callbacks.bindAllApplications(added);
} else {
callbacks.bindAppsAdded(added);
}
......
} else {
......
}
}
});
......
}
......
}
......
}
......
}
この関数は、最初に CATEGORY_LAUNCHER 型の Intent を構築します。
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
次に,変数mContextからPackageManagerServiceインタフェースを取得します.
final PackageManager packageManager = mContext.getPackageManager();
次に、この PackageManagerService.queryIntentActivities インターフェイスを使用して、Intent タイプのすべての Action を取得します。category_launcherを取得します。
PackageManagerService.queryIntentActivities関数で、これらのActivityをどのように取得するか見てみましょう。
ステップ19. PackageManagerService.queryIntentActivities
この関数は、frameworks/base/services/java/com/android/server/PackageManagerService.java ファイルに定義されています。
class PackageManagerService extends IPackageManager.Stub {
......
public List<ResolveInfo> queryIntentActivities(Intent intent,
String resolvedType, int flags) {
......
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return (List<ResolveInfo>)mActivities.queryIntent(intent,
resolvedType, flags);
}
......
}
......
}
......
}
以前の投稿を思い出してください Androidアプリのインストール処理のソースコード解析 前回のStep8では、システムがPackageManagerServiceを起動すると、システム内のすべてのアプリケーションを解析し、解析したActivityをmActivities変数に保存していますが、ここではmActivities変数のqueryIntent関数で条件を満たしたActivityを返しています。条件を満たすActivityは、mActivities変数のqueryIntent関数で返され、ここで返されるのは、ActionタイプがIntentのActivityになります。
Step 18 の LoaderTask.loadAllAppsByBatch 関数に戻り、queryIntentActivities 関数呼び出しから要求された Activity を返した後、関数 tryGetCallbacks(oldCallbacks) が呼び出されて CallBack インターフェースに戻り、このインターフェースは Launcher クラスによって実装されており、さらに操作するためにインターフェースの .bindAllApplications 関数が呼び出されるのです。ここでも、アプリケーションをロードする操作は、メッセージによって処理されることに注意してください。
ステップ20. ランチャー.bindAllApplications
この関数はpackages/apps/Launcher2/src/com/android/launcher2/Launcher.javaファイルで定義されています。
public final class Launcher extends Activity
OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
......
private AllAppsView mAllAppsGrid;
......
public void bindAllApplications(ArrayList<ApplicationInfo> apps) {
mAllAppsGrid.setApps(apps);
}
......
}
ここでの mAllAppsGrid は AllAppsView 型の変数で、実際の型は通常 AllApps2D になります。
ステップ21.AllApps2D.setApps(オールアップス2D.セットアップス
この関数は、packages/apps/Launcher2/src/com/android/launcher2/AllApps2D.javaファイル内で以下のように定義されています。
public class AllApps2D
extends RelativeLayout
implements AllAppsView,
OnItemClickListener,
OnItemLongClickListener, OnItemLongClickListener,
OnKeyListener,
DragSource {
......
public void setApps(ArrayList<ApplicationInfo> list) {
mAllAppsList.clear();
addApps(list);
}
public void addApps(ArrayList<ApplicationInfo> list) {
final int N = list.size();
for (int i=0; i<N; i++) {
final ApplicationInfo item = list.get(i);
int index = Collections.binarySearch(mAllAppsList, item,
LauncherModel.APP_NAME_COMPARATOR);
if (index < 0) {
index = -(index+1);
}
mAllAppsList.add(index, item);
}
mAppsAdapter.notifyDataSetChanged();
}
......
}
setApps関数は、まずmAllAppsListのリストをクリアし、次にaddApps関数を呼び出して、前のステップで取得したアプリケーションごとにApplicationInfoのインスタンスを生成しています。
この時点で、システムのデフォルトのホームアプリケーションLauncherは、PackageManagerServiceからアプリケーションを読み込み、画面上の以下のアイコンをクリックすると、先ほど読み込んだアプリケーションがアイコンとして表示されます。
このボタンがクリックされると、Launcher.onClick関数に応答します。
public final class Launcher extends Activity
OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
......
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
......
} else if (tag instanceof FolderInfo) {
......
} else if (v == mHandleView) {
if (isAllAppsVisible()) {
......
} else {
showAllApps(true);
}
}
}
......
}
次に、showAllApps 関数を呼び出して、アプリケーションアイコンを表示します。
public final class Launcher extends Activity
OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
......
void showAllApps(boolean animated) {
mAllAppsGrid.zoom(1.0f, animated);
((View) mAllAppsGrid).setFocusable(true);
((View) mAllAppsGrid).requestFocus();
// TODO: fade these two too
mDeleteZone.setVisibility(View.GONE);
}
......
}
これにより、システム内のアプリケーションを確認することができます。
これらのアプリケーションアイコンが上記でクリックされると、AllApps2D.onItemClick関数が応答します。
public class AllApps2D
extends RelativeLayout
implements AllAppsView,
OnItemClickListener,
OnItemLongClickListener, OnItemLongClickListener,
OnKeyListener,
DragSource {
......
public void onItemClick(AdapterView parent, View v, int position, long id) {
ApplicationInfo app = (ApplicationInfo) parent.getItemAtPosition(position);
mLauncher.startActivitySafely(app.intent, app);
}
......
}
ここでのメンバ変数mLauncherはLauncher型なので、Launcher.startActivitySafely関数が呼び出されてアプリケーションが起動し、次のように表示されていることがわかります。 Androidアプリの起動処理に関するソースコード解析 ある記事
ラオ・ルオの新浪微博。
http://weibo.com/shengyangluo
とフォローを歓迎します
関連
-
Android のリストビューでアダプタを使用しているときに null オブジェクトの参照に対して仮想メソッド xxxxxxxx を呼び出そうとする問題が解決されました。
-
コンフィギュレーション 'compile' は廃止され、'implementati solution' に置き換わりました。
-
ProcessBuilderExceptionCreateProcess error=2, ϵͳÕҲ "μ½ָ¶".
-
Android フロントカメラのビデオ録画に失敗しました (MediaRecorder: start failed: -19)
-
アンドロイドにおけるemsの本当の意味
-
ArrayAdapter のソリューションでは、リソース ID が TextView である必要があります。
-
指定された子にはすでに親がいます。まず、その子の親に対して removeView() をコールする必要があります。
-
Manifest merger failed : Android 12以降をターゲットとするアプリは、明示的な指定が必要です。
-
Androidのレイアウトにおけるmarginとpaddingの違いについて
-
android.content.res.Resources$NotFoundException: 文字列リソースID #0x1 Sinkhole!
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
AndroidStudioのエラーAAPT2エラーの解決:詳細のログを確認する
-
BindView 問題 NULLオブジェクト参照で仮想メソッド 'void android ...' を呼び出そうとする
-
Android ARTランタイムのDalvik仮想マシンをシームレスに置き換えるプロセスの分析
-
暗黙のうちに開始するアクティビティを使用するAndroidについて、Intent問題を処理するアクティビティが見つからないことが報告されました。
-
Android 開発の問題 - いくつかのプロジェクトはすでにワークスペースに存在するため、インポートできません。
-
Android 開発の問題点:ActivityNotFoundException: 明示的なアクティビティクラスを見つけることができません
-
Androidの内部育成に磨きをかける2年間
-
Android統計チャート MPAndroidChart
-
ColorDrawableの簡単な使い方
-
原因:android.content.res.Resources$NotFoundException。文字列リソースID #0x0