1.Bitmap介紹android
Bitmap實如今android.graphics包中。可是Bitmap類的構造函數是私有的,外面並不能實例化,只能是經過JNI實例化。算法
這必然是某個輔助類提供了建立Bitmap的接口,而這個類的實現經過JNI接口來實例化Bitmap的,這個類就是BitmapFactory。編程
Bitmap是一片連續的內存空間,而咱們的應用程序的內存是散亂的,若是當零散的空間下容納不了bitmap時就致使OOM。數組
2.BitmapFactory緩存
其中提供的一個Opitions屬性能夠用來提早獲取到圖片的寬高信息,下面會介紹。網絡
2.Bitmap優化app
當前有一張圖片,大小僅爲1M,可是其規格爲3648*2736,如今須要加載此圖片總像素數=3648*2736=9980928jsp
假設如今像素採用ARGB_4444標準,則其佔用的總空間爲:
圖片佔用空間=總像素數 *像素的單位 ----> 9980928 * 2bytes=19961856bytes = 19M > 16M OOM內存溢出ide
因此咱們必須對圖片進行縮放才能加載:函數
假設:
圖片的寬和高: 3648 * 2736 屏幕的寬和高: 320 * 480
計算縮放比:
寬度縮放比例: 3648 / 320 = 11 高度縮放比例: 2736 / 480 = 5
比較寬和高的縮放比例, 哪個大用哪個進行縮放
縮放後的圖片:
3648 / 11 = 331
2736 / 11 = 248
縮放後圖片的寬和高: 331* 248
331* 248=882088 * 2bytes=160K
3.解析圖片的三種方式
1.使用BitmapFactory解析圖片
2.使用BitmapDrawable解析圖片
3.使用InputStream和BitmapDrawable繪製
圖片的特效包括,圖形的縮放、鏡面、倒影、旋轉、位移等。圖片的特效是將原圖的圖形矩陣乘以一個特效矩陣,造成一個新的圖形矩陣來實現的。
Matrix維護了一個3*3的矩陣去更改像素點的座標。
圖形的默認矩陣用數組表示爲:
{ 1, 0, 0, 表示向量x = 1x + 0y + 0z
0, 1, 0, 表示向量y = 0x + 1y + 0z
0, 0, 1 } 表示向量z = 0x + 0y + 1z
經過更改圖形矩陣的值,能夠作出縮放/鏡面/倒影等圖片特效。
1.縮放
2.鏡面
惟一不一樣的地方在矩陣處
3.倒影
4.旋轉
5.位移
Android提供了顏色過濾器來進行顏色處理。
a)ColorMatrixColorFilter:經過使用一個4*5的顏色矩陣來建立一個顏色過濾器,改變圖片的顏色信息。
b) 圖形顏色默認矩陣是一個4x5的矩陣, 數組表現爲:
{1, 0, 0, 0, 0, // red 1*R + 0*G + 0*B + 0*A + 0
0, 1, 0, 0, 0, // green 0*R + 1*G + 0*B + 0*A + 0
0, 0, 1, 0, 0, // blue 0*R + 0*G + 1*B + 0*A + 0
0, 0, 0, 1, 0} // alpha 0*R + 0*G + 0*B + 1*A + 0
顏色矩陣的每一行的最後一個值更改時,其對應的顏色值就會發生改變,因此更改顏色只需修改其對應顏色矩陣行的最後一項的值便可,最大值範圍爲255
a)首先咱們聲明一個初始化矩陣數組和顏色過濾器
b)重寫progressBar的onProgressChanged方法,並修改矩陣數組的值後,從新建立新的顏色過濾器。
a)首先在佈局文件建立一個ImageView,讓其寬高都填充父窗體,並在代碼中進行實例化,並設置它的Touch事件。
mIv = (ImageView) findViewById(R.id.iv); mIv.setOnTouchListener(this);
b) 而後重寫onTouch(View v, MotionEvent event)方法對手勢進行操做
@Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mStartX = (int) event.getX(); mStartY = (int) event.getY(); if(mBitmap == null){ mBitmap = Bitmap.createBitmap(mIv.getWidth(),mIv.getHeight(),Config.ARGB_8888); // 畫布 mCanvas = new Canvas(mBitmap); // 設置背景色白色 mCanvas.drawColor(Color.WHITE); mPaint = new Paint(); // 設置畫筆顏色爲紅色,線條粗細爲5 mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5); } break; case MotionEvent.ACTION_MOVE: // 記錄移動到的位置座標 int moveX = (int) event.getX(); int moveY = (int) event.getY(); // 繪製線條,鏈接起始位置和當前位置 mCanvas.drawLine(mStartX, mStartY, moveX, moveY, mPaint); // 設置顯示 mIv.setImageBitmap(mBitmap); // 將起始位置改成爲當前移動到的位置 mStartX = moveX; mStartY = moveY; break; case MotionEvent.ACTION_UP: break; } return true; }
c) 上述基本功能已經完成,咱們還須要一個清空的方法
// 清除界面 public void clear(View view){ mBitmap = null; mIv.setImageBitmap(null); }
1. 簡介
如今android應用中不可避免的要使用圖片,有些圖片是能夠變化的,須要每次啓動時從網絡拉取。
如今有一個問題:假如每次啓動的時候都從網絡拉取圖片的話,勢必會消耗不少流量。在當前的情況下,對於非wifi用戶來講,流量仍是很貴的,
一個很耗流量的應用,其用戶數量級確定要受到影響。固然,我想,向百度美拍這樣的應用,必然也有其內部的圖片緩存策略。總之,圖片緩存是很重要並且是必須的。
2.圖片緩存的原理
實現圖片緩存也不難,須要有相應的cache策略。這裏我採用 內存-文件-網絡 三層cache機制,其中內存緩存包括強引用緩存和軟引用緩存(SoftReference),
其實網絡不算cache,這裏姑且也把它劃到緩存的層次結構中。當根據url向網絡拉取圖片的時候,先從內存中找,若是內存中沒有,再從緩存文件中查找,
若是緩存文件中也沒有,再從網絡上經過http請求拉取圖片。在鍵值對(key-value)中,這個圖片緩存的key是圖片url的hash值,value就是bitmap。因此,
按照這個邏輯,只要一個url被下載過,其圖片就被緩存起來了。
關於Java中對象的軟引用(SoftReference),若是一個對象具備軟引用,內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。
只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高 速緩存。使用軟引用能防止內存泄露,加強程序的健壯性。
固然,這種回收機制只存在於Android 2.3版本以前,Android 2.3以後垃圾回收器會更傾向於回收軟引用、弱引用,它們變得再也不可靠。
從代碼上來講,採用一個ImageManager來負責圖片的管理和緩存,函數接口爲public void loadBitmap(String url, Handler handler) ;其中url爲要下載的圖片地址,
handler爲圖片下載成功後的回調,在handler中處理message,而message中包含了圖片的信息以及bitmap對象。ImageManager中使用的ImageMemoryCache(內存緩存)、
ImageFileCache(文件緩存)以及LruCache(最近最久未使用緩存)會在後續文章中介紹。
一、MediaPlayer
該播放器同時只能播放一個音樂文件,文件大小並無限制。MediaPlayer必須嚴格按照狀態圖操做,不然就會出現錯誤。
MediaPlayer播放的實例:
// 建立MediaPlayer對象 MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(Environment.getExternalStorageDirectory() + "/bg.mp3"); // 準備 mediaPlayer.prepare(); // 判斷當前音樂是否在播放 boolean isPlaying = mediaPlayer.isPlaying(); if(!isPlaying){ mediaPlayer.start(); }else{ Toast.makeText(this,"音樂正在播放中",0).show(); }
二、SoundPool
SoundPool特色是能夠自行設置聲音的品質、音量、播放比率等參數。而且它能夠同時管理多個音頻流,每一個流都有獨自的ID,對某個音頻流的管理都是經過ID進行的。
a)SoundPool最大隻能申請1M的內存空間,只能用一些很短的聲音片斷,而不是用它來播放歌曲或者作遊戲背景音樂。
b)SoundPool提供了pause和stop方法,但這些方法建議最好不要輕易使用,由於有些時候它們可能會使你的程序莫名其妙的終止。
c)SoundPool的效率問題。其實SoundPool的效率在這些播放類中算是很好的,這可能會影響用戶體驗。
SoundPool播放的實例:
// 建立SoundPool對象 SoundPool soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,1); // 加載SoundPool音樂文件 int soundID = soundPool.load(this,R.raw.shoot1,1); // 播放soundPool音樂 soundPool.play(soundID,1,1,0,0,1);
1.第一種
a)第一種方式能夠經過surfaceView和MediaPlayer來實現該功能,首先建立一個SufaceView實例
2.第二種
a)設置佈局文件,佈局文件比較簡單,所以這裏只給你VideoView標籤。
mSurface = (SurfaceView) findViewById(R.id.sfv); mHolder = mSurface.getHolder(); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
b) 而後經過以下代碼播放,注意視頻知支持mp4格式
/ 建立播放器對象 MediaPlayer player = new MediaPlayer(); // 獲取音樂路徑 String path = "/mnt/sdcard/oppo.mp4"; // 給播放器設置音樂路徑 player.setDataSource(path); // 設置音樂格式 player.setAudioStreamType(AudioManager.STREAM_MUSIC); // 設置輸出畫面 player.setDisplay(mHolder); // 準備 player.prepare(); // 播放 player.start();
2.第二種
a)設置佈局文件,佈局文件比較簡單,所以這裏只給你VideoView標籤。
<VideoView android:id="@+id/video" android:layout_width="match_parent" android:layout_height="match_parent"/>
b) 設置VideoView的播放文件路徑和媒體控制器,調用start方法便可播放媒體文件,固然如今只是讓當前類實現了MediaController接口
VideoView video = (VideoView) findViewById(R.id.video); video.setVideoPath("/mnt/sdcard/oppo.mp4"); video.setMediaController(new MediaController(this)); // 開始播放 video.start(); // 設置當前播放器窗口爲焦點 video.requestFocus();
c)這時,視頻已經能夠播放,若是想實現本身的業務邏輯,能夠定義類實現MediaPlayerControl接口,其中提供大量的回調方法供咱們使用。
Android手機中內置了不少傳感器,其主要類型有:方向、加速度(重力)、光線、磁場、距離(臨近性)、溫度等。
1.傳感器的使用
a) 獲取傳感器管理器SensorManager
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
b) 經過傳感器管理器對象得到指定類型的傳感器:
// 獲取指定傳感器對象,獲取系統默認的重力加速度傳感器 Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
c) 經過傳感器管理器對象得到手機中全部的傳感器
// 獲取手機支持的全部傳感器 List<Sensor> sensorList = sm.getSensorList(Sensor.TYPE_ALL); for (int i = 0; i < sensorList.size(); i++) { Sensor sensor = sensorList.get(i); // 獲取傳感器名稱 String name = sensor.getName(); // 獲取傳感器廠商 String vendor = sensor.getVendor(); // 獲取傳感器版本號 int version = sensor.getVersion(); }
d) 使用傳感器管理器對象註冊傳感器來使一個傳感器工做:
sm.registerListener(new SensorEventListener() { // 當傳感器改變時觸發該函數 @Override public void onSensorChanged(SensorEvent event) { } // 當傳感器精度更改時觸發該函數 @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } },sensor,SensorManager.SENSOR_DELAY_NORMAL);
(1) listener :傳感器事件監聽器
(2) sensor :要被註冊的傳感器對象
(3) rate :採樣率,分爲最快、遊戲、普通、用戶界面幾種當應用程序請求特定的採樣率時,其實只是對傳感器子系統的一個建議,不保證特定的採樣率可用。
採樣率的四種類型詳解:
最快: SensorManager.SENSOR_DELAY_FASTEST,最低延遲,通常不是特別敏感的處理不推薦使用,該種模式可能形成耗電,因爲傳遞的爲原始數據,
算法不處理好會影響遊戲邏輯和UI的性能
遊戲: SensorManager.SENSOR_DELAY_GAME,遊戲延遲,通常絕大多數的實時性較高的遊戲都使用該級別
普通: SensorManager.SENSOR_DELAY_NORMAL,標準延遲,對於通常的益智類或EASY級別的遊戲可使用,但太低的採樣率可能對一些賽車類遊戲有跳幀現象
用戶界面: SensorManager.SENSOR_DELAY_UI,通常對於屏幕方向自動旋轉使用,相對節省電能和邏輯處理,通常遊戲開發中咱們不使用
總結:因爲傳感器比較多,這裏只是簡單的概述,後續會花大量的時間整理傳感器的使用。
調用系統攝像頭進行拍照和攝像無需添加權限,直接調用便可。只需知道系統攝像頭的action和category就能夠調用系統攝像頭。
a)打開Android源碼,查看」\packages\apps\」文件文件目錄下的Camera應用,即系統攝像頭的應用程序。打開其清單文件文件,查看其Activity的action和category信息。
b)Camera類的action和category
<intent-filter> <action android:name="android.media.action.IMAGE_CAPTURE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
VideoCamera類的action和category
<intent-filter> <action android:name="android.media.action.VIDEO_CAMERA" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
c) 已知調用系統攝像頭拍照和攝像功能對應的action和category信息,採用隱式調用的方式調用Activity。
因爲但願在調用拍照或攝像功能後回到當前應用的界面,且得知拍照或攝像的結果如何是否成功使用startActivityForResult方法開啓Activity,並重寫onActivityResult方法處理回傳的數據。
1.拍照功能
Intent intent = new Intent(); // 設置Action intent.setAction("android.media.action.IMAGE_CAPTURE"); // 建立一個文件 File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"my.jsp"); // 建立Uri對象 Uri uri = Uri.fromFile(file); // 設置圖片輸出路徑 intent.putExtra(MediaStore.EXTRA_OUTPUT,uri); // 開啓Activity startActivityForResult(intent,100);
2.攝像功能
Intent intent = new Intent(); intent.setAction("android.media.action.VIDEO_CAPTURE"); File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"myVedio.mp4"); Uri uri = Uri.fromFile(file); intent.putExtra(MediaStore.EXTRA_OUTPUT,uri); startActivityForResult(intent,101);
3.自定義拍照
a)首先定義一個SurfaceView用做相機的預覽
SurfaceView mSurfaceView = (SurfaceView) this.findViewById(R.id.surfaceView); SurfaceHolder holder = mSurfaceView.getHolder(); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.setKeepScreenOn(true); holder.addCallback(new SurfaceCallback());
b )建立SurfaceCallback來開啓預覽,其中提供了三個回調方法,方便咱們對相機預覽進行管理。
/**sufaceView的回調*/ private final class SurfaceCallback implements Callback { @Override public void surfaceCreated(SurfaceHolder holder) { try { mCamera = Camera.open(0);// 0:打開後置 1:打開前置 if(mCamera != null){ mCamera.setPreviewDisplay(holder); mCamera.setDisplayOrientation(getPreviewDegree(NewCameraCheck.this)); mCamera.startPreview(); }else{ Toast.makeText(NewCameraCheck.this, "獲取不到相機實例", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) { if(mCamera != null){ parameters = mCamera.getParameters(); parameters.setPictureFormat(PixelFormat.JPEG); parameters.setPreviewSize(width, height); parameters.setPreviewFrameRate(5); parameters.setPictureSize(width, height); parameters.setJpegQuality(80); }else{ Toast.makeText(NewCameraCheck.this, "獲取不到相機實例,沒法設置參數。", Toast.LENGTH_SHORT).show(); } } // 在Surface銷燬時釋放資源 @Override public void surfaceDestroyed(SurfaceHolder holder) { if(mCamera != null){ //mCamera.stopPreview(); mCamera.release(); mCamera = null; } } }
c) 提供一個靜態方法用來對相機預覽的旋轉角度進行自動調整
// 提供一個靜態方法,用於根據手機方向得到相機預覽畫面旋轉的角度 public int getPreviewDegree(Activity activity) { // 得到手機的方向 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); mDegree = 0; // 根據手機的方向計算相機預覽畫面應該選擇的角度 switch (rotation) { case Surface.ROTATION_0: mDegree = 90; break; case Surface.ROTATION_90: mDegree = 0; break; case Surface.ROTATION_180: mDegree = 270; break; case Surface.ROTATION_270: mDegree = 180; break; } return mDegree; }
d) 咱們能夠經過調用Camera的tackPicture()方法進行拍照,若是咱們須要對焦拍照的話,使用下面這段代碼拍照
public void tackImage(){ if(mCamera == null){ Toast.makeText(NewCameraCheck.this, "獲取不到相機實例,拍照失敗!", Toast.LENGTH_SHORT).show(); return; } mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { mCamera.takePicture(null, null, new MyPictureCallback()); //mCamera.startPreview(); } }); }
e) 除此以外,咱們相機適配須要注意清單文件中的配置,固然還要記得添加權限
清單文件配置: <activity android:name="com.hll.phoneuser.activity.NewCameraCheck" android:screenOrientation="portrait" android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen" /> 權限配置: <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
4.自定義攝像
使用Camera+MediaRecoder + SurfaceView控件可實現錄製視頻的功能。
a)首先定義一個SurfaceView用做相機的預覽
setContentView(R.layout.activity_main); SurfaceView mSurfaceView = (SurfaceView) findViewById(R.id.surface); SurfaceHolder holder = mSurfaceView .getHolder(); holder.addCallback(new MyVideoCallback());
b) 建立SurfaceCallback來開啓預覽,其中提供了三個回調方法,方便咱們對相機預覽進行管理。
class MyVideoCallback implements Callback{ @Override public void surfaceCreated(SurfaceHolder holder) { mCamera.open(); try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); } }
c)開始錄像須要設置大量的參數,代碼中異常未處理,具體操做以下
// 中止預覽 mCamera.stopPreview(); // 解鎖攝像頭 mCamera.unlock(); // 初始化MediaRecorder對象 MediaRecorder recorder = new MediaRecorder(); // 給recorder設置攝像頭 recorder.setCamera(mCamera); // 設置音頻源 recorder.setAudioSource(AudioSource.CAMCORDER); // 設置視頻源 recorder.setVideoSource(VideoSource.CAMERA); // 設置錄像質量參數 CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P); recorder.setProfile(profile); // 設置錄像輸出路徑 recorder.setOutputFile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/test.mp4"); // 設置預覽顯示對象 recorder.setPreviewDisplay(mHolder.getSurface()); // 準備 recorder.prepare(); recorder.start();
d) 若是想中止錄像的話,只須要調用以下代碼:
mRecorder.stop();
mRecorder.reset();
mRecorder.release();
mCamera.lock();
mCamera.startPreview();
e) 固然別忘記添加權限
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
後續進階能夠參看:Android多媒體開發高級編程
首先調用系統的相機或打開相冊來獲取圖片
// 拍照 btnCamera.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File( Environment.getExternalStorageDirectory(), "temp.jpg"))); startActivityForResult(intent, PHOTOHRAPH); } }); // 從相冊選擇 btnPhoto.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, PHOTOZOOM); } });
在activity中的onActivityResult中進行回調獲取到打開的圖片
public static final int NONE = 0; public static final int PHOTOHRAPH = 1;// 拍照 public static final int PHOTOZOOM = 2; // 縮放 public static final int PHOTORESULE = 3;// 結果 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case NONE: return; case PHOTOHRAPH: // 拍照 // 設置文件保存路徑這裏放在根目錄下 File picture = new File(Environment.getExternalStorageDirectory() + "/temp.jpg"); startPhotoZoom(Uri.fromFile(picture)); break; case PHOTOZOOM: // 從相冊選擇 startPhotoZoom(data.getData()); break; case PHOTORESULE: Bundle extras = data.getExtras(); if (extras != null) { Bitmap photo = extras.getParcelable("data"); ByteArrayOutputStream stream = new ByteArrayOutputStream(); photo.compress(Bitmap.CompressFormat.JPEG, 75, stream);// (0 - 100)壓縮文件 // 設置頭像顯示 mIvPhoto.setImageBitmap(photo); dialog.dismiss(); } break; } }
其中封裝的方法startPhotoZoom,是對圖片進行寬高和壓縮等處理:
public void startPhotoZoom(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); // aspectX aspectY 是寬高的比例 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // outputX outputY 是裁剪圖片寬高 intent.putExtra("outputX", 64); intent.putExtra("outputY", 64); intent.putExtra("return-data", true); startActivityForResult(intent, PHOTORESULE); }