【Android】Firebase配置与使用(下)

来源:互联网 发布:手机淘宝退款退货流程 编辑:程序博客网 时间:2024/03/29 23:12

上一篇博客记录了如何使用Firebase对实时数据库进行增删改以及持久化,本文记录如何对数据进行查操作,以及如何将文件上传服务器。

3.对实时数据库进行增删改查以及持久化操作(续)

Firebase数据库的检索操作主要是通过对树种的某一节点添加监听来实现(官方说的是对FirebaseDatabase添加监听,我认为不大准确,欢迎讨论),官方给出了几种可用的监听器,他们的功能如下:
这里写图片描述
对某一节点的事件进行监听,使用 addValueEventListener() 或者 addListenerForSingleValueEvent() 方法。 要对某一节点的子节点进行监听,可使用 addChildEventListener() 方法。
我们依然以博客文章为场景,看一个监听节点本身数据变化例子:

ValueEventListener postListener = new ValueEventListener() {    @Override    public void onDataChange(DataSnapshot dataSnapshot) {        // 监听到数据库变化,传入一个该路径下的节点数据“快照”        Post post = dataSnapshot.getValue(Post.class);        // ...    }    @Override    public void onCancelled(DatabaseError databaseError) {        // 监听数据库失败        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());        // ...    }};mPostReference.addValueEventListener(postListener);

我们看到,监听器ValueEventListener的onDataChange回调中,传入了一个DataSnapShot的对象,这个对象可以理解为当前节点处的数据快照,即他记录了当前节点的现状,我们可以通过这个快照获取该节点处的各个值,这里通过getValue()方法传入Post类型,获取到我们的Post的Java对象。

DataSnapShot的概念在我看来有点类似于上文中提到的MutableData,他们都封装了某个节点的“现状”,在Firebase数据库中,节点并非只有我们这里要的Post对象,还有该节点的ID等,我们所要的Post对象仅仅是该节点众多封装数据中的一个而已。

再看一个监听子节点变化的例子,在博客中我们需要监听评论的变化:

ChildEventListener childEventListener = new ChildEventListener() {    @Override    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {        Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());        // A new comment has been added, add it to the displayed list        Comment comment = dataSnapshot.getValue(Comment.class);        // ...    }    @Override    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {        Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());        //一条评论被改动,我们可以获取这条评论的key来判断是否是一个正在被展示的评论,如果是则改动他        Comment newComment = dataSnapshot.getValue(Comment.class);        String commentKey = dataSnapshot.getKey();    }    @Override    public void onChildRemoved(DataSnapshot dataSnapshot) {        Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());        // 一条评论被删除,我们通过获取这条评论的key来判断是否为一个正在被展示的评论,如果是,则删掉它        String commentKey = dataSnapshot.getKey();        // ...    }    @Override    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {        Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());        // 一条评论改变路径,获取Key来判断是否需要改变,同上        Comment movedComment = dataSnapshot.getValue(Comment.class);        String commentKey = dataSnapshot.getKey();        // ...    }    @Override    public void onCancelled(DatabaseError databaseError) {        Log.w(TAG, "postComments:onCancelled", databaseError.toException());        Toast.makeText(mContext, "Failed to load comments.",                Toast.LENGTH_SHORT).show();    }};ref.addChildEventListener(childEventListener);

每次修改子节点时,均会触发 onChildChanged() 回调。这包括对子节点的后代所做的任何修改。该回调通常与 onChildAdded() 和 onChildRemoved() 回调一起使用,以响应对项目列表所做的更改。传递给事件侦听器的快照包含子节点的更新数据。
删除直接子节点时,将会触发 onChildRemoved() 回调。该回调通常与 onChildAdded() 和 onChildChanged() 回调一起使用。传递给事件回调的快照包含已删除的子节点的数据。
每当因更新(导致子节点重新排序)而触发 onChildChanged() 回调时,系统就会触发 onChildMoved() 回调。 该回调与按优先级排序的数据结合使用。

可以添加监听器,也就可以删除监听器:通过在节点上调用 removeEventListener() 方法即可删除回调。
如果多次将侦听器添加到某一数据位置,则将为每个事件多次调用侦听器,并且必须分离相同的次数才能将其完全删除。
注意:在父侦听器上调用 removeEventListener() 时不会自动删除在其子节点上注册的侦听器;还必须在子节点上调用 removeEventListener() 才能删除回调。

如果需要仅监听一次,后续不再进入监听回调,可以使用前文提到的addListenerForSingleValueEvent() 方法,使用这个方法加入的监听只会触发一次,以后不再进入回调。

如果要对树的节点的查询结果进行排序操作,可以使用以下函数:
这里写图片描述
返回值都为一个Querry类的对象,Querry是DatabaseReference的父类,他们都是用来读取JSON树种某一位置处的数据的,例如:

//根据星数排序博文String myUserId = getUid();Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)        .orderByChild("starCount");

同样可以对查询结果进行过滤操作,官方给出的方法如下:
这里写图片描述
例如我们要过滤到只有前100条数据,可以这样:

Query recentPostsQuery = databaseReference.child("posts")        .limitToFirst(100);

至此增删改查操作就都有了。

4.文件上传/下载

前文我们的讨论的JSON树其实是一个RealtimeDatabase,但是他不负责存储文件,Firebase提供了Cloud Storage服务,专门用来存放我们的文件。这个系统类似于计算机中的文件系统,我们需要先创建这些文件的对象,然后对数据的上传和下载可以通过操作这些对象来实现。
要使用Storage功能,首先要创建Storage的引用,引用是通过调用 getReferenceFromUrl 方法并在 gs://<你的存储槽名> 形式的网址中传递,依据 Firebase 应用上的 FirebaseStorage 服务创建的。可以在 Firebase console的”storage”部分找到你的存储槽名。

StorageReference storageRef = storage.getReferenceFromUrl(RT.FIREBASE_STORAGE);//这里RT.FIREBASE_STORAGE是我们的存储槽名

创建完存储槽的引用后,需要对我们将要上传文件的位置创建对象:

StorageReference storageRef = storage.getReferenceFromUrl("gs://<槽名>");StorageReference mountainsRef = storageRef.child("mountains.jpg");StorageReference mountainImagesRef = storageRef.child("images/mountains.jpg");

上述两种方法(传入文件名和传入路径)都可行,他们会创建两个名字相同,路径不同的节点对象。

创建完引用后,上传操作才正式开始:
创建合适的引用后,可以调用 putBytes()、putFile() 或 putStream() 方法将文件上传至Firebase。

putBytes()

imageView.setDrawingCacheEnabled(true);imageView.buildDrawingCache();Bitmap bitmap = imageView.getDrawingCache();ByteArrayOutputStream baos = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);byte[] data = baos.toByteArray();UploadTask uploadTask = mountainsRef.putBytes(data);//************分割线********************uploadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {      //...    }}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {    @Override    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {        // ...        Uri downloadUrl = taskSnapshot.getDownloadUrl();    }});

分割线以下的部分在下文会介绍,先看上面的部分即可。

putStream()

InputStream stream = new FileInputStream(new File("path/to/images/rivers.jpg"));uploadTask = mountainsRef.putStream(stream);uploadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {    //...    }}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {    @Override    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {        // taskSnapshot.getMetadata()返回值包含了文件的媒体信息,比如大小、MIME类型、下载地址等        Uri downloadUrl = taskSnapshot.getDownloadUrl();    }});

putFile()

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());uploadTask = riversRef.putFile(file);uploadTask.addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // ...    }}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {    @Override    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {        Uri downloadUrl = taskSnapshot.getDownloadUrl();    }});

注:这里的file是Uri的对象而不是File的对象。

分割线以下的部分是对上传的状态进行了监听,这里回调中传入的是一个TaskSnapshot对象,这个对象是一个任务当前状态的“快照”,“快照”的概念在上一篇博客中有解释。
TaskSnapshot类包含了以下方法:
这里写图片描述
这个例子展示了TaskSnapshot的一个简单的使用:

uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {    @Override    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();        System.out.println("Upload is " + progress + "% done");    }}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {    @Override    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {        System.out.println("Upload is paused");    }});

值得一提的是,常常在一些场景中,我们在activity中进行上传操作,但是当activity生命周期变化后,我们需要保存上传的状态并且在activity停止时暂停这些操作,否则会出现一些意外情况。

StorageReference mStorageRef;  //mStorageRef was previously used to transfer data.@Overrideprotected void onSaveInstanceState(Bundle outState) {    super.onSaveInstanceState(outState);    // If there's an upload in progress, save the reference so you can query it later    if (mStorageRef != null) {        outState.putString("reference", mStorageRef.toString());    }}@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) {    super.onRestoreInstanceState(savedInstanceState);    // If there was an upload in progress, get its reference and create a new StorageReference    final String stringRef = savedInstanceState.getString("reference");    if (stringRef == null) {        return;    }    mStorageRef = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);    // Find all UploadTasks under this StorageReference (in this example, there should be one)    List tasks = mStorageRef.getActiveUploadTasks();    if (tasks.size() > 0) {        // Get the task monitoring the upload        UploadTask task = tasks.get(0);        // Add new listeners to the task using an Activity scope        task.addOnSuccessListener(this, new OnSuccessListener() {          @Override          public void onSuccess(UploadTask.TaskSnapshot state) {             handleSuccess(state); //call a user defined function to handle the event.          }        });    }}

上传需要支持断点续传功能,这点Firebase也已经考虑到了:

uploadTask = mStorageRef.putFile(localFile);sessionUri = uploadTask.getUploadSessionUri();uploadTask = mStorageRef.putFile(localFile,                    new StorageMetadata.Builder().build(), sessionUri);

在putFile生成的 StorageTask 上,调用 getUploadSessionUri 并将生成的值保存在内存中(如 SharedPreferences)。

以上是上传的操作,下载也一样简单:

还是先创建引用:

StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-bucket-name>");StorageReference pathReference = storageRef.child("images/stars.jpg");StorageReference gsReference = storage.getReferenceFromUrl("gs://bucket/images/stars.jpg");StorageReference httpsReference = storage.getReferenceFromUrl("https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg");

创建引用后,可以通过调用 getBytes() 或 getStream() 从 Firebase Storage下载文件。

getBytes()

使用 getBytes() 方法将文件下载至 byte[]。这是下载文件最简单的方法,不过,它必须将您的文件的整个内容加载到内存中。如果请求大于应用可用内存的文件,应用将崩溃。 为防止内存出现问题,getBytes() 具有下载的最大字节数限制。 将最大大小设为应用可以处理的大小,或者使用其他下载方法。

StorageReference islandRef = storageRef.child("images/island.jpg");final long ONE_MEGABYTE = 1024 * 1024;islandRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {    @Override    public void onSuccess(byte[] bytes) {        // Data for "images/island.jpg" is returns, use this as needed    }}).addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // Handle any errors    }});

getFile()

getFile() 方法可以将文件直接下载到本地设备中。如果用户希望离线时可以访问文件或者希望在不同的应用中分享文件,请使用这种方法。getFile() 会返回一个 DownloadTask,我们可以使用它管理下载和监视下载的状态。

islandRef = storageRef.child("images/island.jpg");File localFile = File.createTempFile("images", "jpg");islandRef.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {    @Override    public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {        // Local temp file has been created    }}).addOnFailureListener(new OnFailureListener() {    @Override    public void onFailure(@NonNull Exception exception) {        // Handle any errors    }});

有关断点续传以及Activity生命周期变更引起的续传操作与上传相同,简单看个例子:

StorageReference mStorageRef;  //mStorageRef was previously used to transfer data.@Overrideprotected void onSaveInstanceState(Bundle outState) {    super.onSaveInstanceState(outState);    // If there's a download in progress, save the reference so you can query it later    if (mStorageRef != null) {        outState.putString("reference", mStorageRef.toString());    }}@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) {    super.onRestoreInstanceState(savedInstanceState);    // If there was a download in progress, get its reference and create a new StorageReference    final String stringRef = savedInstanceState.getString("reference");    if (stringRef == null) {        return;    }    mStorageRef = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);    // Find all DownloadTasks under this StorageReference (in this example, there should be one)    List tasks = mStorageRef.getActiveDownloadTasks();    if (tasks.size() > 0) {        // Get the task monitoring the download        DownloadTask task = tasks.get(0);        // Add new listeners to the task using an Activity scope        task.addOnSuccessListener(this, new OnSuccessListener() {          @Override          public void onSuccess(DownloadTask.TaskSnapshot state) {             handleSuccess(state); //call a user defined function to handle the event.          }        });    }}

以上Firebase的文件上传下载的操作就完成了。

感谢阅读,理解的不对的希望指正!