• [ARM原生] null
    null
  • [ARM原生] null
    null
  • [技术干货] Android @id和@+id 以及 @android:id 的区别
    昨天突然有新来的同事问我这个@id 和@+id 的区别 ,为什么 我们的项目都是@id 自己新增的ui 使用的@+id 这里说下我的简单的回复项目是维护的之前的是为了统一管理使用了@id  方便修改 ,因为在ids.xml 里面有引用,@+id 是新增了,没有那样写,感觉有点麻烦,不过为了统一最后自己又修改为了@id,可能这些说的有些模糊 ,下面是自己整理的一些知识,方便查找首先我们需要知道我们平时使用的id 是int 类型的 1 @+id :我们经常使用的当修改完某个布局文件并保存后,系统会自动在R.java文件中生成相应的int类型变量。变量名就是“/”后面的值例如 android:id="@+id/tv"  变量名为tv2 @id 表示引用一个已经存在的ID,在R.java里面的,比如在res 里面的value 里面建了一个ids.xml<?xml version="1.0" encoding="utf-8"?><resources><item name="text_id_text" type="id" /></resources>我们的id 引用<TextViewandroid:id="@id/text_id_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试一次文字" />个人感觉这样做呢为了方便修改id 假如很多界面使用了相同的id 我们就可以这样写(维护的项目大约有30几个界面使用相同的id) 3 @android:id  这个是引用系统的id 自己基本没有使用过,简单的了解
  • [问题求助] Android OBS 断点续传上传
    根据SDK文档中示例的程序进行文件上传,使用的是:以下代码展示了如何使用断点续传上传接口上传文件:String endPoint = "https://your-endpoint"; String ak = "*** Provide your Access Key ***"; String sk = "*** Provide your Secret Key ***"; // 创建ObsClient实例 ObsClient obsClient = new ObsClient(ak, sk, endPoint);          UploadFileRequest request = new UploadFileRequest("bucketname", "objectname"); // 设置待上传的本地文件,其中localfile为待上传的本地文件路径,需要指定到具体的文件名 request.setUploadFile("localfile"); // 设置分段上传时的最大并发数 request.setTaskNum(5); // 设置分段大小为10MB request.setPartSize(10 * 1024 * 1024); // 开启断点续传模式 request.setEnableCheckpoint(true);try{     // 进行断点续传上传     CompleteMultipartUploadResult result = obsClient.uploadFile(request); }catch (ObsException e) {     // 发生异常时可再次调用断点续传上传接口进行重新上传 }针对同一个文件,使用相同的bucketname和objectname,但再次上传时,进度似乎不是从上次上传的位置返回的
  • [技术干货] android 自定义广播
    1.1广播机制简介为了便于进行系统级别的广播通知,Android引入了一套广播消息机制,Android的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序只会接收到自己所关心的广播内容,这些广播可能是来自系统的,也可能是来自其他应用的,Android提供了一套完整的API,允许应用程序自由地接受和发送广播。接受广播的方法引入一个新概念——广播接收器(Broadcast Receiver)。广播主要分为两种类型,标准广播,有序广播。标准广播:是一种完全异步执行的广播,在广播发出后,所有的广播几乎在同一时刻接受这条广播消息,没有顺序,效率比较高,同时意味着无法截断。有序广播:一种同步执行的广播,在广播发出后,同一时刻只会有一个广播接收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,此时的广播接收器是有先后顺序的,优先级高的广播就可以先接收到消息,并且前面的广播接收器还可以截断正在传递的广播,后面的就无法接收消息了。1.2 接受系统广播1.2.1 动态注册监听网络变化注册广播:1.代码注册,动态注册。2.在AndroidManifest.xml中注册,静态注册。该如何创建一个广播接收器,只需要新建一个类,让它继承自BroadcastReceiver,并重写父类的onReceive()方法。这样当有广播到来时,onReceive()方法就会得到执行,具体的逻辑可以在这个方法中处理先写一个通过动态注册的方式监听网络变化的程序,新建一个项目public class MainActivity extends AppCompatActivity {     private IntentFilter intentFilter;     private NetworkChangeReceiver networkChangeReceiver;          @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         intentFilter = new IntentFilter();         intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");         networkChangeReceiver = new NetworkChangeReceiver();         registerReceiver(networkChangeReceiver,intentFilter);     }     @Override     protected void onDestroy(){         super.onDestroy();         unregisterReceiver(networkChangeReceiver);     }     class NetworkChangeReceiver extends BroadcastReceiver{         @Override         public void onReceive(Context context, Intent intent){             Toast.makeText(context,"network changes",Toast.LENGTH_SHORT).show();         }     }}123456789101112131415161718192021222324在MainActivity中定义了一个内部类NetworkChangeReceiver,继承BroadcastReceiver,并重写了父类的onReceive()方法,当网络状态发生变化时,onReceive()方法就会得到执行,Toast提示了一段文本。OnCreate()方法,首先创建了IntentFilter的实例,并给他添加了android.net.conn.CONNECTIVITY_CHANGE的action,因为当网络状态发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_CHANGE的广播。所以我们的广播接收器想要监听什么广播,就在这里添加相应的action。接下来创建一个NetworkChangeReceiver实例,然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例都传进去。这样NetworkChangeReceiver就会收到值为android.net.conn.CONNECTIVITY_CHANGE的广播,也就实现了监听网络的功能。最后,动态注册的广播接收器一定要取消注册,这里在onDestroy()方法中通过调用**unregisterReceiver()**方法来实现。对以上代码进行优化,提醒当前是有网络还是没网络class NetworkChangeReceiver extends BroadcastReceiver{     @Override     public void onReceive(Context context ,Intent intent){         ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);         NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();         if(networkInfo!=null&&networkInfo.isAvailable()){             Toast.makeText(context,"network is available",Toast.LENGTH_SHORT).show();         }else{             Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();         }     }}1234567891011在onReceive()方法中,通过getSystemService()方法得到了ConnectivityManager的实例,这是一个系统服务类,专门用于管理网络连接,然后调用他的getActivityNetworkInfo()方法可以得到NetworkInfo实例,接着调用NetworkInfo的isAvailable()方法就可以判断当前是否有网络,最后通过Toast提示。要在配置文件里声明权限,在AndroidManifest.xml中加入<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>1.2.2 静态注册实现开机启动动态注册的广播可以自由地控制注册与注销,灵活,但有一个缺点,必须要在程序启动后才能接收广播,因为注册的逻辑是写在onCreate()方法里,想要让程序在未启动的情况下就能接受广播,这就需要静态注册。让程序接受一条开机广播,当收到这条广播就可以在onReceive()方法里执行相应的逻辑,从而实现开机启动功能。可以使用Android Studio提供的快捷方式来创建一个广播接收器,New,Other,Broadcast Receiver,将广播接收器命名为BootCompleteReceiver,Exported属性表示是否允许这个广播接收器接受本程序以外的广播,Enabled属性表示是否启用这个广播接收器。修改BootCompleteReceiver中的代码public class BootCompleteReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show();     }}12345静态的广播器要在AndroidManifest.xml中注册,修改其中的代码......<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>......<receiver     android:name=".BootCompleteReceiver"     android:enabled="true"     android:exported="true">     <intent-filter>         <action android:name="android.intent.action.BOOT_COMPLETED"/>     </intent-filter></receiver>......123456789101112在< aoolication >标签内出现了一个新标签< receiver > ,所有静态的广播接收器都是可以在这里进行注册,用法和< activity >标签非常相似,通过android:name来指定具体注册哪一个广播接收器。由于Android系统启动后会发出一条值为android.intent.action.BOOT_COMPLETED的广播,所以在< intent-filter >标签里添加相应的action。另外监听系统开机广播需要声明权限,在< uses-permission >标签里加入了一条android.permission.RECEIVE_BOOT_COMPLETED权限。Android 8.0或更高版本,无法使用清单为大多数隐式广播声明接收器,开机时无法接收到广播,解决方法:Intent it = new Intent("com.example.broadcast.BootCompleteReceiver");it.setComponent(new ComponentName("com.example.broadcast", "com.example.broadcast.BootCompleteReceiver"));sendBroadcast(it);12第一个参数是包名,第二个参数是广播器名,然后开机,效果如图在真正项目中的时候,可以在onReceive()中写自己的逻辑,但是不要在其中添加过多的逻辑或进行任何耗时的操作,因为在广播接收器里是不允许开启线程的。所以广播接收器更多的扮演一种打开程序其他组件的角色,比如创建一条状态通知栏,或者启动一个服务。1.3 发送自定义广播1.3.1发送标准广播发送广播之前先定义一个广播接收器来准备接受此广播,新建MyBroadcastReceiver,public class MyBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();     }}123456修改AndroidManifest.xml中的代码...<receiver     android:name=".MyBroadcastReceiver"     android:enabled="true"     android:exported="true">     <intent-filter>         <action android:name="com.example.broadcast.MY_BROADCAST"/>     </intent-filter></receiver>...123456789让MyBroadcastReceiver接受一条值为com.example.broadcast.MY_BROADCAST的广播,因此待会儿发送广播的时候,就需要发出这样一条广播。修改activity_main.xml中的代码<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:orientation="vertical"     android:layout_width="match_parent"     android:layout_height="match_parent">     <Button         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:id="@+id/button"         android:text="Send Broadcast"/></LinearLayout>12345678910然后修改MainActivity中的onCreate()的代码...Button button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {     @Override     public void onClick(View view) {         Intent intent = new Intent("com.example.broadcast.MY_BROADCAST");         intent.setComponent(new ComponentName("com.example.broadcast", "com.example.broadcast.MyBroadcastReceiver"));         sendBroadcast(intent);     }});...12345678910在按钮的点击事件里加入了发送自定义的广播逻辑,首先构建出了一个Intent对象,并把要发送的广播的值传入,然后调用了Context的sendBroadcast()方法将广播发送出去,这样所有监听com.example.broadcast.MY_BROADCAST这条广播的广播接收器就会收到这条消息,此时发出去的广播是一条标准广播。点击按钮,效果如下1.3.2发送有序广播广播是一种可以跨进程的通信方式,因此在我们应用内发出的广播,其他应用程序也可以收到。再创建一个新项目BroadcastTest2,在这个项目下定义一个广播接收器,用于接收上一节中的自定义广播,新建AnotherBroadcastReceiverpublic class AnotherBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         Toast.makeText(context,"received in AnotherBroadcast",Toast.LENGTH_SHORT).show();     }}123456修改AndroidManifest.xml中的代码...<receiver     android:name=".AnotherBroadcastReceiver"     android:enabled="true"     android:exported="true">     <intent-filter>         <action android:name="com.example.broadcasttest1.MY_BROADCAST"/>     </intent-filter></receiver>...123456789AnotherBroadcastReceiver同样接收的是com.example.broadcasttest1.MY_BROADCAST这条广播,先运行BroadcastTest2安装到模拟器上,然后修改Broadcast的MainActivity中的代码button.setOnClickListener(new View.OnClickListener() {     @Override     public void onClick(View view) {         Intent intent = new Intent("com.example.broadcast.MY_BROADCAST");         intent.setComponent(new ComponentName("com.example.broadcast", "com.example.broadcast.MyBroadcastReceiver"));         sendBroadcast(intent);           Intent intent2 = new Intent("com.example.broadcast.MY_BROADCAST");         intent2.setComponent(new ComponentName("com.example.broadcasttest2","com.example.broadcasttest2.AnotherBroadcastReceiver"));         sendBroadcast(intent2);     }});1234567891011运行,点击按钮就会分别弹出两次提示信息。这样证明了,我们应用程序发出的广播可以被其他应用程序接收到。1.4 使用本地广播本地广播机制,发出的广播只能在程序的内部进行传递,并且广播接收器也只能接受来自本应用程序发出的广播。主要使用LocalBroadcastManager来对广播进行管理。public class MainActivity extends AppCompatActivity {     private IntentFilter intentFilter;     private LocalReceiver localReceiver;     private LocalBroadcastManager localBroadcastManager;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         localBroadcastManager = LocalBroadcastManager.getInstance(this); //得到实例         Button button = (Button) findViewById(R.id.button);         button.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View view) {                 Intent intent = new Intent("com.example.broadcasttest1.LOCAL_BROADCAST");                 localBroadcastManager.sendBroadcast(intent); //发送本地广播             }         });         intentFilter = new IntentFilter();         intentFilter.addAction("com.example.broadcasttest1.LOCAL_BROADCAST"); //注册本地广播监听器         localReceiver = new LocalReceiver();         localBroadcastManager.registerReceiver(localReceiver,intentFilter);     }     @Override     protected void onDestroy(){         super.onDestroy();         localBroadcastManager.unregisterReceiver(localReceiver);     }     class LocalReceiver extends BroadcastReceiver{         @Override         public void onReceive(Context context,Intent intent){             Toast.makeText(context,"received in local broadcast",Toast.LENGTH_SHORT).show();         }     }12345678910111213141516171819202122232425262728293031323334本地广播无法通过静态注册的方式来接收。优势:1.可以明确知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄露。2.其他程序无法将广播发送黄金我们程序的内部,因此不需要担心会有安全泄露。3.发送本地广播会比发送系统全局广播更高效。
  • [技术干货] Android 蓝牙启动流程
    最近在做蓝牙开发,研究了一下蓝牙的启动流程,总结一下Google在Android源码中推出了它和博通公司一起开发的BlueDroid以替代BlueZ。BlueZ的创始者,高通公司也将在基于其芯片的Android参考设计中去除BlueZ,支持BlueDroid。相比BlueZ,BlueDroid最值得称道的地方就是其框架结构变得更为简洁和清晰。对我们工程师来说这也是个不错的福利,清晰、简洁的架构使我们在debug过程中思路更清晰;蓝牙的总体流程图从流程图我们可以看出来 蓝牙应用通过binder和系统蓝牙Service进行通讯 ,然后通过Jin与蓝牙HAL层进行通讯. 按层级划分Bluetooth Service(Java层)代码路径frameworks/base/core/java/Android/Bluetooth:Bluetooth 应用层代码路径packages/apps/BluetoothBluetooth协议层代码system/bt/涉及的总共路径:frameworks/base/core/java/android/bluetooth/BluetoothAdapter.javaframeworks/base/core/java/android/bluetooth/BluetoothManagerService.javapackages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.javapackages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterState.javapackages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cppsystem/bt/btif/src/btif_dm.csystem/bt/btif/include/btif_common.hhardware/libhardware/include/hardware/bluetooth.h首先我们调用蓝牙的时候使用的是Bluetooth的 BluetoothAdapter.java 类 中的enable方法 路径如下frameworks/base/core/java/android/bluetooth/BluetoothAdapter.javapublic boolean enable() {android.util.SeempLog.record(56);if (isEnabled()) {if (DBG) Log.d(TAG, "enable(): BT already enabled!");return true;}try {// 此处调用的是  IBluetoothManager.java的 enablereturn mManagerService.enable(ActivityThread.currentPackageName());} catch (RemoteException e) {Log.e(TAG, "", e);}return false;}我们查看到 IBluetoothManager.java的实现类是BluetoothManagerService.java , BluetoothManagerService.javaclass BluetoothManagerService extends IBluetoothManager.Stub {.....省略代码.....public boolean enable(String packageName) throws RemoteException {final int callingUid = Binder.getCallingUid();final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;.....省略代码.....// 权限检查.....省略代码.....synchronized(mReceiver) {mQuietEnableExternal = false;mEnableExternal = true;// waive WRITE_SECURE_SETTINGS permission checksendEnableMsg(false, packageName);}if (DBG) Slog.d(TAG, "enable returning");return true;}.....省略代码.....}我们继续查看可以看到调用的是 sendEnableMsg(boolean quietMode, String packageName) 方法. private void sendEnableMsg(boolean quietMode, String packageName) {mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,quietMode ? 1 : 0, 0));addActiveLog(packageName, true);}在sendEnableMsg 方法中 发送了一个message消息 MESSAGE_ENABLE case MESSAGE_ENABLE:try {if (mBluetooth != null) {int state = mBluetooth.getState();if (state == BluetoothAdapter.STATE_BLE_ON) {//如果蓝牙已经打开调用 BluetoothAdapterService.java 开启profilemBluetooth.onLeServiceUp();//保存蓝牙的开关状态persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);break;}}}mQuietEnable = (msg.arg1 == 1);//如果没有蓝牙没有打开去打开蓝牙if (mBluetooth == null) {handleEnable(mQuietEnable);} else {if(state == BluetoothAdapter.STATE_TURNING_OFF || state == BluetoothAdapter.STATE_BLE_TURNING_OFF)waitForMonitoredOnOff(false, true);//重启蓝牙Message restartMsg = mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE);mHandler.sendMessageDelayed(restartMsg,2 * SERVICE_RESTART_TIME_MS);}break;在 MESSAGE_ENABLE 接收的消息中会先判断盘牙是否打开,如果已经打开 就调用mBluetooth.onLeServiceUp();去打开 Profile文件如果没有打开就调用handleEnable(mQuietEnable);去打开蓝牙. 咱们这里先不看 mBluetooth.onLeServiceUp(); 方法先查看handleEnable(mQuietEnable)private void handleEnable(boolean quietMode) {if ((mBluetooth == null) && (!mBinding)) {//蓝牙打开超时记录Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);Intent i = new Intent(IBluetooth.class.getName());//此处我们查看到  mConnection 方法是链接状态的回调if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,UserHandle.CURRENT)) {mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);} else {mBinding = true;}}}其中mConnection 是链接的回调 BluetoothServiceConnection 就是 mConnection 类在BluetoothServiceConnection 类中会发送   MESSAGE_BLUETOOTH_SERVICE_CONNECTED 消息并传递了一个 AdapterService对象.private class BluetoothServiceConnection implements ServiceConnection {public void onServiceConnected(ComponentName componentName, IBinder service) {String name = componentName.getClassName();if (DBG) Slog.d(TAG, "BluetoothServiceConnection: " + name);Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);if (name.equals("com.android.bluetooth.btservice.AdapterService")) {msg.arg1 = SERVICE_IBLUETOOTH;}msg.obj = service;mHandler.sendMessage(msg);}public void onServiceDisconnected(ComponentName componentName) {// Called if we unexpectedly disconnect.String name = componentName.getClassName();if (DBG) Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name);Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);if (name.equals("com.android.bluetooth.btservice.AdapterService")) {msg.arg1 = SERVICE_IBLUETOOTH;}mHandler.sendMessage(msg);}}我们继续追踪  MESSAGE_BLUETOOTH_SERVICE_CONNECTED消息 case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:{IBinder service = (IBinder) msg.obj;mBinding = false;mBluetoothBinder = service;mBluetooth = IBluetooth.Stub.asInterface(service);//注册 CallBack 重要,后面的回调要使用try {mBluetooth.registerCallback(mBluetoothCallback);} catch (RemoteException re) {Slog.e(TAG, "Unable to register BluetoothCallback",re);}//Do enable requesttry {//打开蓝牙请求if (mQuietEnable == false) {if (!mBluetooth.enable()) {Slog.e(TAG,"IBluetooth.enable() returned false");}}}break;}MESSAGE_BLUETOOTH_SERVICE_CONNECTED消息里面可以查看到 mBluetooth = IBluetooth.Stub.asInterface(service);获取的是 IBluetooth 对象 , 而AdapterService.java 内部类 AdapterServiceBinder  实现了 IBluetooth packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.javaprivate static class AdapterServiceBinder extends IBluetooth.Stub {private AdapterService mService;public AdapterService getService() {if (mService != null && mService.isAvailable()) {return mService;}return null;}public boolean enable() {........AdapterService service = getService();if (service == null) return false;return service.enable();}}由上面可以看出 return service.enable();调用的是 AdapterService.java 中的方法 enable() 方法 public synchronized boolean enable(boolean quietMode) {enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");debugLog("enable() - Enable called with quiet mode status =  " + mQuietmode);mQuietmode = quietMode;Message m = mAdapterStateMachine.obtainMessage(AdapterState.BLE_TURN_ON);mAdapterStateMachine.sendMessage(m);mBluetoothStartTime = System.currentTimeMillis();return true;}发送的是 BLE_TURN_ON packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterState.java 接受了这个消息 case BLE_TURN_ON:notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON);//将蓝牙的状态转换成 mPendingCommandStatemPendingCommandState.setBleTurningOn(true);transitionTo(mPendingCommandState);sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);adapterService.BleOnProcessStart();break;将了蓝牙的状态转换成 mPendingCommandState 调用 BleOnProcessStart 方法,也可以在supportedProfileServices  方法中去掉自己不想开启的ProfileService.void BleOnProcessStart() {//获取支持的profile 文件Class[] supportedProfileServices = Config.getSupportedProfiles();//Initialize data objectsfor (int i=0; i < supportedProfileServices.length;i++) {mProfileServicesState.put(supportedProfileServices[i].getName(),BluetoothAdapter.STATE_OFF);}mRemoteDevices.cleanup();mAdapterProperties.init(mRemoteDevices);mJniCallbacks.init(mBondStateMachine,mRemoteDevices);//Start Gatt service//调用 ProfileServicesetGattProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);}获取支持的ProfileService列表. private static final Class[] PROFILE_SERVICES = {HeadsetService.class,A2dpService.class,A2dpSinkService.class,HidService.class,HealthService.class,PanService.class,GattService.class,BluetoothMapService.class,HeadsetClientService.class,AvrcpControllerService.class,SapService.class,PbapClientService.class};//发送  ACTION_SERVICE_STATE_CHANGED参数 每一个profile 都有 遍历开启 profile private void setGattProfileServiceState(Class[] services, int state) {for (int i=0; i <services.length;i++) {mProfileServicesState.put(serviceName,pendingState);Intent intent = new Intent(this,services[i]);intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);intent.putExtra(BluetoothAdapter.EXTRA_STATE,state);startService(intent);return;}}}public int onStartCommand(Intent intent, int flags, int startId) {if (intent == null) {Log.d(mName, "Restarting profile service...");return PROFILE_SERVICE_MODE;} else {String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {} else if (state == BluetoothAdapter.STATE_ON) {........省略代码......// 开启服务doStart(intent);}}return PROFILE_SERVICE_MODE;} 调用doStartService之后 我们可以看到调用了 notifyProfileServiceStateChanged 方法。private void doStart(Intent intent) {if (!mStartError) {// 开始调用 notifyProfileServiceStateChangednotifyProfileServiceStateChanged(BluetoothAdapter.STATE_ON);}}继续查看notifyProfileServiceStateChanged 方法 ,里面又回到了AdapterServer 中的 onProfileServiceStateChanged方法protected void notifyProfileServiceStateChanged(int state) {//Notify adapter serviceAdapterService adapterService = AdapterService.getAdapterService();if (adapterService != null) {adapterService.onProfileServiceStateChanged(getClass().getName(), state);}}发送MESSAGE_PROFILE_SERVICE_STATE_CHANGED 消息 给消息处理public void onProfileServiceStateChanged(String serviceName, int state) {Message m = mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED);m.obj=serviceName;m.arg1 = state;mHandler.sendMessage(m);}在消息接收的地方查看到蓝牙的 processProfileServiceStateChanged 方法被调用,在processProfileServiceStateChanged 方法中发送了 BLE_STARTED 消息,而这个消息接受的地方是开起底层ProfileService的地方。case MESSAGE_PROFILE_SERVICE_STATE_CHANGED: {debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");processProfileServiceStateChanged((String) msg.obj, msg.arg1);}break; private void processProfileServiceStateChanged(String serviceName, int state) {if (isBleTurningOn) {if (serviceName.equals("com.android.bluetooth.gatt.GattService")) {debugLog("GattService is started");mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BLE_STARTED));return;}}}/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterState.java BLE_STARTED消息处理任务,通过 adapterService.enableNative(isGuest) 开启底层的profileService。case BLE_STARTED://开启底层服务if (!adapterService.enableNative(isGuest)) {errorLog("Error while turning Bluetooth on");notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);transitionTo(mOffState);}break; 调用的是 AdapterService.java 中的native方法 ,根据包名和JNI 方法的名称我们可以推算出来调用的CPP的名称是 com_android_bluetooth_btservice_AdapterService.cpp  /*package*/ native boolean enableNative(boolean startRestricted);在如下路径找到了 packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp 在enableNative中查看到 int ret = sBluetoothInterface->enable(isGuest == JNI_TRUE ? 1 : 0); 调用的是 sBluetoothInterface 中的enable方法static jboolean enableNative(JNIEnv* env, jobject obj, jboolean isGuest) {ALOGV("%s:",__FUNCTION__);jboolean result = JNI_FALSE;if (!sBluetoothInterface) return result;int ret = sBluetoothInterface->enable(isGuest == JNI_TRUE ? 1 : 0);result = (ret == BT_STATUS_SUCCESS || ret == BT_STATUS_DONE) ? JNI_TRUE : JNI_FALSE;return result;}具体的实现在 bluetooth.c中。 /system/bt/btif/src/bluetooth.cstatic int enable(bool start_restricted) {LOG_INFO(LOG_TAG, "%s: start restricted = %d", __func__, start_restricted);restricted_mode = start_restricted;if (!interface_ready())return BT_STATUS_NOT_READY;stack_manager_get_interface()->start_up_stack_async();return BT_STATUS_SUCCESS;}底层回调传递到上层的时候主要使用的是 btif_dm.c 中对消息的处理 BTA_DM_INQ_RES_EVT 消息system/bt/btif/src/btif_dm.cstatic void btif_dm_search_devices_evt (UINT16 event, char *p_param){...省略代码.....case BTA_DM_INQ_RES_EVT:...省略代码...../* Callback to notify upper layer of device */HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties);break;/system/bt/btif/include/btif_common.hdefine HAL_CBACK(P_CB, P_CBACK, ...) 方法 , HAL_CBACK就是一宏定义,就是调用结构体中对应的方法extern bt_callbacks_t *bt_hal_cbacks;#define HAL_CBACK(P_CB, P_CBACK, ...)\    if (P_CB && P_CB->P_CBACK) {            \        BTIF_TRACE_API("HAL %s->%s", #P_CB, #P_CBACK); \        P_CB->P_CBACK(__VA_ARGS__);         \    }                                       \    else {                                  \        ASSERTC(0, "Callback is NULL", 0);  \    }bt_hal_cbacks 其实就是 蓝牙AdapterService中调用了initNative方法传入到底层的。看下jni中的initNative方法:@Overridepublic void onCreate() {super.onCreate();..........//调用底层方法initNative();........}private native boolean initNative();调用com_android_bluetooth_btservice_AdapterService.cpp init方法 将Callback传递到底层static bool initNative(JNIEnv* env, jobject obj) {.......//调用com_android_bluetooth_btservice_AdapterService.cpp init方法 将Callback传递到底层// 再调用 bluetooth.c 的 init方法int ret = sBluetoothInterface->init(&sBluetoothCallbacks);.......}  com_android_bluetooth_btservice_AdapterService.cpp  再调用 bluetooth.c 的 init方法static int init(bt_callbacks_t *callbacks) {CallBack传递到底层bt_hal_cbacks = callbacks;stack_manager_get_interface()->init_stack();}其实这里有两个地方有  bt_callbacks_t  我们可以对比看一下  1./hardware/libhardware/include/hardware/bluetooth.h 2. /packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp  bluetooth.h 中的 bt_callbacks_ttypedef struct {/** set to sizeof(bt_callbacks_t) */size_t size;adapter_state_changed_callback adapter_state_changed_cb;adapter_properties_callback adapter_properties_cb;remote_device_properties_callback remote_device_properties_cb;device_found_callback device_found_cb;discovery_state_changed_callback discovery_state_changed_cb;pin_request_callback pin_request_cb;ssp_request_callback ssp_request_cb;bond_state_changed_callback bond_state_changed_cb;acl_state_changed_callback acl_state_changed_cb;callback_thread_event thread_evt_cb;dut_mode_recv_callback dut_mode_recv_cb;le_test_mode_callback le_test_mode_cb;energy_info_callback energy_info_cb;} bt_callbacks_t;// com_android_bluetooth_btservice_AdapterService.cpp 中的 bt_callbacks_tstatic bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,discovery_state_changed_callback,pin_request_callback,ssp_request_callback,bond_state_changed_callback,acl_state_changed_callback,callback_thread_event,dut_mode_recv_callback,le_test_mode_recv_callback,energy_info_recv_callback}; static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,discovery_state_changed_callback,pin_request_callback,ssp_request_callback,bond_state_changed_callback,acl_state_changed_callback,callback_thread_event,dut_mode_recv_callback,le_test_mode_recv_callback,energy_info_recv_callback};最后蓝牙驱动打开之后会通过 /system/bt/btif/src/btif_core.c 中的 btif_enable_bluetooth_evt(tBTA_STATUS status) 回调给应用void btif_enable_bluetooth_evt(tBTA_STATUS status){// 省略代码HAL_CBACK(bt_hal_cbacks, adapter_properties_cb, BT_STATUS_SUCCESS, 1, &prop);}然后回调给 adapter_properties_callback:返回手机蓝牙设备的地址、名称、UUIDadapter_state_change_callback 回调 通过 C调用 Java的代码 callbackEnv->CallVoidMethod(sJniCallbacksObj, method_stateChangeCallback, (jint)status);  返回给应用中的也就是回调java层代码 JniCallback.java文件中stateChangeCallback方法。这时跳转到AdapterState.java中,执行stateChangeCallback()方法;发送了ENABLED_READY消息。根据以上分析,这时状态机还处于PendingCommandState,在该状态下处理ENABLED_READY消息, case ENABLED_READY:removeMessages(ENABLE_TIMEOUT);mPendingCommandState.setBleTurningOn(false);transitionTo(mBleOnState);notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);break;做下面两个动作:状态切换到mOnState;更新adapterProperties中的蓝牙状态信息;通知蓝牙状态变为打开。具体看下notifyAdapterStateChange方法。主要是调用了adapterService类的方法。 private void notifyAdapterStateChange(int newState) {AdapterService adapterService = mAdapterService;AdapterProperties adapterProperties = mAdapterProperties;if ((adapterService == null) || (adapterProperties == null)) {errorLog("notifyAdapterStateChange after cleanup:" + newState);return;}int oldState = adapterProperties.getState();adapterProperties.setState(newState);infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);adapterService.updateAdapterState(oldState, newState);}adapterService.updateAdapterState(oldState, newState); 来到adapterService类。 void updateAdapterState(int prevState, int newState){if (mCallbacks !=null) {int n=mCallbacks.beginBroadcast();debugLog("updateAdapterState() - Broadcasting state to " + n + " receivers.");for (int i=0; i <n;i++) {try {// 完成回调 发送广播给调用者                    mCallbacks.getBroadcastItem(i).onBluetoothStateChange(prevState,newState);}  catch (RemoteException e) {debugLog("updateAdapterState() - Callback #" + i + " failed ("  + e + ")");}}mCallbacks.finishBroadcast();}}之前在打开蓝牙操作初期,在BluetoothManagerService中注册了回调方法,因此又跳转到framework中,执行回调方法。蓝牙打开从framework公共接口开始调用enable方法,执行到bluetooth.apk中,在该应用中通过jni注册回调方法和调用hal层打开蓝牙方法,在驱动层完成蓝牙上电等操作后,通过hal-jni回调到应用层中,应用通过aidl回调通知framework蓝牙状态变化,framework发送广播通知大家蓝牙打开。 
  • [问题求助] OBS Android调用求助
    使用的是文档中示例的  implementation 'com.huawei.storage:esdk-obs-android:3.1.3'  在子线程中调用上传,初始可以调用成功,进度回调通过日志可以看到,但过一会就会抛出异常: OBS servcie Error Message. Request Error: javax.net.ssl.SSLException: Write error: ssl=0x7f7a21f280: I/O error during system call, Broken pipe 请问,这个问题是什么引起的?如何解决这个问题? AK SK 不方便说明,但endpoint 为: https://obs.cn-south-1.myhuaweicloud.com
  • [技术干货] 【转载】Android SDK 及native方法 单元测试
    今天可爱的版主我为大家带来软件开发中的android  大家熟知android的作用给我们的生活带来的便利,为大家提供下sdk中的native方法 单元测试:元测试就是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。    对于AndroidSDK来说,我们的单元测试,主要是针对测试人员测试不到的一些内部逻辑方法。同时,可以在转测之前让程序自动运行自测。一、单元测试常用的覆盖率量化标准一般来说路径覆盖率>判定覆盖率>语句覆盖率1、语句覆盖/行覆盖这是一种比较常用的指标,度量的是被测试代码中所有可执行语句是否被执行到,单独一行的花括号{}也常常被统计进去。语句覆盖只管覆盖代码中的可执行语句,但是没有考虑各种分支的组合,常常被指为“最弱的覆盖”。举个例子,被测试代码如下:int foo(int a, int b) {   return  a / b;}只要设计如下一组测试用例,就可以达到覆盖率100%:TeseCase: a = 10, b = 5但是这里有一个比较简单的bug,当b=0的时候,程序就会抛出异常。所以语句覆盖率这个量化标准,并不能直接的表示代码的功能性完整。2、判定覆盖/分支覆盖它度量程序中每一个判定的分支是被测试到了,容易与条件覆盖混淆,所以在条件覆盖中对比说明。3、条件覆盖率它度量判定中的每个子表达式结果true和false是否被测试到了。举一个例子来区分条件覆盖和判定覆盖,被测试代码如下:int foo(int a, int b) {    if (a < 10 || b < 10) { // 判定        return 0;  // 分支一    }    else {        return 1;  // 分支二    }}设计判定覆盖案例时,我们只需要考虑判定结果为true和false两种情况,因此,我们设计如下的案例就能达到判定覆盖率100%:TestCaes1: a = 5, b = 任意数字  // 覆盖了分支一TestCaes2: a = 15, b = 15         // 覆盖了分支二设计条件覆盖案例时,我们需要考虑判定中的每个条件表达式结果,为了覆盖率达到100%,我们设计了如下的案例:TestCase1: a = 5, b = 5       // true,  trueTestCase4: a = 15, b = 15   // false, false通过上面的例子,我们应该很清楚了判定覆盖和条件覆盖的区别。需要特别注意的是:条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。因此,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。比如上面的例子,假如我设计的案例为:TestCase1: a = 5, b = 15   // true,  false   分支一TestCase1: a = 15, b = 5   // false, true    分支一我们看到,虽然我们完整的做到了条件覆盖,但是我们却没有做到完整的判定覆盖,我们只覆盖了分支一。4、路径覆盖/断言覆盖它度量了是否函数的每一个分支都被执行了。举个例子区分一下四种覆盖方式,被测试代码如下:int foo(int a, int b) {    int nReturn = 0;    if (a < 10) { // 分支一        nReturn += 1;    }    if (b < 10) { // 分支二        nReturn += 10;    }    return nReturn;}a.语句覆盖TestCase a = 5, b = 5   // nReturn = 11b.判定覆盖TestCase1 a = 5,   b = 5     // nReturn = 11TestCase2 a = 15, b = 15   // nReturn = 0c.条件覆盖TestCase1 a = 5,   b = 15   // nReturn = 1TestCase2 a = 15, b = 5     // nReturn = 10d.路径覆盖TestCase1 a = 5,    b = 5     // nReturn = 0TestCase2 a = 15,  b = 5     // nReturn = 1TestCase3 a = 5,    b = 15   // nReturn = 10TestCase4 a = 15,  b = 15   // nReturn = 11二、工具选择:JUnit+PowerMockito    JUnit只能mock方法,不能mock实例;并且JUnit使用本地JVM提供运行环境,如果测试的单元依赖了Android框架(或者测试类依赖于其他外部类的情况),比如用到了Android中的Context、SP类的一些方法,本地JVM将无法提供这样的环境,所以我们一般会结合其他开源Mock框架共同完成单元测试。    Android(Java)常用的单元测试开源库一般是Mockito模拟框架,Mockito可以Mock对象,同时可以模拟安卓的运行环境,以及一些service访问返回等。但是,Mokito只能模拟public的方法(网上有通过反射修改private方法access来实现mock方法的,感觉比较麻烦)比较适用于大多标准的单元测试case。    PowerMockito是一个扩展了其他mock框架的、功能更强大的框架。与Mockito的用处总体上一样,都是为了mock外部的、不容易构造的环境,但是PowerMokito可以用于解决更多复杂的情况,PowerMockito可以对private/final/static的方法进行mock,同时PowerMockito支持Mockito和EasyMock。三、常用方法模糊匹配想测试一个方法,但是不想传入精确的值,这时候可以使用Mockito.antInt(),Mockito.anyString()方法。比如我们的日志功能Log.d(tag,msg),我们想mock这个方法,不论传入的tag和msg是什么字符串,就可以使用这个方法,后面的方法会有具体的代码示例。模拟static方法代码中比较常见的就是自定义的static日志方法,Log.d(tagStr, mesStr),测试一个方法的时候,如果里面有日志输出,可以在方法调用之前,mock当调用日志方法的时候,什么都不做,代码如下:PowerMockito.mockStatic(Log.class); try {     PowerMockito.doNothing().when(Log.class, "d", Mockito.anyString(), Mockito.anyString()); } catch (Exception e) {     e.printStackTrace(); }如果是有返回值得static方法,则可能用PowerMockito.doReturn(mockValue).when()或者PowerMockito.when().thenReturn(mockValue)来模拟。如果使用了mockStatic方法,需要在class前面加注解,代码如下:@RunWith(PowerMockRunner.class) @PrepareForTest({Log.class})理论上来说,@PrepareForTests注解也可以放在对应的测试方法上方,但是有时候会报错,所以最好还是统一放在class前面,里面包含了所有需要mock的class。private方法的单元测试基本实现方式是通过反射,将private方法的accessible设为true,之后与正常的public方法的单元测试方法相同。比如待测试类Tested中有待测试方法如下:private long getPrivateMethod(SelfClass self) {     long result = -1;     if (self.methodA()) {         result = 1;     }     return result; }单元测试方法中,反射代码如下:Tested test = new Tested(); Method getPrivate = test.getClass().getDeclaredMethod("getPrivateMethod", Tested.class); getPrivate.setAccessible(true);native方法的单元测试native方法的单元测试从运行环境上也是有两种实现方式:本地单元测试和真机(仪器化)测试。本地单元测试,运行在JVM上,需要依赖于Espresso等模拟安卓环境的工具,好处是运行速度较快,但是仿真度稍差,同时比较麻烦。仪器化测试运行在真机上,好处是仿真度高,同时比较方便,缺点是,仪器化测试除了打包本身的apk外,还会生成test-apk(SDK的话,只会生成test-apk),因为要生成apk,所以速度比较慢。仪器化测试需要将测试代码写在androidTest目录下,如果native方法依赖于本地的so库,则需要以相同的目录结构,在androidTest目录下放置so文件。仪器化测试一般用的是Android自带的AndroidJUnitRunner,不过目前不支持PowerMockito,依赖隔绝不太方便,所以建议尽量使用本地测试,对于依赖于不好模拟的安卓环境,或者耗时较高的文件读写等方法,再使用真机测试。四、IDEA(AndroidStudio)配置依赖配置build.gradle文件:dependencies {......testImplementation 'junit:junit:4.12'testImplementation 'org.mockito:mockito-core:2.8.0'testImplementation 'org.powermock:powermock-module-junit4:1.7.1'testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.1'testImplementation 'org.powermock:powermock-api-mockito2:1.7.1'androidTestImplementation 'com.android.support:support-annotations:24.0.0'androidTestImplementation 'com.android.support.test:runner:0.5'......}需要注意PowerMockito和Mockito的版本对应,否则可能会报java.lang.ClassNotFoundException: org.mockito.exceptions.Reporter。testImplementation 'org.powermock:powermock-api-mockito2:1.7.1'上面的配置需要注意是mockito2。(之前每次使用PowerMockito.doXXX.when()的时候都会报错,升级版本之后就没有了,不确定是不是因为版本的问题,还是这个2的问题)五、覆盖率检测及报告生成:JaCoCo六、可能出现的问题UnfinishedStubbingException1、条件里面嵌套的方法比较多网上的示例基本都是说,when里面的条件比较复杂,就会嵌套调用了方法,可以把方法放到外面来做,网上举的例子错误代码如下:@Testpublic myTest() {    MyMainModel mainModel = Mockito.mock(MyMainModel.class);    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); }修改后代码为:@Testpublic myTest() {    MyMainModel mainModel = Mockito.mock(MyMainModel.class);    List<SomeModel> someModelList = getSomeList();    Mockito.when(mainModel.getList()).thenReturn(someModelList);}网上基本上查到的答案都是上面那个解决方案,不过实际原因可能比较多,还是以Log.d(tag, msg)方法举例:2、条件语句格式的问题PowerMockito.doNothing().when()或者PowerMockito.doReturn.when()的时候,when里面的条件写法也可能导致报错,还是以Log.d(tag, msg)举例:// 错误示例PowerMockito.doNothing().when(log.d(tag, msg));PowerMockito.doNothing().when(log).d(tag, msg);PowerMockito.doNothing().when(log.class, "d", String.class, String.class);// 正确示例PowerMockito.doNothing().when(Log.class, "d", Mockito.anyString(), Mockito.anyString());PowerMockito.doNothing().when(Log.class, "d", "tag", "message");// 这个写法在方法里面写没有问题,但是写在@Before或者@BeforeClass里面就会报错,不知道为啥PowerMockito.doNothing().when(Log.class);类要用class对象而不是具体的实例;通过String的格式传入方法名,此处需要try-catch,因为可能找不到这个方法名的方法;参数要传入具体的实例,或者模糊替代,否则会报找不到对应的方法的错误。找不到Declared的方法参考UnfinishedStubbingException里面条件语句格式问题。
  • [技术干货] 上传Android移动客户端软件包至WI
    1、使用“gandalf”帐号,通过WinSCP登录WI服务器。2、将Android移动客户端通过WinSCP工具上传到WI的“/opt/WI/tomcat/WI/ROOT/plugin”目录下,如图1所示。图1 上传客户端3、修改“/opt/WI/tomcat/WI/ROOT/WEB-INF/conf”文件,如图2所示。androidClientVersion、iosClientVersion的版本号,使其版本号与移动客户端版本号一致。mobiledownload的取值从“0”修改为“1”。图2 修改版本号4、保存修改并退出。5、通过Putty以root帐户登录WI。6、执行以下命令,重启WI服务。service WIService restart7、通过CloudClient登录WI的IP地址。8、输入用户名及密码,成功登录后,根据提示下载移动客户端,如图3所示。图3 下载客户端上传armhf客户端软件包至WI9、使用“gandalf”帐号,通过WinSCP登录WI服务器。10、提取armhf客户端安装包AccessClient_armhf.run,通过WinSCP工具上传到WI的“/opt/WI/tomcat/WI/ROOT/plugin”目录下。11、通过CloudClient登录WI的IP地址。12、输入用户名及密码,成功登录后,根据提示下载armhf客户端。   
  • [分享交流] 导致安卓手机死机照片拍摄者:没想到会造成这么大的破坏
     去年 8 月份在蒙大拿州的冰川国家公园旅行时,科学家兼业余摄影师 Gaurav Agrawal 拍下了一张美丽的日落照片。不幸的是,他在 Flickr 上分享了这张照片后,人们发现该照片竟然能让某些 Android 10 手机死机。上图照片已经过编辑,不会造成死机Agrawal 称,他在 Lightroom 中编辑了该照片,导出照片时选择使用了超宽的 HDR 色彩空间(RGB 色彩空间)。然而,由于 Android 10 内置的色彩渲染引擎无法正确显示照片更大的色彩空间,因此在一些手机(主要是谷歌 Pixel 和三星手机)上将 Agrawal 的照片设置为壁纸时,可能会导致手机开机循环并反复重启,直到进行出厂数据重置。Agrawal 的作品曾出现在《国家地理》杂志上,他最近在接受 BBC 采访时表示,他没想到导出格式会造成这么大的破坏,“我没有故意做任何事情,”他说,“我很难过,人们因此遇到了问题。”“我有一部 iPhone,我的壁纸永远是我妻子的照片。”他告诉该媒体,所以他的手机不会因此死机。IT之家了解到,Android 开发者已经发现了这个问题,并向 Android 开源项目提交了补丁,以帮助防止该 BUG 在未来造成问题。此外,运行 Android 11 测试版的用户表示,由于 Android 11 转换具有不支持颜色空间的图像的方式发生了变化,因此他们的设备也是 “免疫”的。
  • [活动分享] HMS 有没有com.google.android.gms:play-services-location:17.0.0的替代品
    是这样的,我们想研发一个APP,发现华为mete30 不支持 谷歌GMS,因此想更换使用华为的HMS,想问问华为有没有这方面的替代品再一个就是国内手机在国外定位的坐标偏移需要计算一下,想问问有没有相关的资料可查,最好是中东地区的坐标转换资料
  • [大赛专区] 【青高端大赛华为赛道】参赛心得分享-hw70558514_01
    hallo 大家好  我是来自辽宁工业大学田泽:                           昨天比赛成绩出来后真是几家欢喜几家忧啊,我属于中间的可喜的是我拿到了证书愁的是没有达到自己满意的样子,但是无论结果如何这次努力的过程真的让我受益匪浅不仅学到了知识还找到了很多自身的毛病,这次比赛是通过教研室主任发的贴刚开始抱着试试心态,毕竟第一次接触线上开发,不得不说很是新颖满满的科技感 给我带来的更多是新颖,就比如可以随时随地的开发软件,没有了时间地点的限制没有了传统软件的安装等等,我本身是一名大二的学生上学期时参加过开发的比赛 用android 和华为云沃土数字平台的对比简直就是差太多了,比如:                          1:在android中我们需要自己搭建数据库,mysql win+r  -u  root -p  show tables  select *  from user;等等操作语句查看数据库的表格信息导出信息等等,并在android开发软件中查看数据的存储后是否存在等等相关问题,华为云平台可以省去搭建数据库的时间,一些繁琐的操作 可以直接利用数据平台搭建数据库并在数据库中添加JavaScript脚本来简单的执行数据库的存储  删除 查询 选择 重置等相关操作完全的拜托了传统的mysql数据库使数据更加可视化操作更加简单                          2:还有在android中我们debug生成的apk需要安装在模拟器 mumu模拟器或andoird自带的模拟器才能运行,在这里可以直接通过浏览按钮直接浏览当前生成的apk是否符合我们的预期修改起来更加方便,存储更加简单,不占用内存空间。                          3:设计新颖, 传统的软件都是需要一个安装包.exe安装在电脑对应不同的版本不同的包提供不同版本的功能的使用,在华为云沃土数字平台可以直接通过网页版的形式进行数据开发使开发时不需要配置环境变量不需要有任何安装包即可完成一个app系统的开发 大大的缩短了时间                        在看到自己的作品被点评后发现本以为没毛病的自己其实还是有很多内在的问题,粗心!这个词眼说实话对我本人而言还是很有感触的,没有理解这个app 的前因后果就在做无头绪一样,其实是内心的焦躁和粗心使我没有达到自己满意的样子,但是话说回来 比赛给我带来的最终奖品不是多少钱多少码豆是在自身找到了不足应该去改变自己不在犯一样的错误才是最可贵的,华为的比赛不仅丰富了我买的知识还让我们对自己的不足不仅有了解还需要去改善。                        在比赛时结交了渤海大学的朋友结交了南京盐工学院的朋友,我们互相质询,有一次朋友不会部署我app,我帮忙的情况下生成的二维码链接,真的做到了友谊第一比赛第二,这是中华民族的优秀品质不可更改。                       很感谢由大连华为公司举办的这次比赛不仅丰富了我们的生活方式在疫情期间,还让我们学而活用以赛代学为主,更让我们获得了知识,自身的不足如何去完善,还有朋友!很期待华为下次举办的比赛也想像现在一样收获满满!!!期待~~  
  • [问题求助] null
    null
  • [融合视频] android9基线终端播放widevine加密VOD 失败报:widevine请求下载设备证书,其他错误(522013)
    android版本9基线终端播放widevine加密VOD 失败报:widevine请求下载设备证书,其他错误(522013)android版本10 终端播放widevine加密VOD 失败报:播放器widevine库加载失败(522001)
  • [问题求助] ​WELINK视频图像方向倒置了90度,在Android系统层调整了camera sensor方向无效,紧急求助
    WELINK视频图像方向倒置了90度,在Android系统层调整了camera sensor方向,但没有效果,查看log好像welink自己在上层选择了90度,请教一下高人,welink判断摄像头方向的依据是什么?
总条数:181 到第
上滑加载中