『壹』 android_studio手機藍牙串口通信源代碼
初涉android的藍牙操作,按照固定MAC地址連接獲取Device時,程序始終是異常終止,查了好多天代碼都沒查出原因。今天改了一下API版本,突然就成功連接了。總結之後發現果然是個坑爹之極的錯誤。
為了這種錯誤拚命查原因浪費大把時間是非常不值得的,但是問題不解決更是揪心。可惜我網路了那麼多,都沒有給出確切原因。今天特此mark,希望後來者遇到這個問題的時候能輕松解決。
下面是我的連接過程,中間崩潰原因及解決辦法。
1:用AT指令獲得藍牙串口的MAC地址,地址是簡寫的,按照常理猜測可得標准格式。
2:開一個String adress= "************" //MAC地址, String MY_UUID= "************"//UUID根據通信而定,網上都有。
3:取得本地Adapter用getDefaultAdapter(); 遠程的則用getRemoteDevice(adress); 之後便可用UUID開socket進行通信。
如果中途各種在getRemoteDevice處崩潰,大家可以查看一下當前的API版本,如果是2.1或以下版本的話,便能確定是API版本問題,只要換成2.2或者以上就都可以正常運行了~ 這么坑爹的錯誤的確很為難初學者。 唉·········· 為這種小trick浪費很多時間真是難過。
(另外有個重要地方,別忘了給manifest裡面加以下兩個藍牙操作許可權哦~)
<uses-permissionandroid:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
下面附上Android藍牙操作中用固定MAC地址傳輸信息的模板,通用搜索模式日後再補刪模板:
=null;
=null;
privateOutputStreamoutStream=null;
privateInputStreaminStream=null;
privatestaticfinalUUIDMY_UUID=UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//這條是藍牙串口通用的UUID,不要更改
privatestaticStringaddress="00:12:02:22:06:61";//<==要連接的藍牙設備MAC地址
/*獲得通信線路過程*/
/*1:獲取本地BlueToothAdapter*/
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter==null)
{
Toast.makeText(this,"Bluetoothisnotavailable.",Toast.LENGTH_LONG).show();
finish();
return;
}
if(!mBluetoothAdapter.isEnabled())
{
Toast.makeText(this,"-runthisprogram.",Toast.LENGTH_LONG).show();
finish();
return;
}
/*2:獲取遠程BlueToothDevice*/
BluetoothDevicedevice=mBluetoothAdapter.getRemoteDevice(address);
if(mBluetoothAdapter==null)
{
Toast.makeText(this,"Can'tgetremotedevice.",Toast.LENGTH_LONG).show();
finish();
return;
}
/*3:獲得Socket*/
try{
btSocket=device.(MY_UUID);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Socketcreationfailed.",e);
}
/*4:取消discovered節省資源*/
mBluetoothAdapter.cancelDiscovery();
/*5:連接*/
try{
btSocket.connect();
Log.e(TAG,"ONRESUME:BTconnectionestablished,datatransferlinkopen.");
}catch(IOExceptione){
try{
btSocket.close();
}catch(IOExceptione2){
Log.e(TAG,"ONRESUME:",e2);
}
}
/*此時可以通信了,放在任意函數中*/
/*try{
outStream=btSocket.getOutputStream();
inStream=btSocket.getInputStream();//可在TextView里顯示
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Outputstreamcreationfailed.",e);
}
Stringmessage="1";
byte[]msgBuffer=message.getBytes();
try{
outStream.write(msgBuffer);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Exceptionringwrite.",e);
}
*/
通用搜索模式代碼模板:
簡潔簡潔方式1 demo
作用: 用VerticalSeekBar控制一個 LED屏幕的亮暗。
直接上碼咯~
packagecom.example.seed2;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.app.Dialog;
importandroid.os.Bundle;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.util.UUID;
importandroid.bluetooth.BluetoothAdapter;
importandroid.bluetooth.BluetoothDevice;
importandroid.bluetooth.BluetoothSocket;
importandroid.content.DialogInterface;
importandroid.util.Log;
importandroid.view.KeyEvent;
importandroid.widget.Toast;
{
privatestaticfinalStringTAG="BluetoothTest";
=null;
=null;
privateOutputStreamoutStream=null;
privateInputStreaminStream=null;
privateVerticalSeekBarvskb=null;
privatestaticfinalUUIDMY_UUID=UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//這條是藍牙串口通用的UUID,不要更改
privatestaticStringaddress="00:12:02:22:06:61";//<==要連接的藍牙設備MAC地址
/**.*/
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.vskb=(VerticalSeekBar)super.findViewById(R.id.mskb);
this.vskb.setOnSeekBarChangeListener(newOnSeekBarChangeListenerX());
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter==null)
{
Toast.makeText(this,"Bluetoothisnotavailable.",Toast.LENGTH_LONG).show();
finish();
return;
}
if(!mBluetoothAdapter.isEnabled())
{
Toast.makeText(this,"-runthisprogram.",Toast.LENGTH_LONG).show();
finish();
return;
}
}
.OnSeekBarChangeListener{
publicvoidonProgressChanged(VerticalSeekBarseekBar,intprogress,booleanfromUser){
//Main.this.clue.setText(seekBar.getProgress());
/*Stringmessage;
byte[]msgBuffer;
try{
outStream=btSocket.getOutputStream();
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:OutputStreamcreationfailed.",e);
}
message=Integer.toString(seekBar.getProgress());
msgBuffer=message.getBytes();
try{
outStream.write(msgBuffer);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Exceptionringwrite.",e);
}*/
}
(VerticalSeekBarseekBar){
Stringmessage;
byte[]msgBuffer;
try{
outStream=btSocket.getOutputStream();
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:OutputStreamcreationfailed.",e);
}
message=Integer.toString(seekBar.getProgress());
msgBuffer=message.getBytes();
try{
outStream.write(msgBuffer);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Exceptionringwrite.",e);
}
}
publicvoidonStopTrackingTouch(VerticalSeekBarseekBar){
Stringmessage;
byte[]msgBuffer;
try{
outStream=btSocket.getOutputStream();
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:OutputStreamcreationfailed.",e);
}
message=Integer.toString(seekBar.getProgress());
msgBuffer=message.getBytes();
try{
outStream.write(msgBuffer);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Exceptionringwrite.",e);
}
}
}
@Override
publicvoidonStart()
{
super.onStart();
}
@Override
publicvoidonResume()
{
super.onResume();
BluetoothDevicedevice=mBluetoothAdapter.getRemoteDevice(address);
try{
btSocket=device.(MY_UUID);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Socketcreationfailed.",e);
}
mBluetoothAdapter.cancelDiscovery();
try{
btSocket.connect();
Log.e(TAG,"ONRESUME:BTconnectionestablished,datatransferlinkopen.");
}catch(IOExceptione){
try{
btSocket.close();
}catch(IOExceptione2){
Log.e(TAG,"ONRESUME:",e2);
}
}
//.
/*try{
outStream=btSocket.getOutputStream();
inStream=btSocket.getInputStream();
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Outputstreamcreationfailed.",e);
}
Stringmessage="read";
byte[]msgBuffer=message.getBytes();
try{
outStream.write(msgBuffer);
}catch(IOExceptione){
Log.e(TAG,"ONRESUME:Exceptionringwrite.",e);
}
intret=-1;
while(ret!=-1)
{
try{
ret=inStream.read();
}catch(IOExceptione)
{
e.printStackTrace();
}
}
*/
}
@Override
『貳』 Android源碼解析Window系列第(一)篇---Window的基本認識和Activity的載入流程
您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關系,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象類,它的具體實現是PhoneWindow,PhoneWindow還有個內部類DecorView,WindowManager是一個interface,繼承自ViewManager,它是外界訪問Window的入口,,提供了add/remove/updata的方法操作View,WindowManager與WindowManagerSerice是個跨進程的過程,WindowManagerService的職責是對系統中的所有窗口進行管理。如果您不太清楚,建議往下看,否則就不要看了。
Android系統的Window有很多種,大體上來說,Framework定義了三種窗口類型;
這就是Framework定義了三種窗口類型,這三種類型定義在WindowManager的內部類LayoutParams中,WindowManager講這三種類型 進行了細化,把每一種類型都用一個int常量來表示,這些常量代表窗口所在的層,WindowManagerService在進行窗口疊加的時候,會按照常量的大小分配不同的層,常量值越大,代表位置越靠上面, 所以我們可以猜想一下,應用程序Window的層值常量要小於子Window的層值常量,子Window的層值常量要小於系統Window的層值常量。 Window的層級關系如下所示。
上面說了Window分為三種,用Window的type區分,在搞清楚Window的創建之前,我們需要知道怎麼去描述一個Window,我們就把Window當做一個實體類,給我的感覺,它必須要下面幾個欄位。
實際上WindowManager.LayoutParams對Window有很詳細的定義。
提取幾個重要的參數
Window是一個是一個抽象的概念,千萬不要認為我們所看到的就是Window,我們平時所看到的是視圖,每一個Window都對應著一個View,View和Window通過ViewRootImpl來建立聯系。有了View,Window的存在意義在哪裡呢,因為View不能單獨存在,它必須依附著Window,所以有視圖的地方就有Window,比如Activity,一個Dialog,一個PopWindow,一個菜單,一個Toast等等。
通過上面我們知道視圖和Window的關系,那麼有一個問題,是先有視圖,還是先有Window。這個答案只有在源碼中找了。應用程序的入口類是ActivityThread,在ActivityThread中有performLaunchActivity來啟動Activity,這個performLaunchActivity方法內部會創建一個Activity。
如果activity不為null,就會調用attach,在attach方法中通過PolicyManager創建了Window對象,並且給Window設置了回調介面。
PolicyManager的實現類是Policy
這樣Window就創建出來了, 所以先有Window,後有視圖,視圖依賴Window存在 ,再說一說視圖(Activity)為Window設置的回調介面。
Activity實現了這個回調介面,當Window的狀態發生變化的時候,就會回調Activity中實現的這些介面,有些回調介面我們還是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。
下面分析view是如何附屬到window上的,通過上面可以看到,在attach之後就要執行callActivityOnCreate,在onCreate中我們會調用setContentView方法。
getWindow獲取了Window對象,Window的具體實現類是PhoneWindow,所以要看PhoneWindow的setContentView方法。
這里涉及到一個mContentParent變數,他是一個DecorView的一部分,DecorView是PhoneWindow的一個內部類,我先介紹一下關於DecorView的知識。
DecorView是Activity的頂級VIew,DecorView繼承自FrameLayout,在DecorView中有上下兩個部分,上面是標題欄,下面是內容欄,我們通過PhoneWindow的setContentView所設置的布局文件是加到內容欄(mContentParent)裡面的,View層的事件都是先經過DecorView在傳遞給我們的View的。
OK在回到setContentView的源碼分析,我們可以得到Activity的Window創建需要三步。
- 1、 如果沒有DecorView,在installDecor中創建DecorView。
- 2、將View添加到decorview中的mContentParent中。
- 3、回調Activity的onContentChanged介面。
先看看第一步,installDecor的源碼
installDecor中調用了generateDecor,繼續看
直接給new一個DecorView,有了DecorView之後,就可以載入具體的布局文件到DecorView中了,具體的布局文件和系統和主題有關系。
在看第二步,將View添加到decorview中的mContentParent中。
直接將Activity視圖加到DecorView的mContentParent中,最後一步,回調Activity的onContentChanged介面。在Activity中尋找onContentChanged方法,它是個空實現,我們可以在子Activity中處理。
到此DecorView被創建完畢,我們一開始從Thread中的handleLaunchActivity方法開始分析,首先載入Activity的位元組碼文件,利用反射的方式創建一個Activity對象,調用Activity對象的attach方法,在attach方法中,創建系統需要的Window並為設置回調,這個回調定義在Window之中,由Activity實現,當Window的狀態發生變化的時候,就會回調Activity實現的這些回調方法。調用attach方法之後,Window被創建完成,這時候需要關聯我們的視圖,在handleLaunchActivity中的attach執行之後就要執行handleLaunchActivity中的callActivityOnCreate,在onCreate中我們會調用setContentView方法。通過setContentView,創建了Activity的頂級View---DecorView,DecorView的內容欄(mContentParent)用來顯示我們的布局。 這個是我們上面分析得到了一個大致流程,走到這里,這只是添加的過程,還要有一個顯示的過程,顯示的過程就要調用handleLaunchActivity中的handleResumeActivity方法了。最後會調用makeVisible方法。
這裡面首先拿到WindowManager對象,用tWindowManager 的父介面ViewManager接收,ViewManager可以
最後調用 mDecor.setVisibility(View.VISIBLE)設置mDecor可見。到此,我們終於明白一個Activity是怎麼顯示在我們的面前了。
參考鏈接:
http://blog.csdn.net/feiclear_up/article/details/49201357
『叄』 Android開發_彈出小小提示框_Toast
Android開發,彈出提示框「Toast」是因為輸入了下面這句操作命令:
Toast.makeText(getApplicationContext(),"你的提示內容",Toast.LENGTH_SHORT).show();
Android開發操作如下:
先導入:
import android.widget.Toast;
關鍵代碼:
Toast.makeText(getApplicationContext(),"提示內容",Toast.LENGTH_SHORT).show();
例子:
在一個activity中,只有一個button,單擊這個button彈出「單擊完成」提示框。
提示:
只需在onCreante方法中添加button的單擊事件
完整代碼:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast1);
//設置button的單擊事件
findViewById(R.id.btnToast).setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
//彈出提示框
Toast.makeText(getApplicationContext(),"單擊完成",Toast.LENGTH_SHORT).show();
}
});
}
『肆』 Android怎麼得到toast的內容,不一定是自己軟體的toast
Android中Toast的顯示時間為特定時間且不可更改,但是有時候我們開發設計需要讓Toast顯示更長時間,或者自己完全控制Toast的顯示和關閉。通過查看Toast類的源碼,可以看出,這有點難為它了,Toast類本身並沒有提供相應方法。
但是通過源碼的查看,還是可以看出點眉頭。源碼分析思路在這里轉eoe里的一篇文章,思路較為清晰:
轉:
Toast信息提示框之所以在顯示一定時間後會自動關閉,是因為在系統中有一個Toast隊列。系統會依次從隊列中取(出隊列)一個Toast,並 顯示它。在顯示一段時間後,再關閉,然後再顯示下一個Toast信息提示框。
直到Toast隊列中所有Toast都顯示完為止。那麼有些時候需要這個Toas t信息提示框長時間顯示,直到需要關閉它時通過代碼來控制,而不是讓系統自動來關閉Toast信息提示框。
不過這個要求對於Toast本身來說有些過 分,因為Toast類並沒有提供這個功能。雖然如此,但方法總比問題多。通過一些特殊的處理還是可以實現這個功能的,而且並不復雜。
『伍』 Android 下拉通知欄時Activity的生命周期——重新理解onPause()
在某個APP中,發現下拉通知欄的時候,正在播放的視頻會暫停,於是有點好奇這段操作是不是在生命周期中實現的。在網上眾多關於Activity生命周期的討論中,很多人認為 onPause() 和 onStop() 的區別就是「部分遮擋」和「全部遮擋」,那按照這個猜測來分析一下這個過程:
首先,通知欄下拉一點點,符合一般描述中「Activity被部分遮擋」—— onPause()
然後,通知欄完全落下之後,「Activity被全部遮擋」—— onStop()
於是自己寫了一個實例來驗證:
啟動APP時,毫無疑問,調用了 onCreate() → onStart() → onResume() ;
完全下拉通知欄,然後上拉通知欄,發現沒有日誌列印,說明 下拉通知欄對Activity的生命周期沒有影響 。
經過測試不難發現,在Activity中彈出AlertDialog、Toast時,Activity的 onPause() 並沒有調用;筆者還嘗試在MIUI系統中喚醒小愛同學,發現 onPause() 仍然沒有被調用。
但是在以下特殊的情況下, onPause() 會被調用:
跑去看文檔發現了如下信息:
發現了 onPause() 和Activity的奇妙聯系,就不難理解之前為什麼沒有被調用的問題了。
查看AlertDialog和Toast的源碼,可以發現它們顯示的原理,都是通過 WindowManager.addView() 來顯示的。也就是說,AlertDialog和Toast可以看做是當前Activity的一部分View,當然也不會對Activity的生命周期構成影響。
因此, onPause() 是否調用的關鍵就是,是否有另一個Activity參與進來了。
而網上流傳甚廣的 onPause() 和 onStop() 調用中提到的「遮擋」,應該修正為 「被Activity遮擋」
至於官方文檔中提到的, onPause() 之後會調用 onStop() 或者 onResume() ,前者很好理解,一般的退出、新啟動一個全屏Activity、鎖屏、返回HOME等操作都是這種情況;至於後者,筆者能想到的情況就是,彈出部分遮擋的Activity類型的對話框,然後按返回鍵。