Android(6) —— 主函数的详解 MainActivity.java

前言

当前,深度学习有很多框架:tensorflow、pytorch、caffe、keras等。很多场景下,需要训练好的模型在移动端运行。移动端的框架又有很多TensorFlow Lite、Core ML、NCNN、MNN等等。
其中 tensorflow 所对应的移动端移植框架 TensorFlow Lite。在自己这个系列记录使用 调用tfile进行神经网络预测的android的实现。

整个记录为:
Android(1) —— Android studio 开发环境搭建
Android(2) —— Android Studio找不到连接的手机
Android(3) —— 环境配置、手机端界面设计
Android(4) —— 图像分类的*.tfile的使用 Classify.java
Android(5) —— 安卓机通过相机或相册获取图片PhotoUtil.java
Android(6) —— 主函数的详解 MainActivity.java

1 代码讲解

1.1 代码概述

该脚定义了在安卓端上实现神经网络(图片分类)的移植的主函数。是通过定义了public class ClassifyLib 来实现的。会调用Classify.javaPhotoUtil.java两个脚本(这两个脚本前面的博客有详细的讲解)


先定义了类内的所需变量

  • 三个按键:用于加载神经网络模型的按键,另外两个用于选择照片的来源:使用相机拍照的按键、从相册中选择照片的按键。
  • 两个展示区域:获取到图片的展示区域,神经网络预测的结果展示区域。
  • 实例化 Classify类,完成神经网络模型的加载、预测、结果解析的功能
public class MainActivity extends AppCompatActivity {

   private static final String TAG = MainActivity.class.getName();
   private static final int USE_PHOTO = 1001;
   private static final int START_CAMERA = 1002;
   private String camera_image_path;
   
   private ImageView show_image;    // 手机界面的图像展示区域
   private TextView result_text;           // 手机界面的文字展示区域
   private Button load_model;             // 按键:加载模型
   private Button use_photo;               // 按键:使用相册图片
   private Button start_photo;              // 按键:使用相机拍照
   
   private boolean load_result = false; // 是否加载模型成功
   private Context context = null;
   
   private static final int REQUEST_EXTERNAL_STORAGE = 1;   // 请求外部存储器
   private static String[] PERMISSIONS_STORAGE = {
           "android.permission.READ_EXTERNAL_STORAGE",         // 读取外部存储器
           "android.permission.WRITE_EXTERNAL_STORAGE" };     // 写外部存储器
           
   Classify test = new Classify(); // 新建一个对象,用于调用神经网络模型相关内容
   

1.2 onCreate 的重写

1.2.1 onCreate 方法的简单介绍

  • Activity中有一个名称叫 onCreate 的方法。该方法是在Activity创建时被系统调用,是一个Activity生命周期的开始。参数名称为savedInstanceState。
  • Activity中另一个名称 onsaveInstanceState 方法。该方法是用来保存Activity的状态的。当一个Activity在生命周期结束前,会调用该方法保存状态。参数名称为savedInstanceState。
public void onCreate(Bundle savedInstanceState){
   super.onCreate(savedInstanceState);
}

public void onSaveInstanceState(Bundle savedInstanceState){
   super.onSaveInstanceState(savedInsanceState);
}

在实际应用中,当一个Activity结束前,如果需要保存状态,就在onsaveInstanceState中, 将状态数据以key-value的形式放入到savedInstanceState中。这样,当一个Activity被创建时,就能从onCreate的参数savedInstanceState中获得状态数据。
比如:一个游戏在退出前,保存一下当前游戏运行的状态,当下次开启时能接着上次的继续玩下去。再比如:电子书程序,当一本小说被阅读到第199页后退出了(不管是内存不足还是用户自动关闭程序),当下次打开时,读者可能已忘记了上次已阅读到第几页了,但是,读者想接着上次的读下去。如果采用savedInstancsState参数,就很容易解决上述问题。


1.2.2 onCreate 的重写

  • 由于会使用到读取相册,或者使用相机,所以要检测和申请相关权限。
  • 然后要对三个按键进行初始化,进行监听,当触发时完成相应的功能
  • 如果不是验证集(具有标签),就不用执行加载标签文件的操作
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   
   setContentView(R.layout.activity_main);
   verifyStoragePermissions(this);           // 验证存储权限
   context = getApplicationContext();
   init_view();                               // 三个初始化
   test.readCacheLabelFromLocalFile(context); // 加载标签文件,在 Classify.java脚本中实现
}

1.3 按键的初始化 init_view

在资源目录 res 文件夹下的所有资源(文件以及信息),系统都会自动在 gen 目录中的 R.java文件中生成一个对应且唯一的 id。我们可以通过这个唯一的 id去查找当前对应的相关内容。gen 目录下的所有文件是不需要我们手动添加和修改的,它是会自动在编译中生成,系统会自动对其进行操作。

  • R.id.*** 其中的R指的是 gen 目录中的 R.java 文件;R.id.xxx 指的就是 R.java 中 xxx 的具体id。该控件 id 在我们编辑 xml 你就文件的时候给控件添加 Android::id 属性添加,R文件中会自动生成与之对应的id值。所以我们访问R.id.xxx 时,就会返回系统自动生成的 id 值。
  • findViewById 安卓编程的定位函数,主要是引用.R文件里的引用名。
    例如:Button button=(Button)findViewById(R.id.button01); 这样就引用了XML(res里的布局文件)文件里面的button,使得在写.java的按钮时能与XML里的一致。
private void init_view() {
   request_permissions();
   show_image = (ImageView) findViewById(R.id.show_image);
   result_text = (TextView) findViewById(R.id.result_text);
   result_text.setMovementMethod(ScrollingMovementMethod.getInstance());
   load_model = (Button) findViewById(R.id.load_model);
   use_photo = (Button) findViewById(R.id.use_photo);
   start_photo = (Button) findViewById(R.id.start_camera);
   

监听事件有多种写法,这里使用的是 【匿名内部类实现事件监听】

  • findViewById 代表着设置时间处理的监听器
  • new View.OnClickListener() 代表时间监听器的接口
  • onClick 代表着事件处理函数,也就是点击了 Button 按键后,就会进入到onClick函数里面,执行相应的功能

这里对三个按键分别设置了监听器

  • 加载模型的点击事件:触发该按钮,就进行 *.tfile模型的读取
  • 相册选择图片的点击事件:该功能的实现在PhotoUtil.java中实现
  • 拍照按钮的点击事件:该功能的实现在PhotoUtil.java中实现
  // 加载模型的点击事件
   load_model.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           load_result = test.load_model(context);//加载模型,在 Classify.java脚本中实现
           Toast.makeText(MainActivity.this, "模型加载已完成", Toast.LENGTH_SHORT).show();
       }
   });

   // 相册选择图片的点击事件
   use_photo.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           if (!load_result) {
               Toast.makeText(MainActivity.this, "never load model", Toast.LENGTH_SHORT).show();
               return;
           }
           PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);//从相册获得一张图片
       }
   });

   // 拍照按钮的点击事件实现
   start_photo.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           if (!load_result) {
               Toast.makeText(MainActivity.this, "never load model", Toast.LENGTH_SHORT).show();
               return;
           }
           System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
           camera_image_path = PhotoUtil.start_camera(MainActivity.this, START_CAMERA); >//从拍照获得一张图片的临时路径
           System.out.println(camera_image_path);
           System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
       }
   });
}

1.4 获取到图片后的操作 onActivityResult


1.4.1 基础知识的简单介绍

Activity的启动涉及到两个Activity——父Activity和子Activity,

  • 父Activity是启动方,即调用startActivity()方法一方,
  • 子Activity是被启动方,即被startActivity()方法打开的一方。

接下来分析,实现父Activity打开子Activity的方法 startActivityForResult(),和实现子Activity回传结果给父 Activity的方法 setResult()onActivityResult()

  1. 使用 intent extra 附加数据
    Inter 是四大组件之间和四大组间与操作系统之间通信的一种媒介工具。我们将需要传递的数据作为 entra 信息,附加给 startActivity(Intent) 方法的 Inter 上发送出去。extra是一种 key-value 结构。
    调用 Inter.putExtra() 方法将 extra 信息添加给 inter,Inter.putExtra() 方法有很多种形式。(1) 一个参数固定为 String 类型的 Key。(2) 一个参数值可以是多种数据类型:包括8中剧本数据类型 和 实现了 Serializable接口的类的实例对象。子 activity调用对应的 getXXextra()方法对extra进行解析即可获取父 activity发过来的数据。

  2. 父 acitvity 调用 startActivityForResult() 方法,启动子activity
    父 activity为了从子 activity中获取返回结果,所以调用启动子 activity的另外一种方法startActivityForResult(Intent intent, intrequestCode),该方法接收两个参数:(1) 一个是携带了 extra的 Intent (2) 另一个参数是请求代码 requestCode。
    请求代码是发送给子 activity,然后再返回给父 activity的用户定义整数值。(当一个activity启动多个不同类型的字 activity,且需要判断区分消息回馈放时,会要使用到请求码进行区分)

  3. 子 acitvity 调用 setResult() 方法,回传数据给父 activity
    实现子 activity返回值给父 activity,有以下两种 setResult() 方法可供调用:
    public final void setResult(int resultCode)
    public final void setResult(int resultCode), 通常来说,参数 result-code 可以是以下两个系统预定义常量中的任何一个:【ACTIVITY.RESULT_OK】、【 ACTIVITY_RESULT_CANCELED】。 使用 setResult() 方法代码附在后面。

    如果子 activity 是 startActivityForResult() 方法启动的,结果代码则总是会返回给父 activity。子 activity可以不调用 setResult() 方法,如果不需要区分附加在 intent上的结果或者其他信息,可以让操作系统发送默认的结果代码。

Intent data = new Intent();
data.putExtra(EXTRA_ANSWER_SHOWN, isAnswerShown);
setResult(RESULT_OK, data);

  1. 父 activity 调用 onActivityResult() 方法接收子 activity 返回的数据
    在用户单击后退,从子 activity回到父 activity时,ActivityManager会调用父 activity的onActivityResult(int requestCode,int resultCode, Intent data) 方法。该方法接收三个参数:
    • requestCode :请求的标识,用于分辨父activity获取到的数据来源于哪个子 activity
    • resultCode :子activity返回的标识,代表着返回数据是否成功
    • Intent :setResult() 返回的结果

1.4.2 onActivityResult 的具体定义

在本工程中这个命令执行的时间:

  • (1) 拍完照片,返回到了软件打开时的界面后执行;
  • (2) 从相册中选择了要用于神经网络预测的照片后,返回到了软件打开时的界面之后执行。

执行的内容:

  • 获取到的图像的路径,并展示图片
  • 读取图片、CNN预测、返回预测结果并展示(在Classify.java脚本中实现)
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
   super.onActivityResult(requestCode,resultCode,data);
   String image_path;
   RequestOptions options = new >RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);
   if (resultCode == Activity.RESULT_OK) {
       switch (requestCode) {
           // 当选择使用相册中的照片
           case USE_PHOTO:
           if (data == null) {
                   Log.w(TAG, "user photo data is null");
                   return;
               }
               // 获取到图片的 Uri
               Uri image_uri = data.getData();
               // 在手机图像展示区域,展示图像
               Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);
               // get image path from uri
               image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
               System.out.println("----------------------------------------------------------------");
               System.out.println(image_path);
               System.out.println("----------------------------------------------------------------");
               // predict image
               //predict_image(image_path);
               BitmapFactory.Options opt = new BitmapFactory.Options();
               opt.inJustDecodeBounds = false;
               Bitmap bmp = BitmapFactory.decodeFile(image_path, opt);
               String tmp1 = test.predict_image(bmp);//执行推理与预测
               result_text.setText(tmp1);
               break;
           case START_CAMERA:
               // show photo
               Glide.with(MainActivity.this).load(camera_image_path).apply(options).into(show_image);
               // predict image
               BitmapFactory.Options opt1 = new BitmapFactory.Options();
               opt1.inJustDecodeBounds = false;
               Bitmap bmp1 = BitmapFactory.decodeFile(camera_image_path, opt1);
               String tmp2 = test.predict_image(bmp1);//执行推理与预测
               result_text.setText(tmp2);
               break;
       }
   }
}

1.5 检测和申请相关权限

Android 权限相关内容,稍微有点长,所以在这个链接中进行介绍。下面直接展示在该工程中的代码编写。

1.5.1 验证存储权限

public static void verifyStoragePermissions(Activity activity) {
   try {
       //检测是否有写的权限
       int permission = ActivityCompat.checkSelfPermission(activity,
               "android.permission.WRITE_EXTERNAL_STORAGE");
       if (permission != PackageManager.PERMISSION_GRANTED) {
           // 没有写的权限,去申请写的权限,会弹出对话框
           ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
       }
   } catch (Exception e) {
       e.printStackTrace();
   }
}

1.5.2 检测和申请权限

// request permissions
private void request_permissions() {

   List<String> permissionList = new ArrayList<>();
   if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
       permissionList.add(Manifest.permission.CAMERA);
   }

   if (ContextCompat.checkSelfPermission(this, >Manifest.permission.WRITE_EXTERNAL_STORAGE) != >PackageManager.PERMISSION_GRANTED) {
       permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
   }

   if (ContextCompat.checkSelfPermission(this, >Manifest.permission.READ_EXTERNAL_STORAGE) != >PackageManager.PERMISSION_GRANTED) {
       permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
   }

   // if list is not empty will request permissions
   if (!permissionList.isEmpty()) {
       ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
   }
}

1.5.3 权限的回调函数

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, >@NonNull int[] grantResults) {
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);
   switch (requestCode) {
       case 1:
           if (grantResults.length > 0) {
               for (int i = 0; i < grantResults.length; i++) {

                   int grantResult = grantResults[i];
                   if (grantResult == PackageManager.PERMISSION_DENIED) {
                       String s = permissions[i];
                       Toast.makeText(this, s + " permission was denied", Toast.LENGTH_SHORT).show();
                   }
               }
           }
           break;
   }
}
  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值