我在Firebase存储器上名为Icons的文件夹中有超过1.5K张图像,我想将这些图像加载到使用GridLayoutManager的RecyclerView中。显然,我只希望仅在Recyclerview中可见这些图像时才请求加载这些图像,否则这会浪费对存储的读取权限?
因此,我首先创建R.layout.card_icons_rcv_item xml布局-在ConstraintLayout内部包含一个ImageView,以表示RecyclerView中的每个单元。
然后我创建了Adapter类,您可以在onBindViewHolder的下面和内部看到它,我试图加载与各自RecyclerView单元中的每个单元相关联的图像,但是效果不佳。
首先,我仅获得约2张图像,或仅加载了2张图像(大部分时间),没有其他任何东西,其次,我可以看到Log.d("Adapter","Error loading card icon from firestore for card #" + position);
语句被调用了最后3个单元格,最后我在logcat中看到,即使recyclerView中没有图像,所有对firebase的所有请求都同时发出。
更新
reycler视图似乎成功加载了图标,但是非常非常缓慢。所以我在文档中做了一些挖掘,偶然发现了位图。文档说,由于我的图像视图比Firebase存储上的图像小,因此我可能应该对它们进行下采样。正如您在更新的onBindViewHolder方法中看到的那样,我正是这样做的。图像加载速度明显加快,但仍然不够快。我还遇到了一个我似乎无法解决的怪异异常。我在下面更新我的问题:
[更新]所以这是我的问题:
- 我如何仅请求从Firebase加载仅可见单元的图像,因为从我现在在Logcat中看到的情况来看,发出的请求等于
getItemCount()
返回的数字。 li>
- 在
getItemCount()
方法中,我返回一个随机数50,因为我不知道如何返回数据库中的图像数。那里至少有1.5K图像,数据库将逐渐变大。这会引起任何问题吗?如果是的话,我该如何克服呢? - 如何使这些图像的下载速度更快?
- 在将图像下载到onBindViewHolder方法中看到的临时文件中之后,我应该删除它们还是填充回收者视图的图像视图所需?
- 运行几次后,未对我的代码进行任何更改,我开始在Logcat中收到以下错误:
E / AuthUI:发生登录错误。 com.firebase.ui.auth.FirebaseUiException:保存凭据时出错。 在com.firebase.ui.auth.viewmodel.smartlock.SmartLockHandler $ 1.onComplete(SmartLockHandler.java:99) 在com.google.android.gms.tasks.zzj.run(未知来源:4) 在android.os.Handler.handleCallback(Handler.java:883) 在android.os.Handler.dispatchMessage(Handler.java:100) 在android.os.Looper.loop(Looper.java:221) 在android.app.activityThread.main(activityThread.java:7520) 在java.lang.reflect.Method.invoke(本机方法) 在com.android.internal.os.RuntimeInit $ MethodAndArgsCaller.run(RuntimeInit.java:539) 在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 引起原因:com.google.android.gms.common.api.ApiException:16:当前应用程序的保存提示被禁用。要还原,请删除 密码智能锁中“从不保存”列表中的此应用 此设备上所有帐户的设置。 在com.google.android.gms.common.internal.ApiExceptionUtil.fromStatus(com.google.android.gms:play-services-base @@ 17.1.0:4) 在com.google.android.gms.common.internal.zai.zaf(com.google.android.gms:play-services-base @@ 17.1.0:2) 在com.google.android.gms.common.internal.zak.onComplete(com.google.android.gms:play-services-base @@ 17.1.0:6) 在com.google.android.gms.common.api.internal.BasePendingResult.zaa(com.google.android.gms:play-services-base @@ 17.1.0:176) com.google.android.gms.common.api.internal.BasePendingResult.setResult(com.google.android.gms:play-services-base @@ 17.1.0:135) com.google.android.gms.common.api.internal.BaseImplementation $ ApiMethodImpl.setResult(com.google.android.gms:play-services-base @@ 17.1.0:36) 在com.google.android.gms.internal.auth-api.zzo.zzc中(未知来源:4) 在com.google.android.gms.internal.auth-api.zzv.dispatchTransaction(未知 资料来源:9) 在com.google.android.gms.internal.auth-api.zzd.onTransact上(未知 资料来源:12) 在android.os.Binder.execTransactInternal(Binder.java:1021) 在android.os.Binder.execTransact(Binder.java:994)
我尝试以用户(所有者)身份登录的用户已根据我的Log.d语句成功进行了身份验证,但出于某些奇怪的原因,它不允许我从Firebase读取数据。该错误提到我应该更改Google SmartLock中的设置,但我发誓天哪,我到处都在手机上寻找该设置,但找不到。我也在Google上进行了搜索,但是没有运气。关于在哪里可以找到该设置的任何想法?
CardIconRCVAdapter.java:
public class CardIconsRCVAdapter extends RecyclerView.Adapter<CardIconsRCVAdapter.ViewHolder> {
private LayoutInflater inflater;
private Context mContext;
private StorageReference storageReference;
public CardIconsRCVAdapter(Context context,StorageReference storageRef) {
mContext = context;
inflater = LayoutInflater.from(context);
storageReference = storageRef;
}
// stores and recycles views as they are scrolled off screen
class ViewHolder extends RecyclerView.ViewHolder {
ImageView iconImgView;
ViewHolder(@NonNull View itemView) {
super(itemView);
iconImgView = itemView.findViewById(R.id.cardIcon);
}
}
// Inflates the cell layout from xml when needed
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,int viewType) {
View view = inflater.inflate(R.layout.card_icons_rcv_item,parent,false);
return new ViewHolder(view);
}
// Binds the data to the views in each cell
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder,final int position) {
//Log.d("Adapter","OnBindViewHolder called for #" + position);
StorageReference iconRefJpeg = storageReference.child("Icons/icon" + position + ".jpeg");
final StorageReference iconRefPng = storageReference.child("Icons/icon" + position + ".png");
try {
final File localFile = File.createTempFile("icon" + position,".jpeg");
iconRefJpeg.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.Tasksnapshot>() {
@Override
public void onSuccess(FileDownloadTask.Tasksnapshot tasksnapshot) {
holder.iconImgView.setImageBitmap(decodeSampledBitmapFromResource(localFile.getabsolutePath(),150,150));
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("AdapterError","Failed fetching .jpeg image. Trying to fetch .png");
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
private static Bitmap decodeSampledBitmapFromResource(String filePath,int reqWidth,int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath,options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath,options);
}
private static int calculateInSampleSize(BitmapFactory.Options options,int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d("CalcSampleSize","height = " + height + " width = " + width + " Image type : " + options.outMimeType);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
@Override
public int getItemCount() {
return 50;
}
}
Mainactivity.kt:
class Mainactivity : AppCompatactivity() {
private val TAG: String = Mainactivity::class.java.simpleName // Tag used for debugging
// View declarations
private lateinit var iconsRCV : RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val storageRef = FirebaseStorage.getInstance().reference // Create a storage reference
// Setting Toolbar default settings
val toolbar : Toolbar = findViewById(R.id.mainToolbar)
setSupportactionBar(toolbar) // set the custom toolbar as the support action bar
supportactionBar?.setDisplayShowTitleEnabled(false) // remove the default action bar title
// RecyclerView initializations
iconsRCV = findViewById(R.id.cardIconsRCV)
val numOfColumns = 5
iconsRCV.layoutManager = GridLayoutManager(this,numOfColumns)
val iconsAdapter = CardIconsRCVAdapter(this,storageRef)
iconsRCV.adapter = iconsAdapter
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.appcompat.widget.Toolbar
android:id="@+id/mainToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp"
android:background="@color/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/toolbar_title"
android:text="@string/app_name"
android:textSize="18sp"
android:textColor="@android:color/white"
android:layout_gravity="center"/>
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cardIconsRCV"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/mainToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
card_icons_rcv_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/cardIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="cardIcon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
更新:
我在下面发布我对我的应用程序中的用户进行身份验证的方式,以帮助人们理解为什么我在第五个问题中遇到了身份验证错误。这是相应的代码:
class Userauthenticationactivity : AppCompatactivity() {
private val TAG = Userauthenticationactivity::class.java.simpleName
private val RC_SIGN_IN: Int = 101 // A request code used to authenticate users
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Choose authentication providers
val providers = arrayListOf(
AuthUI.IdpConfig.EmailBuilder().build(),AuthUI.IdpConfig.GoogleBuilder().build()
)
// Create and launch sign-in intent
startactivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setavailableProviders(providers)
.setIsSmartLockEnabled(true)
.setLogo(R.drawable.applogotmp) // Set logo drawable-temp
.build(),RC_SIGN_IN)
}
override fun onactivityResult(requestCode: Int,resultCode: Int,data: Intent?) {
super.onactivityResult(requestCode,resultCode,data)
if (requestCode == RC_SIGN_IN) {
val response = IdpResponse.fromResultIntent(data)
if (resultCode == activity.RESULT_OK) {
// Successfully signed in
// Consider passing the user object to the Mainactivity using a bundle
val user = FirebaseAuth.getInstance().currentUser
user?.let {
// Name,email address,and profile photo Url
val name = user.displayName
val email = user.email
Log.d(TAG,"User $user just logged in. Name $name and email $email")
}
startactivity(Intent(this,Mainactivity::class.java)) // User was authenticated,allow app access
finish()
} else { // Sign in failed
if(response == null) { // If response is null the user canceled the sign-in flow using the back button.
startactivity(Intent(this,Userauthenticationactivity::class.java))// restart the activity/prompt for auth again
finish()
} else {// Otherwise check response.getError().getErrorCode() and handle the error.
Log.d(TAG,"Error logging in user. Reason : ${response.error?.errorCode}")
}
}
}
}
}
在onactivityResult()中,如果登录成功,我只是从当前活动到我的Mainactivity(RecyclerView所在的位置)创建一个新的Intent,或者在用户登录之前重新启动当前活动。