主頁 > 移動端開發 > Android 百度影像識別(詳細步驟+原始碼)

Android 百度影像識別(詳細步驟+原始碼)

2021-04-07 10:29:31 移動端開發

百度影像識別

    • 運行效果圖
    • 一、創建平臺應用
    • 二、創建Android專案
    • 三、網路訪問框架
    • 四、添加請求API介面
    • 五、獲取鑒權認證Token
    • 六、網路圖片Url識別
    • 七、相冊圖片識別
    • 八、拍照圖片識別
    • 九、原始碼

運行效果圖

在這里插入圖片描述

如果你對這個效果圖不滿意就不用往下看了,那樣只會浪費你的時間,

一、創建平臺應用

先登錄百度開放平臺
在這里插入圖片描述

然后進入管理控制臺找到影像識別
在這里插入圖片描述
點擊進入,
在這里插入圖片描述
創建應用
在這里插入圖片描述
然后點擊下方的立即創建按鈕,
在這里插入圖片描述
注意看下圖示注的資訊

在這里插入圖片描述
由于影像識別沒有直接的Android SDK,因此本文將通過API訪問進行影像的識別,

二、創建Android專案

通過API方式,則需要先完成鑒權認證,然后拿到Access Token,通過這個Access Token才能去請求這個影像識別的介面,這里要分為兩步走,現在思路清楚了,下面先創建一個專案吧,命名為ImageDiscernDemo,
在這里插入圖片描述
專案創建好之后,先配置專案,打開工程的build.gradle,添加如下代碼:

	maven { url "https://jitpack.io" }

添加位置如下:
在這里插入圖片描述

然后是修改app下的build.gradle,有兩處

	compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
	//權限請求框架
    implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation "io.reactivex.rxjava2:rxjava:2.0.0"
    //retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    //RecyclerView最好的配接器,讓你的配接器一目了然,告別代碼冗余
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
    //Glide框架
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
    //添加material庫
    implementation 'com.google.android.material:material:1.2.1'

添加位置如下:
在這里插入圖片描述

在這里插入圖片描述

然后Sync Now,完成后來配置AndroidManifest.xml,添加如下權限:

	<!--網路權限-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--檔案讀寫-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!--相機-->
    <uses-permission android:name="android.permission.CAMERA"/>

在這里插入圖片描述
再來配置一個FileProvider,在layout下新建一個xml檔案,檔案夾下新建一個file_paths.xml,里面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="" />
</paths>

然后在AndroidManifest.xml中配置

		<provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.llw.imagediscerndemo.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

添加位置如下圖:
在這里插入圖片描述

下面我們首先簡單來寫一個網路訪問的工具類,

三、網路訪問框架

在com.llw.imagediscerndemo下新建一個network包,包下新建一個ServiceGenerator類,里面的代碼如下:

package com.llw.imagediscerndemo.network;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 介面地址管理
 *
 * @author llw
 */
public class ServiceGenerator {

    /**
     * 默認地址
     */
    public static String BASE_URL = "https://aip.baidubce.com";

    /**
     * 創建服務  引數就是API服務
     *
     * @param serviceClass 服務介面
     * @param <T>          泛型規范
     * @return api介面服務
     */
    public static <T> T createService(Class<T> serviceClass) {

        //創建OkHttpClient構建器物件
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();

        //設定請求超時的時間,這里是10秒
        okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS);

        //訊息攔截器  因為有時候介面不同在排錯的時候 需要先從介面的回應中做分析,利用了訊息攔截器可以清楚的看到介面回傳的所有內容
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();

        //setlevel用來設定日志列印的級別,共包括了四個級別:NONE,BASIC,HEADER,BODY
        //BASEIC:請求/回應行
        //HEADER:請求/回應行 + 頭
        //BODY:請求/回應航 + 頭 + 體
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //為OkHttp添加訊息攔截器
        okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);

        //在Retrofit中設定httpclient
        //設定地址  就是上面的固定地址,如果你是本地訪問的話,可以拼接上埠號  例如 +":8080"
        Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
                //用Gson把服務端回傳的json資料決議成物體
                .addConverterFactory(GsonConverterFactory.create())
                //放入OKHttp,之前說過retrofit是對OkHttp的進一步封裝
                .client(okHttpClientBuilder.build())
                .build();
        //回傳這個創建好的API服務
        return retrofit.create(serviceClass);
    }

}

很簡單的代碼,也都是網路上常見的,OkHttp + Retrofit,

這里面的默認地址 https://aip.baidubce.com是影像識別API的固定地址,后面的有變化的,通過介面來配置,

然后再寫一個NetCallBack類,用來處理Retrofit的回傳,里面的代碼如下:

package com.llw.imagediscerndemo.network;

import android.util.Log;

import com.google.gson.Gson;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * 網路請求回呼
 *
 * @param <T>
 */
public abstract class NetCallBack<T> implements Callback<T> {//這里實作了retrofit2.Callback

    //訪問成功回呼
    @Override
    public void onResponse(Call<T> call, Response<T> response) {//資料回傳
        if (response != null && response.body() != null && response.isSuccessful()) {
            onSuccess(call, response);
        } else {
            onFailed(response.raw().toString());
        }
    }

    //訪問失敗回呼
    @Override
    public void onFailure(Call<T> call, Throwable t) {
        Log.d("data str", t.toString());
        onFailed(t.toString());
    }

    //資料回傳
    public abstract void onSuccess(Call<T> call, Response<T> response);

    //失敗例外
    public abstract void onFailed(String errorStr);


}

那么這樣簡單的網路框架寫好了,

四、添加請求API介面

百度的影像識別,首先要完成鑒權認證,拿到一個Token,然后通過這個Token再去請求影像識別的API介面才行,所以要完成兩步操作,先來看第一步
鑒權的地址如下:

https://aip.baidubce.com/oauth/2.0/token

這里面需要再帶三個Url引數,使用Post請求方式,

grant_type: 必須引數,固定為client_credentials;
client_id: 必須引數,應用的API Key;
client_secret: 必須引數,應用的Secret Key;

回傳的是一串JSON字串,如下:

{
  "refresh_token": "25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074",
  "expires_in": 2592000,
  "scope": "public wise_adapt",
  "session_key": "9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI",
  "access_token": "24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074",
  "session_secret": "dfac94a3489fe9fca7c3221cbf7525ff"
}

當請求和回傳都確定之后,我們就可以構建請求物體和回傳物體了,
而請求物體都是放在Url中的,因此不需要通過物體來構建,直接傳引數就好,

在com.llw.imagediscerndemo包下新建一個model包,包下新建一個GetTokenResponse類,里面的代碼如下:

package com.llw.imagediscerndemo.model;

/**
 * 獲取鑒權認證Token回應物體
 */
public class GetTokenResponse {

    /**
     * refresh_token : 25.b55fe1d287227ca97aab219bb249b8ab.315360000.1798284651.282335-8574074
     * expires_in : 2592000
     * scope : public wise_adapt
     * session_key : 9mzdDZXu3dENdFZQurfg0Vz8slgSgvvOAUebNFzyzcpQ5EnbxbF+hfG9DQkpUVQdh4p6HbQcAiz5RmuBAja1JJGgIdJI
     * access_token : 24.6c5e1ff107f0e8bcef8c46d3424a0e78.2592000.1485516651.282335-8574074
     * session_secret : dfac94a3489fe9fca7c3221cbf7525ff
     */

    private String refresh_token;
    private long expires_in;
    private String scope;
    private String session_key;
    private String access_token;
    private String session_secret;

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public long getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(long expires_in) {
        this.expires_in = expires_in;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getSession_key() {
        return session_key;
    }

    public void setSession_key(String session_key) {
        this.session_key = session_key;
    }

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getSession_secret() {
        return session_secret;
    }

    public void setSession_secret(String session_secret) {
        this.session_secret = session_secret;
    }
}

這是通過剛才的回傳資料生成的物體Bean,當網路請求回傳資料后通過Retrofit會決議成這個回傳物體,
下面添加介面,在network包下新建一個ApiService介面,里面的代碼如下:

package com.llw.imagediscerndemo.network;

import com.llw.imagediscerndemo.model.GetTokenResponse;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Headers;
import retrofit2.http.POST;

/**
 * API服務
 *
 * @author llw
 * @date 2021/4/1 17:48
 */
public interface ApiService {

    /**
     * 獲取鑒權認證Token
     * @param grant_type 型別
     * @param client_id API Key
     * @param client_secret Secret Key
     * @return GetTokenResponse
     */
    @FormUrlEncoded
    @POST("/oauth/2.0/token")
    Call<GetTokenResponse> getToken(@Field("grant_type") String grant_type,
                                    @Field("client_id") String client_id,
                                    @Field("client_secret") String client_secret);

    
}

這里還有一個介面呢,那就是影像識別介面,地址為:

https://aip.baidubce.com/rest/2.0/image-classify/v2/advanced_general

找個介面比較特殊,官方檔案的描述如下:
在這里插入圖片描述
看你是否理解了,回傳資料如下:

{
	"log_id": 327863200205075661,
	"result_num": 5,
	"result": [{
		"score": 0.967622,
		"root": "公眾人物",
		"baike_info": {
			"baike_url": "http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884",
			"image_url": "http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg",
			"description": "新垣結衣(Aragaki Yui),1988年6月11日出生于沖繩縣那霸市,日本女演員、歌手、模特,畢業于日出高中,2001年,參加《nicola》模特比賽并獲得最優秀獎,2005年,因出演現代劇《澀谷15》而作為演員出道,2006年,參演校園劇《我的老大,我的英雄》;同年,她還出版了個人首本寫真集《水漾青春》,2007年,她從日出高校畢業后開始專注于演藝發展,并發表個人首張音樂專輯《天空》;同年,新垣結衣還主演了愛情片《戀空》,而她也憑借該片獲得了多個電影新人獎項,2010年,主演愛情片《花水木》,2011年,主演都市劇《全開女孩》,2012年,相繼參演現代劇《Legal High》、劇情片《劇場版新參者:麒麟之翼》,2013年,主演都市劇《飛翔情報室》,2014年,她主演了劇情片《黎明的沙耶》,2016年,主演愛情喜劇《逃避雖可恥但有用》,并憑借該劇獲得了多個電視劇女主角獎項,2017年,主演愛情片《戀愛回旋》,憑借該片獲得第60屆藍絲帶獎最佳女主角;同年11月,她還憑借醫療劇《Code Blue 3》獲得第94屆日劇學院賞最佳女配角,"
		},
		"keyword": "新垣結衣"
	},
	{
		"score": 0.716067,
		"root": "人物-人物特寫",
		"keyword": "頭發"
	},
	{
		"score": 0.421281,
		"root": "商品-穿戴",
		"keyword": "圍巾"
	},
	{
		"score": 0.22347,
		"root": "商品-五金",
		"keyword": "拉鏈"
	},
	{
		"score": 0.028031,
		"root": "商品-穿戴",
		"keyword": "脖套"
	}]
}

通過這個回傳示例資料,可以生成一個物體Bean,在model包下新建一個GetDiscernResultResponse類,代碼如下:

package com.llw.imagediscerndemo.model;

import java.util.List;

/**
 * 獲取識別結果回應物體
 */
public class GetDiscernResultResponse {

    /**
     * log_id : 327863200205075661
     * result_num : 5
     * result : [{"score":0.967622,"root":"公眾人物","baike_info":{"baike_url":"http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884","image_url":"http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg","description":"新垣結衣(Aragaki Yui),1988年6月11日出生于沖繩縣那霸市,日本女演員、歌手、模特,畢業于日出高中,2001年,參加《nicola》模特比賽并獲得最優秀獎,2005年,因出演現代劇《澀谷15》而作為演員出道,2006年,參演校園劇《我的老大,我的英雄》;同年,她還出版了個人首本寫真集《水漾青春》,2007年,她從日出高校畢業后開始專注于演藝發展,并發表個人首張音樂專輯《天空》;同年,新垣結衣還主演了愛情片《戀空》,而她也憑借該片獲得了多個電影新人獎項,2010年,主演愛情片《花水木》,2011年,主演都市劇《全開女孩》,2012年,相繼參演現代劇《Legal High》、劇情片《劇場版新參者:麒麟之翼》,2013年,主演都市劇《飛翔情報室》,2014年,她主演了劇情片《黎明的沙耶》,2016年,主演愛情喜劇《逃避雖可恥但有用》,并憑借該劇獲得了多個電視劇女主角獎項,2017年,主演愛情片《戀愛回旋》,憑借該片獲得第60屆藍絲帶獎最佳女主角;同年11月,她還憑借醫療劇《Code Blue 3》獲得第94屆日劇學院賞最佳女配角,"},"keyword":"新垣結衣"},{"score":0.716067,"root":"人物-人物特寫","keyword":"頭發"},{"score":0.421281,"root":"商品-穿戴","keyword":"圍巾"},{"score":0.22347,"root":"商品-五金","keyword":"拉鏈"},{"score":0.028031,"root":"商品-穿戴","keyword":"脖套"}]
     */

    private long log_id;
    private int result_num;
    private List<ResultBean> result;

    public long getLog_id() {
        return log_id;
    }

    public void setLog_id(long log_id) {
        this.log_id = log_id;
    }

    public int getResult_num() {
        return result_num;
    }

    public void setResult_num(int result_num) {
        this.result_num = result_num;
    }

    public List<ResultBean> getResult() {
        return result;
    }

    public void setResult(List<ResultBean> result) {
        this.result = result;
    }

    public static class ResultBean {
        /**
         * score : 0.967622
         * root : 公眾人物
         * baike_info : {"baike_url":"http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884","image_url":"http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg","description":"新垣結衣(Aragaki Yui),1988年6月11日出生于沖繩縣那霸市,日本女演員、歌手、模特,畢業于日出高中,2001年,參加《nicola》模特比賽并獲得最優秀獎,2005年,因出演現代劇《澀谷15》而作為演員出道,2006年,參演校園劇《我的老大,我的英雄》;同年,她還出版了個人首本寫真集《水漾青春》,2007年,她從日出高校畢業后開始專注于演藝發展,并發表個人首張音樂專輯《天空》;同年,新垣結衣還主演了愛情片《戀空》,而她也憑借該片獲得了多個電影新人獎項,2010年,主演愛情片《花水木》,2011年,主演都市劇《全開女孩》,2012年,相繼參演現代劇《Legal High》、劇情片《劇場版新參者:麒麟之翼》,2013年,主演都市劇《飛翔情報室》,2014年,她主演了劇情片《黎明的沙耶》,2016年,主演愛情喜劇《逃避雖可恥但有用》,并憑借該劇獲得了多個電視劇女主角獎項,2017年,主演愛情片《戀愛回旋》,憑借該片獲得第60屆藍絲帶獎最佳女主角;同年11月,她還憑借醫療劇《Code Blue 3》獲得第94屆日劇學院賞最佳女配角,"}
         * keyword : 新垣結衣
         */

        private double score;
        private String root;
        private BaikeInfoBean baike_info;
        private String keyword;

        public double getScore() {
            return score;
        }

        public void setScore(double score) {
            this.score = score;
        }

        public String getRoot() {
            return root;
        }

        public void setRoot(String root) {
            this.root = root;
        }

        public BaikeInfoBean getBaike_info() {
            return baike_info;
        }

        public void setBaike_info(BaikeInfoBean baike_info) {
            this.baike_info = baike_info;
        }

        public String getKeyword() {
            return keyword;
        }

        public void setKeyword(String keyword) {
            this.keyword = keyword;
        }

        public static class BaikeInfoBean {
            /**
             * baike_url : http://baike.baidu.com/item/%E6%96%B0%E5%9E%A3%E7%BB%93%E8%A1%A3/8035884
             * image_url : http://imgsrc.baidu.com/baike/pic/item/91ef76c6a7efce1b27893518a451f3deb58f6546.jpg
             * description : 新垣結衣(Aragaki Yui),1988年6月11日出生于沖繩縣那霸市,日本女演員、歌手、模特,畢業于日出高中,2001年,參加《nicola》模特比賽并獲得最優秀獎,2005年,因出演現代劇《澀谷15》而作為演員出道,2006年,參演校園劇《我的老大,我的英雄》;同年,她還出版了個人首本寫真集《水漾青春》,2007年,她從日出高校畢業后開始專注于演藝發展,并發表個人首張音樂專輯《天空》;同年,新垣結衣還主演了愛情片《戀空》,而她也憑借該片獲得了多個電影新人獎項,2010年,主演愛情片《花水木》,2011年,主演都市劇《全開女孩》,2012年,相繼參演現代劇《Legal High》、劇情片《劇場版新參者:麒麟之翼》,2013年,主演都市劇《飛翔情報室》,2014年,她主演了劇情片《黎明的沙耶》,2016年,主演愛情喜劇《逃避雖可恥但有用》,并憑借該劇獲得了多個電視劇女主角獎項,2017年,主演愛情片《戀愛回旋》,憑借該片獲得第60屆藍絲帶獎最佳女主角;同年11月,她還憑借醫療劇《Code Blue 3》獲得第94屆日劇學院賞最佳女配角,
             */

            private String baike_url;
            private String image_url;
            private String description;

            public String getBaike_url() {
                return baike_url;
            }

            public void setBaike_url(String baike_url) {
                this.baike_url = baike_url;
            }

            public String getImage_url() {
                return image_url;
            }

            public void setImage_url(String image_url) {
                this.image_url = image_url;
            }

            public String getDescription() {
                return description;
            }

            public void setDescription(String description) {
                this.description = description;
            }
        }
    }
}

下面在ApiService中添加介面,

	/**
     * 獲取影像識別結果
     * @param accessToken 獲取鑒權認證Token
     * @param url 網路圖片Url
     * @return JsonObject
     */
    @FormUrlEncoded
    @POST("/rest/2.0/image-classify/v2/advanced_general")
    @Headers("Content-Type:application/x-www-form-urlencoded; charset=utf-8")
    Call<GetDiscernResultResponse> getDiscernResult(@Field("access_token") String accessToken,
                                                    @Field("url") String url);

我這個寫法和官方說明好像有點不同,我沒有用Body,選擇直接把資料放在Url中請求,

現在萬事具備了,下面先來獲取鑒權認證Token,

五、獲取鑒權認證Token

打開MainActivity,添加如下代碼:

	private static final String TAG = "MainActivity";
    /**
     * Api服務
     */
    private ApiService service;
    /**
     * 鑒權Toeken
     */
    private String accessToken;

然后在onCreate方法中對ApiService進行實體化,

	service = ServiceGenerator.createService(ApiService.class);

新增如下方法獲取Token,

	/**
     * 訪問API獲取介面
     */
    private void requestApiGetToken() {
        String grantType = "client_credentials";
        String apiKey = "TjPChftoEyBq7Nzm65KNerqr";
        String apiSecret = "eTph4jO95te6R3G2aecktGMbkieOv7rS";
        service.getToken(grantType, apiKey, apiSecret)
                .enqueue(new NetCallBack<GetTokenResponse>() {
                    @Override
                    public void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {
                        if (response.body() != null) {
                            //鑒權Token
                            accessToken = response.body().getAccess_token();
                            Log.d(TAG,accessToken);
                        }
                    }

                    @Override
                    public void onFailed(String errorStr) {
                        Log.e(TAG, "獲取Token失敗,失敗原因:" + errorStr);
                        accessToken = null;
                    }
                });
    }

然后在onCreate中呼叫它,
在這里插入圖片描述
運行一下,看一下控制臺是否列印了日志,
在這里插入圖片描述
你可以看到這個Token還是挺長的,對于這個Token,是有有效期的,基本上是一個月,所以你可以不用每次使用時都重新請求這個介面去獲取Token,這里可以用快取來解決這個問題,

說一下邏輯,當通過介面拿到Token時保存Token、Token獲取時間、Token有效時長三個資料到快取中,每一次使用前進行一次判斷,首先是判斷有沒有Token,其次是判斷Token有沒有過期,那么按照這個思路我們就可以這么寫代碼了,

這里為了方便我在com.llw.imagediscerndemo包下新建一個util包,包下新建一個Constant類,里面的代碼如下:

package com.llw.imagediscerndemo.util;

/**
 * 全域常量
 */
public class Constant {
    /**
     * 鑒權Token
     */
    public static final String TOKEN = "accessToken";
    /**
     * 獲取Token的時間
     */
    public static final String GET_TOKEN_TIME = "getTokenTime";
    /**
     * Token有效期
     */
    public static final String TOKEN_VALID_PERIOD = "tokenValidPeriod";
}

這三個值,我剛才也說明過了,下面寫一個快取的SPUtils工具類,里面的代碼如下:

package com.llw.imagediscerndemo.util;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * SharedPreferences工具類
 *
 * @author llw
 */
public class SPUtils {
    private static final String NAME = "config";

    public static void putBoolean(String key, boolean value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putBoolean(key, value).commit();
    }

    public static boolean getBoolean(String key, boolean defValue, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        return sp.getBoolean(key, defValue);
    }

    public static void putString(String key, String value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putString(key, value).commit();
    }

    public static String getString(String key, String defValue, Context context) {
        if (context != null) {
            SharedPreferences sp = context.getSharedPreferences(NAME,
                    Context.MODE_PRIVATE);
            return sp.getString(key, defValue);
        }
        return "";

    }

    public static void putInt(String key, int value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putInt(key, value).commit();
    }


    public static int getInt(String key, int defValue, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        return sp.getInt(key, defValue);
    }

    public static void putLong(String key, long value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putLong(key, value).commit();
    }


    public static long getLong(String key, long defValue, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        return sp.getLong(key, defValue);
    }

    public static void remove(String key, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().remove(key).commit();
    }

}

也是很簡單的代碼,相信你一眼就看明白了,下面就該在MainActivity中去處理快取資料的存取了,
首先是放快取,這當然是在請求介面的成功資料回傳中放,修改onSuccess中的代碼,如下,

					@Override
                    public void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {
                        if (response.body() != null) {
                            //鑒權Token
                            accessToken = response.body().getAccess_token();
                            //過期時間 秒
                            long expiresIn = response.body().getExpires_in();
                            //當前時間 秒
                            long currentTimeMillis = System.currentTimeMillis() / 1000;
                            //放入快取
                            SPUtils.putString(Constant.TOKEN, accessToken, MainActivity.this);
                            SPUtils.putLong(Constant.GET_TOKEN_TIME, currentTimeMillis, MainActivity.this);
                            SPUtils.putLong(Constant.TOKEN_VALID_PERIOD, expiresIn, MainActivity.this);
                        }
                    }

然后寫一個判斷Token是否過期的方法,方法代碼如下:

	/**
     * Token是否過期
     *
     * @return
     */
    private boolean isTokenExpired() {
        //獲取Token的時間
        long getTokenTime = SPUtils.getLong(Constant.GET_TOKEN_TIME, 0, this);
        //獲取Token的有效時間
        long effectiveTime = SPUtils.getLong(Constant.TOKEN_VALID_PERIOD, 0, this);
        //獲取當前系統時間
        long currentTime = System.currentTimeMillis() / 1000;

        return (currentTime - getTokenTime) >= effectiveTime;
    }

這個方法也是很好理解的,首先取出快取中的獲取Token的時間,然后獲取Token的有效時長,再獲取當前系統時間,然后通過當前系統時間減去獲得Token的時間,得到的值再與Token有效期做比較,如果大于等于有效期則說明Token過期,回傳true,否則回傳false,

下面再寫一個方法,用來獲取Token,同時將我們之前寫的代碼給串起來,

	/**
     * 獲取鑒權Token
     */
    private String getAccessToken() {
        String token = SPUtils.getString(Constant.TOKEN, null, this);
        if (token == null) {
            //訪問API獲取介面
            requestApiGetToken();
        } else {
            //則判斷Token是否過期
            if (isTokenExpired()) {
                //過期
                requestApiGetToken();
            } else {
                accessToken = token;
            }
        }
        return accessToken;
    }

首先獲取快取中的Token,應用第一次進入肯定是沒有值的,沒有值則回傳默認值null,那么token變數此時為null,那么就會通過介面去獲取Token,當獲取之后存入快取,再次進入時,就不是null了,那么就會通過isTokenExpired()方法來判斷Token是否過期,過期了也是通過網路請求重新拿到Token放入快取,如果沒有過期則直接使用快取中的Token,最后回傳Token,

六、網路圖片Url識別

Token拿到以后我們來進行網路圖片Url識別,先說一下思路,首先是通過網路圖片url和Token去請求介面,然后獲得回傳值,此時要顯示一個加載條,然后通過回傳資料渲染串列,當資料顯示在串列之后就完成了,

首先找一個網路圖片Url,如下:

https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg

這個網路圖片是一個水杯的圖片,如下所示:
在這里插入圖片描述
首先修改布局activity_main.xml,里面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv_picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />

    <LinearLayout
        android:layout_marginBottom="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="IdentifyWebPictures"
            android:text="識別網路圖片" />

    </LinearLayout>

    <ProgressBar
        android:visibility="gone"
        android:id="@+id/pb_loading"
        android:layout_centerInParent="true"
        android:layout_width="60dp"
        android:layout_height="60dp"/>


</RelativeLayout>

然后在MainActivity中寫入IdentifyWebPictures方法,代碼如下:

	/**
     * 識別網路圖片
     *
     * @param view
     */
    public void IdentifyWebPictures(View view) {
    	
    }

首先創建物件

	/**
     * 顯示圖片
     */
    private ImageView ivPicture;
    /**
     * 進度條
     */
    private ProgressBar pbLoading;

然后在onCreate中系結xml中的控制元件id,

		ivPicture = findViewById(R.id.iv_picture);
        pbLoading = findViewById(R.id.pb_loading);

下面來修改IdentifyWebPictures()方法的代碼,如下:

	public void IdentifyWebPictures(View view) {
        pbLoading.setVisibility(View.VISIBLE);
        String token = getAccessToken();
        String imgUrl = "https://bce-baiyu.cdn.bcebos.com/14ce36d3d539b6004ef2e45fe050352ac65cb71e.jpeg";
        //顯示圖片
        Glide.with(this).load(imgUrl).into(ivPicture);
        showMsg("影像識別中");
        service.getDiscernResult(token, imgUrl).enqueue(new NetCallBack<GetDiscernResultResponse>() {
            @Override
            public void onSuccess(Call<GetDiscernResultResponse> call, Response<GetDiscernResultResponse> response) {
                List<GetDiscernResultResponse.ResultBean> result = response.body() != null ? response.body().getResult() : null;
                if (result != null && result.size() > 0) {
                    //顯示識別結果
                    showDiscernResult(result);
                } else {
                    pbLoading.setVisibility(View.GONE);
                    showMsg("未獲得相應的識別結果");
                }
            }

            @Override
            public void onFailed(String errorStr) {
                pbLoading.setVisibility(View.GONE);
                Log.e(TAG, "影像識別失敗,失敗原因:" + errorStr);
            }
        });
    }

當點擊按鈕時,顯示進度條,然后通過getAccessToken()方法獲取Token,之后顯示網路圖片在ImageView控制元件中,Toast提示一下,之后請求的成功和失敗的回呼了,在成功的回呼中先判斷資料是否為空,不為空再通過showDiscernResult()方法去顯示資料,下面寫這個方法,

	/**
     * 顯示識別的結果串列
     *
     * @param result
     */
    private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {
        
    }

showMsg方法:

	/**
     * Toast提示
     * @param msg 內容
     */
    private void showMsg(String msg){
        Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
    }

為了不占用螢屏的控制元件,我這里打算用一個彈窗來顯示資料,彈窗里面是一個串列,串列通過item布局構建,資料由剛才的方法傳遞進來,我們一步一步來寫,首先構建item的布局,在layout下新建一個item_result_rv.xml,里面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:padding="16dp"
    android:background="#FFF"
    android:layout_marginBottom="1dp"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv_keyword"
        android:textSize="16sp"
        android:textColor="#000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:layout_marginTop="@dimen/dp_4"
        android:layout_below="@+id/tv_keyword"
        android:id="@+id/tv_root"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tv_score"
        android:layout_alignParentEnd="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

item布局有了,下面構建彈窗的布局代碼,在layout下新建一個 dialog_bottom.xml,里面的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#EEE"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFF"
        android:gravity="center"
        android:padding="16dp"
        android:text="識別結果" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="1dp" />

</LinearLayout>

下面布局都有了,先構建這個串列的配接器,在com.llw.imagediscerndemo下新建一個adapter包,包下新建一個DiscernResultAdapter類,里面的代碼如下:

package com.llw.imagediscerndemo.adapter;

import androidx.annotation.Nullable;

import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.llw.imagediscerndemo.R;
import com.llw.imagediscerndemo.model.GetDiscernResultResponse;

import java.util.List;

/**
 * 識別結果串列配接器
 * @author llw
 */
public class DiscernResultAdapter extends BaseQuickAdapter<GetDiscernResultResponse.ResultBean, BaseViewHolder> {
    public DiscernResultAdapter(int layoutResId, @Nullable List<GetDiscernResultResponse.ResultBean> data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, GetDiscernResultResponse.ResultBean item) {
        helper.setText(R.id.tv_keyword,item.getKeyword())
                .setText(R.id.tv_root,item.getRoot())
                .setText(R.id.tv_score,String.valueOf(item.getScore()));
    }
}

萬事具備,只差顯示資料了,下面進入MainActivity中,首先創建物件

	/**
     * 底部彈窗
     */
    private BottomSheetDialog bottomSheetDialog;
    /**
     * 彈窗視圖
     */
    private View bottomView;

然后在onCreate中實體化,

	bottomSheetDialog = new BottomSheetDialog(this);
    bottomView = getLayoutInflater().inflate(R.layout.dialog_bottom, null);

然后修改showDiscernResult方法,代碼如下:

	private void showDiscernResult(List<GetDiscernResultResponse.ResultBean> result) {
        bottomSheetDialog.setContentView(bottomView);
        bottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
        RecyclerView rvResult = bottomView.findViewById(R.id.rv_result);
        DiscernResultAdapter adapter = new DiscernResultAdapter(R.layout.item_result_rv, result);
        rvResult.setLayoutManager(new LinearLayoutManager(this));
        rvResult.setAdapter(adapter);
        //隱藏加載
        pbLoading.setVisibility(View.GONE);
        //顯示彈窗
        bottomSheetDialog.show();
    }

下面運行一下:
在這里插入圖片描述
可以看到結果識別到了,

七、相冊圖片識別

在實際應用中,更多是采用本地的圖片進行識別,通常是選擇拍照的圖片或者打開相冊獲取圖片,先來看看通過相冊獲取圖片進行影像識別,要實作這個功能首先要改一下介面,加一個image引數,

在這里插入圖片描述
然后修改ImageDiscern方法,

	/**
     * 影像識別請求
     *
     * @param token       token
     * @param imageBase64 圖片Base64
     * @param imgUrl      網路圖片Url
     */
    private void ImageDiscern(String token, String imageBase64, String imgUrl) {
        service.getDiscernResult(token, imageBase64, imgUrl).enqueue(new NetCallBack<GetDiscernResultResponse>() {
            @Override
            public void onSuccess(Call<GetDiscernResultResponse> call, Response<GetDiscernResultResponse> response) {
                List<GetDiscernResultResponse.ResultBean> result = response.body() != null ? response.body().getResult() : null;
                if (result != null && result.size() > 0) {
                    //顯示識別結果
                    showDiscernResult(result);
                } else {
                    pbLoading.setVisibility(View.GONE);
                    showMsg("未獲得相應的識別結果");
                }
            }

            @Override
            public void onFailed(String errorStr) {
                pbLoading.setVisibility(View.GONE);
                Log.e(TAG, "影像識別失敗,失敗原因:" + errorStr);
            }
        });
    }

這個方法接收三個引數,Token、ImageBase64、圖片Url,ImageBase64和圖片Url只能二選一,選其中一個另一個則傳null,比如之前的通過網路圖片Url識別,
在這里插入圖片描述
介面的相關方法都改好了,下面來寫打開相冊的方法,Android6.0以后讀寫檔案都屬于危險權限,因此需要動態請求,在MainActivity中宣告:

	private RxPermissions rxPermissions;

然后在onCreate中實體化

	rxPermissions = new RxPermissions(this);

下面修改布局,在之前的按鈕后面再加一個按鈕

	<Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="IdentifyAlbumPictures"
            android:text="識別相冊圖片" />

然后在MainActivity中增加IdentifyAlbumPictures方法,代碼如下:

	/**
     * 識別相冊圖片
     *
     * @param view
     */
    @SuppressLint("CheckResult")
    public void IdentifyAlbumPictures(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            rxPermissions.request(
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .subscribe(grant -> {
                        if (grant) {
                            //獲得權限
                            openAlbum();
                        } else {
                            showMsg("未獲取到權限");
                        }
                    });
        } else {
            openAlbum();
        }
    }

當獲取到權限之后通過openAlbum()方法打開相冊,openAlbum方法代碼如下:

	/**
     * 打開相冊
     */
    private void openAlbum() {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_PICK);
        intent.setType("image/*");
        startActivityForResult(intent, OPEN_ALBUM_CODE);
    }

這里定義了一個請求碼

	/**
     * 打開相冊
     */
    private static final int OPEN_ALBUM_CODE = 100;

打開相冊之后就要回傳了,重寫 onActivityResult方法

	@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            pbLoading.setVisibility(View.VISIBLE);
            if (requestCode == OPEN_ALBUM_CODE) {
                //打開相冊回傳
                String[] filePathColumns = {MediaStore.Images.Media.DATA};
                final Uri imageUri = Objects.requireNonNull(data).getData();
                Cursor cursor = getContentResolver().query(imageUri, filePathColumns, null, null, null);
                cursor.moveToFirst();
                int columnIndex = cursor.getColumnIndex(filePathColumns[0]);
                //獲取圖片路徑
                String imagePath = cursor.getString(columnIndex);
                cursor.close();
                //識別
                localImageDiscern(imagePath);
            } 
        } else {
            showMsg("什么都沒有");
        }
    }

相冊回傳之后先拿到圖片的Uri,然后通過Uri得到圖片的路徑,然后通過這個路徑將圖片轉成位元組,再轉Base64,首先來看localImageDiscern方法,代碼如下:

	/**
     * 本地圖片識別
     */
    private void localImageDiscern(String imagePath) {
        try {
            String token = getAccessToken();
            //通過圖片路徑顯示圖片
            Glide.with(this).load(imagePath).into(ivPicture);
            //按位元組讀取檔案
            byte[] imgData = FileUtil.readFileByBytes(imagePath);
            //位元組轉Base64
            String imageBase64 = Base64Util.encode(imgData);
            //影像識別
            ImageDiscern(token, imageBase64, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

這里面有兩個工具類FileUtil和Base64Util,代碼如下:

FileUtil.java

package com.llw.imagediscerndemo.util;

import java.io.*;

/**
 * 檔案讀取工具類
 */
public class FileUtil {

    /**
     * 讀取檔案內容,作為字串回傳
     */
    public static String readFileAsString(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException(filePath);
        } 

        if (file.length() > 1024 * 1024 * 1024) {
            throw new IOException("File is too large");
        } 

        StringBuilder sb = new StringBuilder((int) (file.length()));
        // 創建位元組輸入流  
        FileInputStream fis = new FileInputStream(filePath);  
        // 創建一個長度為10240的Buffer
        byte[] bbuf = new byte[10240];  
        // 用于保存實際讀取的位元組數  
        int hasRead = 0;  
        while ( (hasRead = fis.read(bbuf)) > 0 ) {  
            sb.append(new String(bbuf, 0, hasRead));  
        }  
        fis.close();  
        return sb.toString();
    }

    /**
     * 根據檔案路徑讀取byte[] 陣列
     */
    public static byte[] readFileByBytes(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException(filePath);
        } else {
            ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
            BufferedInputStream in = null;

            try {
                in = new BufferedInputStream(new FileInputStream(file));
                short bufSize = 1024;
                byte[] buffer = new byte[bufSize];
                int len1;
                while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
                    bos.write(buffer, 0, len1);
                }

                byte[] var7 = bos.toByteArray();
                return var7;
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException var14) {
                    var14.printStackTrace();
                }

                bos.close();
            }
        }
    }
}

Base64Util.java

package com.llw.imagediscerndemo.util;

/**
 * Base64 工具類
 */
public class Base64Util {
    private static final char last2byte = (char) Integer.parseInt("00000011", 2);
    private static final char last4byte = (char) Integer.parseInt("00001111", 2);
    private static final char last6byte = (char) Integer.parseInt("00111111", 2);
    private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
    private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
    private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
    private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

    public Base64Util() {
    }

    public static String encode(byte[] from) {
        StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
        int num = 0;
        char currentByte = 0;

        int i;
        for (i = 0; i < from.length; ++i) {
            for (num %= 8; num < 8; num += 6) {
                switch (num) {
                    case 0:
                        currentByte = (char) (from[i] & lead6byte);
                        currentByte = (char) (currentByte >>> 2);
                    case 1:
                    case 3:
                    case 5:
                    default:
                        break;
                    case 2:
                        currentByte = (char) (from[i] & last6byte);
                        break;
                    case 4:
                        currentByte = (char) (from[i] & last4byte);
                        currentByte = (char) (currentByte << 2);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
                        }
                        break;
                    case 6:
                        currentByte = (char) (from[i] & last2byte);
                        currentByte = (char) (currentByte << 4);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
                        }
                }

                to.append(encodeTable[currentByte]);
            }
        }

        if (to.length() % 4 != 0) {
            for (i = 4 - to.length() % 4; i > 0; --i) {
                to.append("=");
            }
        }

        return to.toString();
    }
}

都放在util包下,那么現在就可以直接運行了,
在這里插入圖片描述
通過這個圖可以看到第一次識別失敗了,第二次成功了,后續的都會成功,不知道是什么奇葩原因,有知道的記得告訴我啊,

八、拍照圖片識別

首先還在在activity_main.xml中識別相冊圖片按鈕的后面加一個識別拍照圖片按鈕,如下:

		<Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="IdentifyTakePhotoImage"
            android:text="識別拍照圖片" />

在MainActivity中增加IdentifyTakePhotoImage方法,代碼如下:

	/**
     * 識別拍照圖片
     *
     * @param view
     */
    @SuppressLint("CheckResult")
    public void IdentifyTakePhotoImage(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            rxPermissions.request(
                    Manifest.permission.CAMERA)
                    .subscribe(grant -> {
                        if (grant) {
                            //獲得權限
                            turnOnCamera();
                        } else {
                            showMsg("未獲取到權限");
                        }
                    });
        } else {
            turnOnCamera();
        }
    }

來看看turnOnCamera方法,在此之前創建變數,用來保存拍照后的圖片

	private File outputImage;

turnOnCamera方法

	/**
     * 打開相機
     */
    private void turnOnCamera() {
        SimpleDateFormat timeStampFormat = new SimpleDateFormat("HH_mm_ss");
        String filename = timeStampFormat.format(new Date());
        //創建File物件
        outputImage = new File(getExternalCacheDir(), "takePhoto" + filename + ".jpg");
        Uri imageUri;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            imageUri = FileProvider.getUriForFile(this,
                    "com.llw.imagediscerndemo.fileprovider", outputImage);
        } else {
            imageUri = Uri.fromFile(outputImage);
        }
        //打開相機
        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, TAKE_PHOTO_CODE);
    }

這里同樣配置了一個打開相機的請求碼

	/**
     * 打開相機
     */
    private static final int TAKE_PHOTO_CODE = 101;

下面進入到onActivityResult方法,加一個條件分支,
在這里插入圖片描述
通過這個圖片保存檔案得到圖片的路徑,然后通過localImageDiscern()方法對這個路徑下的檔案進行處理,和打開相冊之后拿到路徑之后呼叫的是同一個方法,下面來運行一下:
在這里插入圖片描述
嗯,那么到這里整個Demo就寫完了,是不是還挺簡單的,只要思路明確再加上細節處理的到位,任何的功能都不在話下,對吧,

九、原始碼

考慮到有時候GitHub會抽風,因此加上了CSDN的資源下載地址,我設定的0積分,隨便下載,

如果你用的好,不妨給我的原始碼或者資源點個贊給個好評啥的,

GitHub原始碼地址:ImageDiscernDemo

CSDN資源地址:ImageDiscernDemo.rar

掃碼下載APK使用:
在這里插入圖片描述

我是初學者-Study,山高水長,后會有期~

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/273266.html

標籤:其他

上一篇:Android中 UI界面的常用組件

下一篇:Android中 Intent的常用方法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more