SearchViewの詰まったところ備忘録
久しぶりにSearchViewをさわると忘れているポイントが結構あったので、今更感がありますがメモ書きを残します。サンプルコードはKotlinです。
SearchViewをactionbarいっぱいに表示したい
SearchViewをAction Barに配置するとデフォルトで左側に謎の余白ができてしまう問題によく出会います。
SearchViewの android:layout_width
を match_parent
にしても効きません。
ポイントはmaxWidthを変えること。
override fun onCreateOptionsMenu(menu: Menu?): Boolean { this.menuInflater.inflate(R.menu.search_menu, menu) val menuItem = menu?.findItem(R.id.search_view) val searchView = menuItem?.actionView as SearchView searchView.maxWidth = Int.MAX_VALUE return super.onCreateOptionsMenu(menu) }
hint textの色を変えたい
SearchViewの中で定義されているidを使ってSeachView内のEditText(実際はEditTextをextendsした@hideなクラスであるSearchAutoComplete)にアクセスし、hint text colorを書き換えます。
(searchView.findViewById(R.id.search_src_text) as EditText) .setHintTextColor(ContextCompat.getColor(this, android.R.color.white))
他にもなんか思い出したら適宜追記したい。
DroidKaigi 2017で登壇しました
3月9日、10日と二日間に渡って開催されたDroid Kaigi 2017にスピーカーとして参加してきました。
私のトークテーマはパフォーマンス改善について。オーディエンスの対象を初心者から中級者としていたため、わかりやすく丁寧に伝えるように心掛けたのですが、その結果たぶん全セッションの中で一番ゆるふわでほっこりセッションになったんじゃないかなぁと思います。(実際に聞きに来て頂いた人達に手を上げてもらったところ、「Android開発を初めて1年以内」、「2, 3年の経験がある」「それ以上」が同じくらいいらっしゃいました。あれ?笑)
発表練習に何度か付き合ってくれて沢山フィードバックくれたAndroidチームのみんなとか、社内のSlackに投げてたスライドにツッコミをいれてくれたエンジニア勢とか、セッション見に来てくれた皆様やオフィスアワーや懇親会で話しかけて下さった方々、本当にありがとうございますという圧倒的感謝でいっぱいです。
何が言いたかったかというと😺は可愛くて正義ってことです。
Android + Kotlin + Mockito のメモ書き
Kotlin大好きで、Korlin最高だよって言いまくってるんですけど、Kotlinのつらみみたいな部分も残しておかないとフェアじゃないかなと思ったので残しておきます。
Instrument Testがつらい
実機だったりエミュレータ上で実行するAndroid Instrument Testがつらいです。なぜかというとKotlinは基本的にクラスがデフォルトでfinalになるからです。
Auto testはみなさん大抵mockitoを使うと思います。mockito 2.0 からfinal classもmockできるようになりましたが、こちらの機能はopt in機能で素の(Androidではない) Java では src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
のパスに以下の1行を書いたファイルをおけば動くようになります。
mock-maker-inline
ところがAndroidのJVMだとこちらのopt inがうまく機能しません。
mockito-androidに期待したでしょ?
mockito-androidがmockito 2.6.1 から出て来ました。こちらでdexmakerとの依存から解放されると喜んでいる皆さん、残念ながらinline mock makerは対象外です\(^o^)/
Be aware that you cannot use the inline mock maker on Android due to limitations in the Android VM.
最終手段All Open
Kotlin 1.0.6からcompile pluginでall openが追加されました。
こちらを利用するとclassに open
修飾子をつけなくても特定のアノテーションをつけたクラスがopenになります。
blog.jetbrains.com
ただし、Test compile時だけこの機能を有効にする方法が見つからず、結局productionに導入するのは諦めました。
テスト時だけ動くようなGradle Taskを使ってうまくやろうと思ったけれど断念。誰かうまく言ったという人がいればぜひ教えてください(´;ω;`)
結論
powermockとか導入するのもtoo muchな感じでつらみがあるので、
Android JVMを利用しなくても良いunit testに一旦絞ってauto testを描く方針になりそう。
夕飯のカレーとコミットメントの話
先に言っておきますが、ただのポエムです。
エンジニアとして働いていると、「hoge機能を作りたいんだけど、今月中でできますか?」という質問をよくされます。ここでいうhogeがどれくらいしっかり固まっているかで話は変わってきます。
例えば、「今晩カレー食べたい!」というリクエストが来たとします。ここでいうカレーが
1. レトルトカレーを温めてご飯にかけるだけ
2. 市販のカレールーを使ってカレーをつくる
3. スパイスから拘ってインドカレーをつくる
4. おうちカレーじゃなくてカレー屋さんに行きたい
などなど無限に選択肢があります。
カレー食べたいと言い出した人が上のうちどれを想定しているのかによって、当然工数が変わってきます。困ったことに、カレー食べたいと言い出した人がどんなカレーを食べたいかわかっていないことがよくあります。
もしもカレーコンサルタントとして働いていて、それでお金を頂いているのであれば、手厚くヒアリングしましょう。でもそうじゃない場合、どうするのがいいのかなぁとよく考えます。
たとえば、上記4つの選択肢にかかる大体の工数をそれぞれ伝えてあげるのも一つの手段ですが、実際には1のレトルトカレーの選択肢でも、コンビニで買えるのか、Amazon Nowでポチってすぐ手に入るのか、Primeで1日かかるのか、それとも地方まで出かけないと手に入らないご当地カレーなのか様々です。
上のパラグラフのような話を長々としてしまっても、相談した人は困惑を浮かべるでしょう。エンジニアって面倒くさいな…と心の声が聞こえてきます。相談してきた人はできるかできないかの2択の答えを求めているからです。
かと言って適当に「ハイハイできます!」というのも違うなぁと思います。コミットメントした以上は約束を守る責任があるからです。(ここでいうコミットメントは、意識の高い人がよく使う「フルコミット」的な使い方ではなくて、「完遂すると約束すること」として使っています*1
付き合いが長い相手だと、「カレー食べたい!」って言われて、普通にお家カレーなんだろうなと予想がつくので「いいですね。今日のお夕飯はカレーにしますね」と言えます(=今晩の夕飯までにカレーを作るとコミットメントすることができる)が、ソフトウェア開発は斜め上のカレーをリクエストされる可能性が割と高い気がしています。
最近は一番可能性が高そうなケースを狙って、そこを一つの基準としておくのがいいのかなぁという気がしています。「どんなカレーを食べたいかによりますが、一旦週末目標で進めましょう。どんなカレーか具体的に決まったらもう一度話し合いましょうね。」って感じで。
何も決まってない以上、具体的なスケジュールを立てるのはもはや困難なので、相手の人を納得というか安心させてあげるだけでいいのかなという感じです。相談してきた人との信頼貯金を貯めていくというか、すぐに相談に来てもらえる関係性を作る点で有効かなと思っています。
「カレー」という文字を打ちすぎてカレー食べたくなってきたのでやめます。
本日のポエムは以上です。
*1:私がアンクルボブ信者なので。Clean Coderを読むとコミットメントの話題が出てきますね。
SnackbarでCustom Content を表示する
Support library revision25.1.0からSnackBarにCustom Contentを表示できるようになったらしいので試してみました٩( 'ω' )و
今回の変更概要
さて、release noteをよく読んでみましょう。
Snackbar has been refactored to allow apps to display custom content. BaseTransientBottomBar is the new base class that exposes the general sliding and animations behavior.
custom contentを表示するためにリファクタしたよって言ってますね。BaseTransientBottomBarが新しい基底クラスになるよって言ってます。誰よそれって感じなのでリファレンスやコードを読んでみましょう。
BaseTransientBottomBarって何よ
まずはSnackbarのクラス定義。確かにBaseTransientBottomBarをextendsしています。
public final class Snackbar extends BaseTransientBottomBar<Snackbar> {
このBaseTransientBottomBarが何をしてくれるクラスなのか、リファレンスを読んでみましょう。 主にやってくれそうなのは、この二つ。
- 画面下部から現れるViewの表示制御
- 表示・非表示のタイミングを取得するためのCallbackの提供
もともとSnackbarがになっていた機能の一部ですね。
Snackbarがどう変わったか
これらを踏まえて今度はSnackbarのコードを見てみましょう*1。Viewの表示制御をBaseTransientBottomBarへ移したからか、300行ほどの随分とスッキリしたクラスになりました。(以前は確か850行ほどありました。)
中身を読むと分かりますが、このクラスでやっていることは二つ。
- Snackbarのコンテンツ制御
- 表示・非表示のタイミングを取得するためのCallbackの提供
この通り、Viewの表示制御やアニメーションなどはやっていません。(Snackbar#make()の第3引数でdurationをとるので、ある意味ここだけアニメーションと言えなくはない。)
このようにコンテンツ制御というかSnackbar独自のviewの制御に特化したクラスになっています。
さらに言えばSnackbarのカスタムレイアウトはSnackbarContentLayout
としてhideな別クラスに切り出されています。
なのでSnackbar#setText()の中身も結構無理矢理な感じ。
/** * Update the text in this {@link Snackbar}. * * @param message The new text for this {@link BaseTransientBottomBar}. */ @NonNull public Snackbar setText(@NonNull CharSequence message) { final SnackbarContentLayout contentLayout = (SnackbarContentLayout) mView.getChildAt(0); final TextView tv = contentLayout.getMessageView(); tv.setText(message); return this; }
このように、VIewの表示制御とコンテンツ制御を切り離して実装することができるようになりました。 ということは、独自デザインのViewをSnackbarの用に表示・非表示するのが簡単にできるようになったってことです。
試してみた
今回は独自デザインの簡単なサンプルとして背景色が黒じゃないCustomSnackbarを作っていきます٩( 'ω' )و
まずはsupport libraryのrevisionを25.0.1にあげます。対象のlibraryをinstallしてbuild.gradle
をアップデートします。
compile 'com.android.support:design:25.1.0'
それではCustomSnackbarクラスを作っていきます。 BaseTransientBottomBarをextendsしてやって、
public class CustomSnackBar extends BaseTransientBottomBar<CustomSnackBar> {
Snackbarを参考に make()
メソッドを実装します。findSuitableParent()
の中身はSnackbarのそれのコピペです。
ちなみに、BaseTransientBottomBarのコンストラクタはprotectedなので、自前でコンストラクタを作ってやらないと"There is no default constructor available"って怒られます。
@NonNull public static CustomSnackBar make(@NonNull View view, @NonNull CharSequence text, int duration) { LayoutInflater inflater = LayoutInflater.from(view.getContext()); View content = inflater.inflate(R.layout.custom_snackbar, findSuitableParent(view), false); ((TextView) content.findViewById(R.id.custom_snackbar_textview)).setText(text); CustomSnackBar customSnackBar = new CustomSnackBar(findSuitableParent(view), content, new ViewCallBack()); return customSnackBar; } private CustomSnackBar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) { super(parent, content, contentViewCallback); }
ここでinflateするレイアウトですが、下記のような適当な背景色をつけたTextViewを持つLinearLayoutを読み込みます。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/custom_snackbar_textview" android:background="@color/colorAccent" android:textColor="@android:color/white"/> </LinearLayout>
show()メソッドはBaseTransientBottomBarクラスの方ですでに実装されているので、たったこれだけで使うときは普通のSnackbarの様に使えます。
CustomSnackBar.make(view, "This is custom snackbar sample", Snackbar.LENGTH_LONG)
.show();
こんな感じで表示されます٩( 'ω' )و
Android/Kotlin関連の情報収集とか。
最近Android周りの情報をどうやって追っているか?について質問されることが何度かあったのでまとめました。自分向けに2016年末時点でのスナップショットも兼ねて。
知り合いのエンジニアさんや勉強会であった方とTwitterで繋がる機会が多いせいか、Android関連の情報が流れてくるのが多いのは自分にとって圧倒的にTwitterです。みなさんいつも有用な情報をつぶやいてくれたりニュースをretweetしてくれたり本当に助かっています。ありがとうございます。
また、Google公式アカウントも幾つかフォローしています。(@AndroidDevとか。)
Googleの有名なエンジニアの方とかSquareのJakeさんとかフォローしてる方も多いと思いますが、私は海外のエンジニアさんはフォローしていません。そこまで追い始めるとキリがなさそうなので。あと話題になるtweetは誰かがretweetしてくれるからどのみち目にとまるからいいかなと思ったりします。
blogの中の人や更新情報用アカウントをフォローしておりRSS代わりのような用途にも使っていたりします。
メルマガ
Android WeeklyとKotlin Weeklyをsubscribeしています。毎週月曜に一週間のトピックをまとめてくれるので便利です。英語のblogやVideoの情報はだいたいここから入ってきています。
Podcast
Podcastはdex.fmを聞いています。Androidの技術的な話だけでなくチーム開発とかサービスグロースの観点の話題も出てきて非常に参考になります。
AndroidのPodcastと言えばFragmentedなども有名かと思いますが、こちらは全然追えていません。
Advent Calendar
今の時期だとAdvent Calendarも楽しく読んでいます。今年はAndroidのAdvent Calendarが3つもできてていいですね!
Android Advent Calendar 2016, Androidその2 Advent Calendar 2016, Android その3 Advent Calendar 2016
社内Slack
社内はTwitterではなくFacebook文化なので社内のメンバーとTwitterで全く繋がっていません。そのためTwitterで情報を共有することはないのですが、代わりに社内Slackに専用channelを作ってそこで情報を流しています。
うちは分報文化*1が根付いているのですが、そのノリで#times_androidや#times_kotlinを作って興味がある人たちがそこを覗いて情報を流したりそこにコメントして議論に発展したりします。AndroidとKotlinを分けている理由は、サーバサイドの一部でKotlinを採用していおり、Androidを書かないエンジニアもKotlinの情報を必要としているからです。
そういえばpublicなAndroidのslack channelに怖くてjoinできていません。あそこって誰でも勝手に入っていいのかわからない…
というわけで2016年はこんな感じでした。来年の年末に振り返ってみると何か変わってるかなぁ。
Activity#getTitle()はActivity/Applicationのresourcesを使わない
このエントリは先日参加した「まったりAndroid Framework Code Reading #4」の成果です٩( 'ω’ )و
知りたかったこと
AppCompatActivity#setSupportActionBar()した時に、Frameworkのバグと思しき挙動を見つけてしまいました。
setするToolBarにtitleを設定していない場合、ActionBarのタイトルにアプリで設定したlocaleではなくdevice defaultのlocaleが適用されてしまうのです:;(∩´﹏`∩);:
回避策としてToolBarにapplicationのcontextからとってきたstringをsetすればいいんですけど、せっかくなので Frameworkがタイトルに表示するstringをどのように取得するのか知りたくてFrameworkのコードを読んでみました٩( 'ω’ )و
わかったこと
- setSupportActionBar()でsetするToolbarにtitleが設定されていればそれを、設定されていなかったらAppCompatDelegateImplV7のContextからgetTitle()したものが使われる
- AppCompatDelegateImplV7のContextはAppCompatActivity自身
- しかしgetTitle()の時に使われるresourcesはAppCompatActivity自身のものではなく、ActivityThreadがActivityをattachする時に使うresources
- 上記resourcesはApplicationPackageManager#getResourcesForApplication() で取得したもので、これがdevice defaultのlocaleになっている
- device defaultなlocaleになる理由は、引数のoverride configurationにapplicationのresourcesに設定されたconfigurationではなくnullを設定しているから。
1844 Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, 1845 String[] libDirs, int displayId, LoadedApk pkgInfo) { 1846 return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, // ★最後から3番目の引数がoverride configurationだが、ここでnullを指定しているのでdevice defaultの物が設定される 1847 displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader()); 1848 }
ざっくりまとめると以上ですが、コードを読んだメモを置いておきます。
ToolBarについて
AppCompatActivity.java
public void setSupportActionBar(@Nullable Toolbar toolbar) { getDelegate().setSupportActionBar(toolbar); }
AppCompatDelegateImplV7.java
189 @Override 190 public void setSupportActionBar(Toolbar toolbar) { 212 if (toolbar != null) { 213 final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, 214 ((Activity) mContext).getTitle(), mAppCompatWindowCallback); 215 mActionBar = tbab;
ToolbarActionBar.java
73 public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) { 74 mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false); 75 mWindowCallback = new ToolbarCallbackWrapper(callback); 76 mDecorToolbar.setWindowCallback(mWindowCallback); 77 toolbar.setOnMenuItemClickListener(mMenuClicker); 78 mDecorToolbar.setWindowTitle(title); 79 }
ToolbarWidgetWrapper.java
90 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) { 91 this(toolbar, style, R.string.abc_action_bar_up_description, 92 R.drawable.abc_ic_ab_back_material); 93 }
95 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style, 96 int defaultNavigationContentDescription, int defaultNavigationIcon) { 97 mToolbar = toolbar; 98 mTitle = toolbar.getTitle(); 99 mSubtitle = toolbar.getSubtitle(); 100 mTitleSet = mTitle != null; 105 if (style) { 106 final CharSequence title = a.getText(R.styleable.ActionBar_title); 107 if (!TextUtils.isEmpty(title)) { 108 setTitle(title); 109 }
237 @Override 238 public void setWindowTitle(CharSequence title) { 239 // "Real" title always trumps window title. 240 if (!mTitleSet) { 241 setTitleInt(title); 242 } 243 }
AppCompatDelegateImplBase.java
67 AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { 68 mContext = context;
AppCompatDelegate.java
185 private static AppCompatDelegate create(Context context, Window window, 186 AppCompatCallback callback) { 187 final int sdk = Build.VERSION.SDK_INT; 188 if (BuildCompat.isAtLeastN()) { 189 return new AppCompatDelegateImplN(context, window, callback); 190 } else if (sdk >= 23) { 191 return new AppCompatDelegateImplV23(context, window, callback); 192 } else if (sdk >= 14) { 193 return new AppCompatDelegateImplV14(context, window, callback); 194 } else if (sdk >= 11) { 195 return new AppCompatDelegateImplV11(context, window, callback); 196 } else { 197 return new AppCompatDelegateImplV7(context, window, callback); 198 } 199 }
AppCompatActivity.java
509 public AppCompatDelegate getDelegate() { 510 if (mDelegate == null) { 511 mDelegate = AppCompatDelegate.create(this, this); 512 } 513 return mDelegate; 514 }
getTitle()で取得するStringについて
Activity.java
5635 public final CharSequence getTitle() { 5636 return mTitle; 5637 }
6593 final void attach(Context context, ActivityThread aThread, 6594 Instrumentation instr, IBinder token, int ident, 6595 Application application, Intent intent, ActivityInfo info, 6596 CharSequence title, Activity parent, String id, 6597 NonConfigurationInstances lastNonConfigurationInstances, 6598 Configuration config, String referrer, IVoiceInteractor voiceInteractor, 6599 Window window) { 6626 mTitle = title;
ActivityThread.java
2514 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { 2567 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); 2580 activity.attach(appContext, this, getInstrumentation(), r.token, 2581 r.ident, app, r.intent, r.activityInfo, title, r.parent, 2582 r.embeddedID, r.lastNonConfigurationInstances, config, 2583 r.referrer, r.voiceInteractor, window);
ComponentInfo.java
92 @Override public CharSequence loadLabel(PackageManager pm) { 93 if (nonLocalizedLabel != null) { 94 return nonLocalizedLabel; 95 } 96 ApplicationInfo ai = applicationInfo; 97 CharSequence label; 98 if (labelRes != 0) { 99 label = pm.getText(packageName, labelRes, ai); 100 if (label != null) { 101 return label; 102 } 103 } 104 if (ai.nonLocalizedLabel != null) { 105 return ai.nonLocalizedLabel; 106 } 107 if (ai.labelRes != 0) { 108 label = pm.getText(packageName, ai.labelRes, ai); 109 if (label != null) { 110 return label; 111 } 112 } 113 return name; 114 }
ApplicationPackageManager.java
1491 @Override 1492 public CharSequence getText(String packageName, @StringRes int resid, 1493 ApplicationInfo appInfo) { 1494 ResourceName name = new ResourceName(packageName, resid); 1495 CharSequence text = getCachedString(name); 1496 if (text != null) { 1497 return text; 1498 } 1499 if (appInfo == null) { 1500 try { 1501 appInfo = getApplicationInfo(packageName, sDefaultFlags); 1502 } catch (NameNotFoundException e) { 1503 return null; 1504 } 1505 } 1506 try { 1507 Resources r = getResourcesForApplication(appInfo); 1508 text = r.getText(resid); 1509 putCachedString(name, text); 1510 return text;
1241 @Override 1242 public Resources getResourcesForApplication(@NonNull ApplicationInfo app) 1243 throws NameNotFoundException { 1244 if (app.packageName.equals("system")) { 1245 return mContext.mMainThread.getSystemContext().getResources(); 1246 } 1247 final boolean sameUid = (app.uid == Process.myUid()); 1248 try { 1249 return mContext.mMainThread.getTopLevelResources( 1250 sameUid ? app.sourceDir : app.publicSourceDir, 1251 sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, 1252 app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, 1253 mContext.mPackageInfo); 1254 } catch (Resources.NotFoundException cause) { 1255 final NameNotFoundException ex = 1256 new NameNotFoundException("Unable to open " + app.publicSourceDir); 1257 ex.initCause(cause); 1258 throw ex; 1259 } 1260 }
ActivityThread.java
1844 Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, 1845 String[] libDirs, int displayId, LoadedApk pkgInfo) { 1846 return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, 1847 displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader()); 1848 }
ResourcesManager.java
625 public @NonNull Resources getResources(@Nullable IBinder activityToken, 626 @Nullable String resDir, 627 @Nullable String[] splitResDirs, 628 @Nullable String[] overlayDirs, 629 @Nullable String[] libDirs, 630 int displayId, 631 @Nullable Configuration overrideConfig, 632 @NonNull CompatibilityInfo compatInfo, 633 @Nullable ClassLoader classLoader) { 634 try { 635 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); 636 final ResourcesKey key = new ResourcesKey( 637 resDir, 638 splitResDirs, 639 overlayDirs, 640 libDirs, 641 displayId, 642 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy 643 compatInfo); 644 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); 645 return getOrCreateResources(activityToken, key, classLoader); 646 } finally { 647 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 648 } 649 }
517 private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken, 518 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { 519 synchronized (this) { 553 } else { 554 // Clean up any dead references so they don't pile up. 555 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); 556 557 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl 558 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); 559 if (resourcesImpl != null) { 560 if (DEBUG) { 561 Slog.d(TAG, "- using existing impl=" + resourcesImpl); 562 } 563 return getOrCreateResourcesLocked(classLoader, resourcesImpl); 564 } 565 566 // We will create the ResourcesImpl object outside of holding this lock. 567 } 568 } 569 570 // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. 571 ResourcesImpl resourcesImpl = createResourcesImpl(key);