20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结

目录

  • 学习《Java和Android开发学习指南(第二版)》第43、44、45、46章——
  • 教材学习中的问题和解决过程(^▽^)
  • 代码调试中的问题和解决过程
  • 上周错题总结
  • [代码托管]
  • statistics.sh脚本运行结果的截图
  • 学习进度条
  • 参考资料

学习《Java和Android开发学习指南(第二版)》第43、44、45、46章——

第43章制作视频
有两种方法可以给自己的应用程序配备视频拍摄功能。第1种方法,也是较为容易的一种方法,是创建默认的意图并将其传递给startActivityForResult。第2种方法,是直接使用MediaRecorder。这个方法更难一些,但是它可以使用设备相机的全部功能。
43.1使用内建意图
1.如果要保存或处理捕获的视频,必须覆盖onActivityResult方法。系统通过传递3个参数来调用onActivityResult方法。第1个参数是requestCode,这是调用startActivityForResult方法的时候传入的请求代码;onActivityResult方法的第二个参数是一个结果代码;第三个参数包含了来自相机的数据。
2.代码清单43.1AndroidManifest.xml文件




    

    

    
        
            
                
                
            
        
    

3.代码清单43.2 菜单文件(menu_main.xml)


    

4.代码清单43.3 activity_main.xml文件


    
    

5.代码清单43.4MainActivity类

package com.example.videodemo;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

public class MainActivity extends Activity {
    private static final int REQUEST_CODE = 200;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_camera:
                showCamera();
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    private void showCamera() {
        // cannot set the video file 
        Intent intent = new Intent(
                MediaStore.ACTION_VIDEO_CAPTURE);
        // check if the device has a camera: 
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, REQUEST_CODE);
        } else {
            Toast.makeText(this, "Opening camera failed",
                    Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onActivityResult(int requestCode,
                                    int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                if (data != null) {
                    Uri uri = data.getData();
                    VideoView videoView = (VideoView)
                            findViewById(R.id.videoView);

                    videoView.setVideoURI(uri);
                    videoView.setMediaController(
                            new MediaController(this));
                    videoView.requestFocus();
                }
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(this, "Action cancelled",
                        Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "Error", Toast.LENGTH_LONG)
                        .show();
            }
        }
    }
}

当用户离开相机的时候,会调用onActivityResult方法。如果结果代码是RESULT_OK并且data不为空,该方法会在data上调用getData方法,以得到一个指向视频位置的Uri。接下来,它找到VideoView微件并设置其videoURI属性,然后调用VideoView上的其他两个方法,即setMediaController和requestFocus。媒体控制器MediaController可以用来播放和停止视频,requestFocus()来为微件设置焦点。

!真机测试VideoDemo

20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第1张图片
在自己的安卓设备上调试:(1)图标;(2)主界面;(3)图书馆录制(4)保存到本地
43.2MediaRecorder
MediaRecorder有三种状态,包括:Initialized状态、DataSourceConfigured状态和Prepared状态。状态图如下:
20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第2张图片
43.3使用MediaRecorder
VideoRecorder应用程序展示了如何使用MediaRecorder来录制一个视频。它拥有一个活动,其中包含了一个按钮和一个SurfaceView。按钮用来启动和停止录制,而SurfaceView用来显示相机看到了什么。
1.代码清单43.5布局文件(activity_main.xml)



    

2.代码清单43.6MainActivity类

package com.example.videorecorder;
import java.io.File;
import java.io.IOException;
import android.app.Activity;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
    private MediaRecorder mediaRecorder;
    private File outputDir;
    private boolean recording = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        File moviesDir = Environment
                .getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_MOVIES);
        outputDir = new File(moviesDir,
                "VideoRecorder");
        outputDir.mkdirs();
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mediaRecorder = new MediaRecorder();
        initAndConfigureMediaRecorder();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (recording) {
            try {
                mediaRecorder.stop();
            } catch (IllegalStateException e) {
            }
        }
        releaseMediaRecorder();
        Button button = (Button) findViewById(R.id.button1);
        button.setText("Start");
        recording = false;
    }

    private void releaseMediaRecorder() {
        if (mediaRecorder != null) {
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
        }
    }

    private void initAndConfigureMediaRecorder() {
        mediaRecorder.setAudioSource(
                MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder
                .setVideoSource(MediaRecorder.VideoSource.CAMERA);
        mediaRecorder.setOutputFormat(
                MediaRecorder.OutputFormat.MPEG_4);
        mediaRecorder.setVideoFrameRate(10);// make it very low 
        mediaRecorder.setVideoEncoder(
                MediaRecorder.VideoEncoder.MPEG_4_SP);
        mediaRecorder.setAudioEncoder(
                MediaRecorder.AudioEncoder.AMR_NB);
        String outputFile = new File(outputDir,
                System.currentTimeMillis() + ".mp4")
                .getAbsolutePath();

        mediaRecorder.setOutputFile(outputFile);
        SurfaceView surfaceView = (SurfaceView)
                findViewById(R.id.surfaceView);
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        mediaRecorder.setPreviewDisplay(surfaceHolder
                .getSurface());
    }

    public void startStopRecording(View view) {
        Button button = (Button) findViewById(R.id.button1);
        if (recording) {
            button.setText("Start");
            try {
                mediaRecorder.stop();
            } catch (IllegalStateException e) {

            }
            releaseMediaRecorder();
        } else {
            button.setText("Stop");
            if (mediaRecorder == null) {
                mediaRecorder = new MediaRecorder();
                initAndConfigureMediaRecorder();
            }
            // prepare MediaRecorder 
            try {
                mediaRecorder.prepare();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            mediaRecorder.start();
        }
        recording = !recording;
    }
}

20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第3张图片
其中有三个重要的方法:onCreate方法、onResume方法和onPause方法。
第44章声音录制
44.1MediaRecorder类
MediaRecorder类可以采样声音或噪声层级。MediaRecorder类用于记录音频和视频,其输出可以写入到一个文件,可以很容易地选择输入源。有:start、stop、reset和release方法。
44.2示例
1.代码清单44.1SoundMeter的清单




    

    

    
        
            
                
                
            
        
    

使用清单中的uses-permission元素来请求用户许可录制视频。如果没有包括这个元素的话,应用程序将无法工作。此外,如果用户不同意的话,应用程序将不会安装。
2.代码清单44.2SoundMeter中的res/layout/activity_main.xml文件



    

    

该应用程序有两个类。第一个类是一个名为SoundMeter的类,它封装了MediaRecorder并且暴露了3个方法来管理它。第一个方法是start,创建了MediaRecorder的一个实例,配置并且启动它。第二个方法stop,停止了MediaRecorder。第3个方法是getAmplitude,返回一个double类型数据以表明采样声音的层级。
3.代码清单44.3SoundMeter类

package com.example.soundmeter;
import java.io.IOException;
import android.media.MediaRecorder;

public class SoundMeter {

    private MediaRecorder mediaRecorder;
    boolean started = false;

    public void start() {
        if (started) {
            return;
        }
        if (mediaRecorder == null) {
            mediaRecorder = new MediaRecorder();

            mediaRecorder.setAudioSource(
                    MediaRecorder.AudioSource.MIC);
            mediaRecorder.setOutputFormat(
                    MediaRecorder.OutputFormat.THREE_GPP);
            mediaRecorder.setAudioEncoder(
                    MediaRecorder.AudioEncoder.AMR_NB);
            mediaRecorder.setOutputFile("/dev/null");
            try {
                mediaRecorder.prepare();
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            mediaRecorder.start();
            started = true;
        }
    }

    public void stop() {
        if (mediaRecorder != null) {
            mediaRecorder.stop();
            mediaRecorder.release();
            mediaRecorder = null;
            started = false;
        }
    }
    public double getAmplitude() {
        return mediaRecorder.getMaxAmplitude() / 100;
    }
} 

4.代码清单44.4SoundMeter中的MainActivity类

package com.example.soundmeter;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    Handler handler = new Handler();
    SoundMeter soundMeter = new SoundMeter();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it 
        // is present. 
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public void onStart() {
        super.onStart();
        soundMeter.start();
        handler.postDelayed(pollTask, 150);
    }

    @Override
    public void onPause() {
        soundMeter.stop();
        super.onPause();
    }

    private Runnable pollTask = new Runnable() {
        @Override
        public void run() {
            double amplitude = soundMeter.getAmplitude();
            TextView textView = (TextView) findViewById(R.id.level);
            textView.setText("amp:" + amplitude);
            Button button = (Button) findViewById(R.id.button1);
            button.setWidth((int) amplitude * 10);
            handler.postDelayed(pollTask, 150);
        }
    };
}

MainActivity类覆盖了两个活动生命周期方法,onStart和onPause。当活动创建或者活动重新启动之后,系统将调用onStart方法。当活动暂停或者由于另一个活动启动了,或者由于一个重要的事件发生了,系统将调用onPause方法。MainActivity类还使用了一个Handler来实现每150毫秒采样一次声音层级。

!真机测试

用指关节敲击手机屏幕,amp值不断变化:
20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第4张图片
第45章处理Handler
45.1概览
android.os.Handler可以用来在将来的某一时刻执行一个Runnable.分配给Handler的任何任务,都会在Handler的线程上运行。反过来,Handler在创建它的线程上运行,在大多数情况下,这个线程将会是UI线程。因此,不应该使用一个Handler来调度一个长时间运行的任务,这会让应用程序冻结。若能够将任务分解为较小的部分,也可以使用一个Handler来处理长时间运行的任务。
要调度一个在将来某个时刻运行的任务,调用Handler类的postDelayed或postAtTime方法即可。
postDelayed方法调用x毫秒之后,开始运行一个任务。
postAtTime在将来的某一时刻运行一个任务。
45.2示例
1.代码清单45.1HandlerDemo的清单




    
        
            
                

                
            
        
    

2.代码清单45.2HandlerTest中的res/layout/activity_main.xml文件



    

    

3.代码清单45.3 HandlerDemo中的MainActivity类

package com.example.handlerdemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {

    int counter = 0;
    Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getUserAttention();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it 
        // is present. 
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    public void buttonClicked(View view) {
        counter = 0;
        getUserAttention();
    }

    private void getUserAttention() {
        handler.post(task);
    }

    Runnable task = new Runnable() {
        @Override
        public void run() {
            ImageView imageView = (ImageView)
                    findViewById(R.id.imageView1);
            if (counter % 2 == 0) {
                imageView.setVisibility(View.INVISIBLE);
            } else {
                imageView.setVisibility(View.VISIBLE);
            }
            counter++;
            if (counter < 8) {
                handler.postDelayed(this, 400);
            }
        }
    };
}

这个活动的核心是一个叫作task的Runnable,它实现了ImageView的动画以及一个getUserAttention方法,该方法调用一个Handler上的postDelayed方法。这个Runnable,根据counter变量的值是奇数还是偶数,将ImageView的可见性设置为VISIBLE或INVISIABLE。

!真机测试

20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第5张图片
第46章异步工具
46.1概览
Android.os.AsyncTask类使得处理后台进程以及将进度更新发布到UI线程更加容易。这个类专门用于持续最多数秒钟的较短的操作。对于长时间运行的后台任务,应该使用Java并发工具框架。
AsyncTask类带有一组公有的方法和一组受保护的方法。公有方法用于执行和取消其任务。受保护的方法将在子类中覆盖。
46.2示例
反转和模糊的图像操作。
1.代码清单46.1PhotoEditor的清单




    
        
            
                

                
            
        
    

2.代码清单46.2PhotoEditor中的res/layout/activity_main.xml文件



    

        

3.代码清单46.3PhotoEditor中的MainActivity类

package com.example.photoeditor;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;

public class MainActivity extends Activity {
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it 
        // is present. 
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    public void doBlur(View view) {
        BlurImageTask task = new BlurImageTask();
        ImageView imageView = (ImageView)
                findViewById(R.id.imageView1);
        Bitmap bitmap = ((BitmapDrawable)
                imageView.getDrawable()).getBitmap();
        task.execute(bitmap);
    }

    public void doInvert(View view) {
        InvertImageTask task = new InvertImageTask();
        ImageView imageView = (ImageView)
                findViewById(R.id.imageView1);
        Bitmap bitmap = ((BitmapDrawable)
                imageView.getDrawable()).getBitmap();
        task.execute(bitmap);
    }

    private class InvertImageTask extends AsyncTask {
        protected Bitmap doInBackground(Bitmap... bitmap) {
            Bitmap input = bitmap[0];
            Bitmap result = input.copy(input.getConfig(), 
                    /*isMutable'*/true);
            int width = input.getWidth();
            int height = input.getHeight();
            for (int i = 0; i < height; i++) {
                for (int j = 0; j < width; j++) {
                    int pixel = input.getPixel(j, i);
                    int a = pixel & 0xff000000;
                    a = a | (~pixel & 0x00ffffff);
                    result.setPixel(j, i, a);
                }
                int progress = (int) (100*(i+1)/height);
                publishProgress(progress);
            }
            return result;
        }

        protected void onProgressUpdate(Integer... values) {
            progressBar.setProgress(values[0]);
        }

        protected void onPostExecute(Bitmap result) {
            ImageView imageView = (ImageView)
                    findViewById(R.id.imageView1);
            imageView.setImageBitmap(result);
            progressBar.setProgress(0);
        }
    }

    private class BlurImageTask extends AsyncTask {
        protected Bitmap doInBackground(Bitmap... bitmap) {
            Bitmap input = bitmap[0];
            Bitmap result = input.copy(input.getConfig(), 
                    /*isMutable=*/ true);
            int width = bitmap[0].getWidth();
            int height = bitmap[0].getHeight();
            int level = 7;
            for (int i = 0; i < height; i++) {
                for (int j = 0; j < width; j++) {
                    int pixel = bitmap[0].getPixel(j, i);
                    int a = pixel & 0xff000000;
                    int r = (pixel >> 16) & 0xff;
                    int g = (pixel >> 8) & 0xff;
                    int b = pixel & 0xff;
                    r = (r+level)/2;
                    g = (g+level)/2;
                    b = (b+level)/2;
                    int gray = a | (r << 16) | (g << 8) | b;
                    result.setPixel(j, i, gray);
                }
                int progress = (int) (100*(i+1)/height);
                publishProgress(progress);
            }
            return result;
        }

        protected void onProgressUpdate(Integer... values) {
            progressBar.setProgress(values[0]);
        }

        protected void onPostExecute(Bitmap result) {
            ImageView imageView = (ImageView)
                    findViewById(R.id.imageView1);
            imageView.setImageBitmap(result);
            progressBar.setProgress(0);
        }
    }
}
!真机测试

位图效果——
20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第6张图片

教材学习中的问题和解决过程(^▽^)

  • 问题1:究竟什么是UI线程?
  • 问题解决方案1:
    在Windows应用程序中,窗体是由一种称为“UI线程(User Interface Thread)”的特殊类型的线程创建的。
      首先,UI线程是一种“线程”,所以它具有一个线程应该具有的所有特征,比如有一个线程函数和一个线程ID。
      其次,“UI线程”又是“特殊”的,这是因为UI线程的线程函数中会创建一种特殊的对象——窗体,同时,还一并负责创建窗体上的各种控件。
    窗体和控件具有接收用户操作的功能,它们是用户使用整个应用程序的媒介,没有这样一个媒介,用户就无法控制整个应用程序的运行和停止,往往也无法直接看到程序的运行过程和最终结果。
    补充——主线程

    当 Java 程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main Thread),因为它是程序开始时就执行的。
    一般来说,某个类中会有一个 main 函数,当程序启动时,该函数就会第一个自动得到执行,并成为程序的主线程。
    主线程的特征如下:
    (1)主线程是产生其他子线程的线程。
    (2)主线程中执行程序的控制。

(3)通常主线程必须最后完成执行,因为它执行各种关闭动作
(4)永远不要在主线程中直接操作界面

代码调试中的问题和解决过程

  • 问题1:不会真机调试
  • 问题解决方案1:Android Studio在华为手机上的调试步骤——
    (1)设置——系统——关于手机——点击版本号——进入开发者模式。
    (2)设置——系统——开发人员选项——打开USB调试功能。
    (3)进入Android Studio——运行代码无错——Edit Configurations——Target选择USB Device——运行程序——在真机上安装并使用。

上周错题总结

[代码托管]

https://gitee.com/EvelynYang/eleventh_week

statistics.sh脚本运行结果的截图

在新建的AndroidProjects文件夹中运行脚本,第六周及之前都是在IdeaProjects文件夹里运行。
20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结_第7张图片

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 1/3 18/38
第三周 500/1000 1/4 38/76
第四周 1000/2000 1/5 20/96
第五周 1000/3000 1/6 25/121
第六周 1000/4000 1/7 25/146
第七周 1000/5000 1/8 25/171
第八周 1000/6000 1/9 15/186
第九周 1000/7000 1/10 20/206
第十周 1000/8000 1/11 20/226
第十一周 1000/9000 1/12 10/236

参考资料

  • 《Java和Android开发学习指南(第二版)(Java for Android.2nd)》
  • Android Studio中文社区

你可能感兴趣的:(20189230杨 2018-2019-2 《移动平台开发实践》第11周学习总结)