安卓应用

【专题分析】使用Intent打开三方应用 · Android应用开发知识仓库 · 看云 (kancloud)

1)AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/tools"
    package="com.example.myapplication">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <activity
//.MainActivity代表包名+.MainActivity,即com.example.myapplication.MainActivity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
//设置LAUNCHER属性即设置当前activity为开机启动
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

2)context

主要有app,service,activity这些子类,相当于系统的身份证。
Context context = this;
//textView要有context才能和系统打交道
TextView textView = new TextView(context);

3)intent

1、网上使用资料

AS往期版本:Android Studio 下载文件归档 | Android 开发者 | Android Developers (google)

//意图,四大组件沟通桥梁,显性启动
        //显性启动
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        //隐形启动,启动网页
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("http://www.baidu"));
        startActivity(intent);
//intent.setFlag():参数
FLAG_ACTIVITY_BROUGHT_TO_FRONT     

  这个标志一般不是由程序代码设置的,如在launchMode中设置singleTask模式时系统帮你设定。

 

FLAG_ACTIVITY_CLEAR_TOP    

  如果设置,并且这个Activity已经在当前的Task中运行,因此,不再是重新启动一个这个Activity的实例,而是在这个Activity上方的所有Activity都将关闭,然后这个Intent会作为一个新的Intent投递到老的Activity(现在位于顶端)中。      例如,假设一个Task中包含这些ActivityABCD。如果D调用了startActivity(),并且包含一个指向Activity BIntent,那么,CD都将结束,然后B接收到这个Intent,因此,目前stack的状况是:AB。      上例中正在运行的Activity B既可以在onNewIntent()中接收到这个新的Intent,也可以把自己关闭然后重新启动来接收这个Intent。如果它的启动模式声明为“multiple”(默认值),并且你没有在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,那么它将关闭然后重新创建;对于其它的启动模式,或者在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,都将把这个Intent投递到当前这个实例的onNewIntent()中。      这个启动模式还可以与FLAG_ACTIVITY_NEW_TASK结合起来使用:用于启动一个Task中的根Activity,它会把那个Task中任何运行的实例带入前台,然后清除它直到根Activity。这非常有用,例如,当从Notification Manager处启动一个Activity。

 

FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET    

  如果设置,这将在TaskActivity stack中设置一个还原点,当Task恢复时,需要清理Activity。也就是说,下一次Task带着FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。      这在你的程序有分割点的时候很有用。例如,一个e-mail应用程序可能有一个操作是查看一个附件,需要启动图片浏览Activity来显示。这个Activity应该作为e-mail应用程序Task的一部分,因为这是用户在这个Task中触发的操作。然而,当用户离开这个Task,然后从主画面选择e-mail app,我们可能希望回到查看的会话中,但不是查看图片附件,因为这让人困惑。通过在启动图片浏览时设定这个标志,浏览及其它启动的Activity在下次用户返回到mail程序时都将全部清除。

 

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS    

  如果设置,新的Activity不会在最近启动的Activity的列表中保存。

 

FLAG_ACTIVITY_FORWARD_RESULT     

  如果设置,并且这个Intent用于从一个存在的Activity启动一个新的Activity,那么,这个作为答复目标的Activity将会传到这个新的Activity中。这种方式下,新的Activity可以调用setResult(int),并且这个结果值将发送给那个作为答复目标的Activity。

 

FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY     

  这个标志一般不由应用程序代码设置,如果这个Activity是从历史记录里启动的(常按HOME键),那么,系统会帮你设定。

 

FLAG_ACTIVITY_MULTIPLE_TASK     

  不要使用这个标志,除非你自己实现了应用程序启动器。与FLAG_ACTIVITY_NEW_TASK结合起来使用,可以禁用把已存的Task送入前台的行为。当设置时,新的Task总是会启动来处理Intent,而不管这是是否已经有一个Task可以处理相同的事情。      由于默认的系统不包含图形Task管理功能,因此,你不应该使用这个标志,除非你提供给用户一种方式可以返回到已经启动的Task。      如果FLAG_ACTIVITY_NEW_TASK标志没有设置,这个标志被忽略。

 

FLAG_ACTIVITY_NEW_TASK     

   如果设置,这个Activity会成为历史stack中一个新Task的开始。一个Task(从启动它的Activity到下一个Task中的Activity)定义了用户可以迁移的Activity原子组。Task可以移动到前台和后台;在某个特定Task中的所有Activity总是保持相同的次序。      这个标志一般用于呈现“启动”类型的行为:它们提供用户一系列可以单独完成的事情,与启动它们的Activity完全无关。      使用这个标志,如果正在启动的ActivityTask已经在运行的话,那么,新的Activity将不会启动;代替的,当前Task会简单的移入前台。参考FLAG_ACTIVITY_MULTIPLE_TASK标志,可以禁用这一行为。      这个标志不能用于调用方对已经启动的Activity请求结果。

 

FLAG_ACTIVITY_NO_ANIMATION    

  如果在Intent中设置,并传递给Context.startActivity()的话,这个标志将阻止系统进入下一个Activity时应用Acitivity迁移动画。这并不意味着动画将永不运行——如果另一个Activity在启动显示之前,没有指定这个标志,那么,动画将被应用。这个标志可以很好的用于执行一连串的操作,而动画被看作是更高一级的事件的驱动。

 

FLAG_ACTIVITY_NO_HISTORY     

  如果设置,新的Activity将不再历史stack中保留。用户一离开它,这个Activity就关闭了。这也可以通过设置noHistory特性。

 

FLAG_ACTIVITY_NO_USER_ACTION     

  如果设置,作为新启动的Activity进入前台时,这个标志将在Activity暂停之前阻止从最前方的Activity回调的onUserLeaveHint()。      典型的,一个Activity可以依赖这个回调指明显式的用户动作引起的Activity移出后台。这个回调在Activity的生命周期中标记一个合适的点,并关闭一些Notification。      如果一个Activity通过非用户驱动的事件,如来电或闹钟,启动的,这个标志也应该传递给Context.startActivity,保证暂停的Activity不认为用户已经知晓其Notification。

 

FLAG_ACTIVITY_PREVIOUS_IS_TOP     

   If set and this intent is being used to launch a new activity from an existing one, the current activity will not be counted as the top activity for deciding whether the new intent should be delivered to the top instead of starting a new one. The previous activity will be used as the top, with the assumption being that the current activity will finish itself immediately.

 

FLAG_ACTIVITY_REORDER_TO_FRONT    

  如果在Intent中设置,并传递给Context.startActivity(),这个标志将引发已经运行的Activity移动到历史stack的顶端。      例如,假设一个Task由四个Activity组成:A,B,C,D。如果D调用startActivity()来启动Activity B,那么,B会移动到历史stack的顶端,现在的次序变成A,C,D,B。如果FLAG_ACTIVITY_CLEAR_TOP标志也设置的话,那么这个标志将被忽略。

 

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

  If set, and this activity is either being started in a new task or bringing to the top an existing task, then it will be launched as the front door of the task. This will result in the application of any affinities needed to have that task in the proper state (either moving activities to or from it), or simply resetting that task to its initial state if needed.

 

FLAG_ACTIVITY_SINGLE_TOP     

  如果设置,当这个Activity位于历史stack的顶端运行时,不再启动一个新的。 
前名一个参数是应用程序的包名,后一个是这个应用程序的主ActivityIntent intent=new Intent();
intent.setComponent(newComponentName("com.droidnova.android.games.vortex",
"com.droidnova.android.games.vortex.Vortex"));
startActivity(intent);

从任意app,启动另外一个app的activity:
1.  Intent i = new Intent(); 
         ComponentName cn = new ComponentName("com.book.android2",  "com.book.android2.AndroidSearch"); 
         i.setComponent(cn); 
         i.setAction("android.intent.action.MAIN"); 
         startActivity(i); //or startActivityForResult(i, RESULT_OK); 
我用这种方法时,绝大部分应用可以启动,但是像RootExplorer却无法启动,出现FC对话框,因此建议使用下面这种方式:
2.   Intent it = new Intent("android.intent.action.MAIN");
it.setClassName("com.speedsoftware.rootexplorer","com.speedsoftware.rootexplorer.RootExplorer");
startActivity(it);

如果你需要启动一个你自己写的另一个app的activity,你可以在那个的menifest.xml里自定义activity的action:
<activity android:name=".MainActivity" android:label="@string/app_name"android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<intent-filter>
  <action android:name="com.qylk.call.main" />    <!-- 自定义的action-->
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
  <category android:name="android.intent.category.DEFAULT" /><!--必须加上这个,否则下面无法直接使用自定的action-->
  </intent-filter>
  </activity>

其他地方启动它:Intent it = new Intent("com.qylk.call.main"); startActivity(it);

3.使用adb启动activity:
启动RootExolorer:
am start -a android.intent.action.MAIN -n com.speedsoftware.rootexplorer/.RootExplorer
启动系统设置:
am start -a android.settings.SETTINGS

附(转载):android系统Action常量(其实不算全)
android intent和intent action大全 

1.从google搜索内容 
Intent intent = new Intent(); 
intent.setAction(Intent.ACTION_WEB_SEARCH); 
intent.putExtra(SearchManager.QUERY,"searchString") 
startActivity(intent); 

2.浏览网页 
Uri uri = Uri.parse("http://www.google"); 
Intent it  = new Intent(Intent.ACTION_VIEW,uri); 
startActivity(it); 

3.显示地图 
Uri uri = Uri.parse("geo:38.899533,-77.036476"); 
Intent it = new Intent(Intent.Action_VIEW,uri); 
startActivity(it); 

4.路径规划 
Uri uri = Uri.parse("http://maps.google/maps?f=dsaddr=startLat%20startLng&daddr=endLat%20endLng&hl=en"); 
Intent it = new Intent(Intent.ACTION_VIEW,URI); 
startActivity(it); 

5.拨打电话 
Uri uri = Uri.parse("tel:xxxxxx"); 
Intent it = new Intent(Intent.ACTION_DIAL, uri);   
startActivity(it); 
 //<uses-permission id="android.permission.CALL_PHONE" /> 


6.调用发短信的程序 
Intent it = new Intent(Intent.ACTION_VIEW);    
it.putExtra("sms_body", "The SMS text");    
it.setType("vnd.android-dir/mms-sms");    
startActivity(it); 

7.发送短信 
Uri uri = Uri.parse("smsto:0800000123");    
Intent it = new Intent(Intent.ACTION_SENDTO, uri);    
it.putExtra("sms_body", "The SMS text");    
startActivity(it); 

String body="this is sms demo"; 
Intent mmsintent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("smsto", number, null)); 
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_BODY, body); 
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_COMPOSE_MODE, true); 
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_EXIT_ON_SENT, true); 
startActivity(mmsintent); 

8.发送彩信 
Uri uri = Uri.parse("content://media/external/images/media/23");    
Intent it = new Intent(Intent.ACTION_SEND);    
it.putExtra("sms_body", "some text");    
it.putExtra(Intent.EXTRA_STREAM, uri);    
it.setType("image/png");    
startActivity(it); 

StringBuilder sb = new StringBuilder(); 
sb.append("file://"); 
sb.append(fd.getAbsoluteFile()); 
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mmsto", number, null)); 
// Below extra datas are all optional. 
intent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_SUBJECT, subject); 
intent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_BODY, body); 
intent.putExtra(Messaging.KEY_ACTION_SENDTO_CONTENT_URI, sb.toString()); 
intent.putExtra(Messaging.KEY_ACTION_SENDTO_COMPOSE_MODE, composeMode); 
intent.putExtra(Messaging.KEY_ACTION_SENDTO_EXIT_ON_SENT, exitOnSent); 
startActivity(intent); 

9.发送Email 
Uri uri = Uri.parse("mailto:xxx@abc"); 
Intent it = new Intent(Intent.ACTION_SENDTO, uri); 
startActivity(it); 

Intent it = new Intent(Intent.ACTION_SEND);    
it.putExtra(Intent.EXTRA_EMAIL, "me@abc");    
it.putExtra(Intent.EXTRA_TEXT, "The email body text");    
it.setType("text/plain");   
 
startActivity(Intent.createChooser(it, "Choose Email Client")); 
Intent it=new Intent(Intent.ACTION_SEND);      
String[] tos={"me@abc"};      
String[] ccs={"you@abc"};      
it.putExtra(Intent.EXTRA_EMAIL, tos);      
it.putExtra(Intent.EXTRA_CC, ccs);      
it.putExtra(Intent.EXTRA_TEXT, "The email body text");      
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");      
it.setType("message/rfc822");      
startActivity(Intent.createChooser(it, "Choose Email Client"));    

Intent it = new Intent(Intent.ACTION_SEND);    
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");    
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3");    
sendIntent.setType("audio/mp3");    
startActivity(Intent.createChooser(it, "Choose Email Client")); 

10.播放多媒体   
Intent it = new Intent(Intent.ACTION_VIEW); 
Uri uri = Uri.parse("file:///sdcard/song.mp3"); 
it.setDataAndType(uri, "audio/mp3"); 
startActivity(it); 

Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");    
Intent it = new Intent(Intent.ACTION_VIEW, uri);    
startActivity(it); 

11.卸载
Uri uri = Uri.fromParts("package", strPackageName, null);    
Intent it = new Intent(Intent.ACTION_DELETE, uri);    
startActivity(it); 

12.安装
Uri installUri = Uri.fromParts("package", "xxx", null); 
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri); 

13. 打开照相机 
    Intent i = new Intent(Intent.ACTION_CAMERA_BUTTON, null); 
    this.sendBroadcast(i);
 
    long dateTaken = System.currentTimeMillis(); 
    String name = createName(dateTaken) + ".jpg"; 
    fileName = folder + name; 
    ContentValues values = new ContentValues(); 
    values.put(Images.Media.TITLE, fileName); 
    values.put("_data", fileName); 
    values.put(Images.Media.PICASA_ID, fileName); 
    values.put(Images.Media.DISPLAY_NAME, fileName); 
    values.put(Images.Media.DESCRIPTION, fileName); 
    values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, fileName); 
    Uri photoUri = getContentResolver().insert( 
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 
    Intent inttPhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 
    inttPhoto.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); 
    startActivityForResult(inttPhoto, 10); 

14.从gallery选取图片 
  Intent i = new Intent(); 
  i.setType("image/*"); 
  i.setAction(Intent.ACTION_GET_CONTENT); 
  startActivityForResult(i, 11); 

15. 打开录音机 
   Intent mi = new Intent(Media.RECORD_SOUND_ACTION); 
   startActivity(mi); 

16.显示应用详细列表       
Uri uri = Uri.parse("market://details?id=app_id");         
Intent it = new Intent(Intent.ACTION_VIEW, uri);         
startActivity(it);         
     


17.寻找应用      
刚才找app id未果,结果发现用package name也可以 
Uri uri = Uri.parse("market://details?id=<packagename>"); 
这个简单多了 
 
Uri uri = Uri.parse("market://search?q=pname:pkg_name");         
Intent it = new Intent(Intent.ACTION_VIEW, uri);         
startActivity(it); 
     
18.打开联系人列表            
           Intent i = new Intent(); 
           i.setAction(Intent.ACTION_GET_CONTENT); 
           i.setType("vnd.android.cursor.item/phone"); 
           startActivityForResult(i, REQUEST_TEXT); 

            Uri uri = Uri.parse("content://contacts/people"); 
            Intent it = new Intent(Intent.ACTION_PICK, uri); 
            startActivityForResult(it, REQUEST_TEXT); 

20.调用系统编辑添加联系人(高版本SDK有效): 
Intent it = new Intent(Intent.ACTION_INSERT_OR_EDIT); 
                it.setType("vnd.android.cursor.item/contact"); 
                // it.setType(Contacts.CONTENT_ITEM_TYPE); 
                it.putExtra("name", "myName"); 
                it.putExtra(android.provider.Contacts.Intents.Insert.COMPANY,  "organization"); 
                it.putExtra(android.provider.Contacts.Intents.Insert.EMAIL, "email"); 
                it.putExtra(android.provider.Contacts.Intents.Insert.PHONE,"homePhone"); 
                it.putExtra( android.provider.Contacts.Intents.Insert.SECONDARY_PHONE, 
                                "mobilePhone"); 
                it.putExtra(  android.provider.Contacts.Intents.Insert.TERTIARY_PHONE, 
                                "workPhone"); 
                it.putExtra(android.provider.Contacts.Intents.Insert.JOB_TITLE,"title"); 
                startActivity(it); 

21.调用系统编辑添加联系人(全有效): 
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); 
            intent.setType(People.CONTENT_ITEM_TYPE); 
            intent.putExtra(Contacts.Intents.Insert.NAME, "My Name"); 
            intent.putExtra(Contacts.Intents.Insert.PHONE, "+1234567890"); 
            intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE, Contacts.PhonesColumns.TYPE_MOBILE); 
            intent.putExtra(Contacts.Intents.Insert.EMAIL, "com@com"); 
            intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE,                    Contacts.ContactMethodsColumns.TYPE_WORK); 
            startActivity(intent); 



23.最基本的share 信息的intent,可以传一段text信息到各个手机上已安装程序:如SMS,Email,sina微波,米聊,facebook,twitter等等 
            Intent it = new Intent(Intent.ACTION_SEND);     
    it.putExtra(Intent.EXTRA_TEXT, "The email subject text"); 
            it.setType("text/plain"); 
            startActivity(Intent.createChooser(it, "Choose Email Client")); 
          

intent action大全: 
ACTION_MAIN                         作为一个主要的进入口,而并不期望去接受数据 
ACTION_VIEW                         向用户去显示数据 
ACTION_ATTACH_DATA                  用于指定一些数据应该附属于一些其他的地方,例如,图片数据应该附属于联系人 
ACTION_EDIT                         访问已给的数据,提供明确的可编辑 
ACTION_PICK                         从数据中选择一个子项目,并返回你所选中的项目 
ACTION_CHOOSER                      显示一个activity选择器,允许用户在进程之前选择他们想要的 
ACTION_GET_CONTENT                  允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音) 
ACTION_DIAL                         拨打一个指定的号码,显示一个带有号码的用户界面,允许用户去启动呼叫 
ACTION_CALL                         根据指定的数据执行一次呼叫(ACTION_CALL在应用中启动一次呼叫有缺陷,多数应用ACTION_DIAL,ACTION_CALL不能用在紧急呼叫上,紧急呼叫可以用ACTION_DIAL来实现) 
ACTION_SEND                         传递数据,被传送的数据没有指定,接收的action请求用户发数据 
ACTION_SENDTO                       发送一跳信息到指定的某人 
ACTION_ANSWER                       处理一个打进电话呼叫 
ACTION_INSERT                       插入一条空项目到已给的容器 
ACTION_DELETE                       从容器中删除已给的数据 
ACTION_RUN                          运行数据,无论怎么 
ACTION_SYNC                         同步执行一个数据 
ACTION_PICK_ACTIVITY                为已知的Intent选择一个Activity,返回选中的类 
ACTION_SEARCH                       执行一次搜索 
ACTION_WEB_SEARCH                   执行一次web搜索 
ACTION_FACTORY_TEST                 工场测试的主要进入点, 


标准的广播Actions 
ACTION_TIME_TICK                   当前时间改变,每分钟都发送,不能通过组件声明来接收,只有通过Context.registerReceiver()方法来注册 
ACTION_TIME_CHANGED                时间被设置 
ACTION_TIMEZONE_CHANGED            时间区改变 
ACTION_BOOT_COMPLETED              系统完成启动后,一次广播 
ACTION_PACKAGE_ADDED               一个新应用包已经安装在设备上,数据包括包名(最新安装的包程序不能接收到这个广播) 
ACTION_PACKAGE_CHANGED             一个已存在的应用程序包已经改变,包括包名 
ACTION_PACKAGE_REMOVED             一个已存在的应用程序包已经从设备上移除,包括包名(正在被安装的包程序不能接收到这个广播) 
ACTION_PACKAGE_RESTARTED           用户重新开始一个包,包的所有进程将被杀死,所有与其联系的运行时间状态应该被移除,包括包名(重新开始包程序不能接收到这个广播) 
ACTION_PACKAGE_DATA_CLEARED        用户已经清除一个包的数据,包括包名(清除包程序不能接收到这个广播) 
ACTION_BATTERY_CHANGED             电池的充电状态、电荷级别改变,不能通过组建声明接收这个广播,只有通过Context.registerReceiver()注册 
ACTION_UID_REMOVED     一个用户ID已经从系统中移除

android.intent.action.ALL_APPS
android.intent.action.ANSWER
android.intent.action.ATTACH_DATA
android.intent.action.BUG_REPORT
android.intent.action.CALL
android.intent.action.CALL_BUTTON
android.intent.action.CHOOSER
android.intent.action.CREATE_LIVE_FOLDER
android.intent.action.CREATE_SHORTCUT
android.intent.action.DELETE
android.intent.action.DIAL
android.intent.action.EDIT
android.intent.action.GET_CONTENT
android.intent.action.INSERT
android.intent.action.INSERT_OR_EDIT
android.intent.action.MAIN
android.intent.action.MEDIA_SEARCH
android.intent.action.PICK
android.intent.action.PICK_ACTIVITY
android.intent.action.RINGTONE_PICKER
android.intent.action.RUN
android.intent.action.SEARCH
android.intent.action.SEARCH_LONG_PRESS
android.intent.action.SEND
android.intent.action.SENDTO
android.intent.action.SET_WALLPAPER
android.intent.action.SYNC
android.intent.action.SYSTEM_TUTORIAL
android.intent.action.VIEW
android.intent.action.VOICE_COMMAND
android.intent.action.WEB_SEARCH
android.net.wifi.PICK_WIFI_NETWORK

SETTING:
android.settings.AIRPLANE_MODE_SETTINGS
android.settings.APN_SETTINGS
android.settings.APPLICATION_DEVELOPMENT_SETTINGS
android.settings.APPLICATION_SETTINGS
android.settings.BLUETOOTH_SETTINGS
android.settings.DATA_ROAMING_SETTINGS
android.settings.DATE_SETTINGS
android.settings.DISPLAY_SETTINGS
android.settings.INPUT_METHOD_SETTINGS
android.settings.INTERNAL_STORAGE_SETTINGS
android.settings.LOCALE_SETTINGS
android.settings.LOCATION_SOURCE_SETTINGS
android.settings.MANAGE_APPLICATIONS_SETTINGS
android.settings.MEMORY_CARD_SETTINGS
android.settings.NETWORK_OPERATOR_SETTINGS
android.settings.QUICK_LAUNCH_SETTINGS
android.settings.SECURITY_SETTINGS
android.settings.SETTINGS
android.settings.SOUND_SETTINGS
android.settings.SYNC_SETTINGS
android.settings.USER_DICTIONARY_SETTINGS
android.settings.WIFI_IP_SETTINGS
android.settings.WIFI_SETTINGS
android.settings.WIRELESS_SETTINGS

在android SDK文档中有这样一个类,android.provider.Settings类提供android系统各个页面的跳转常量:
使用实例例:startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)),即可跳到android手机网络设置页面。
如果要launch Mobile Networks Setting页面按如下方法:
Intent intent=new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
ComponentName cName = new ComponentName(“com.android.phone”,com.android.phone.Settings);
intent.setComponent(cName);
startActivity(intent);

如果要进入Networks Operators页面按如下方法:
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(“com.android.phone”,com.android.phone.NetworkSetting);
startActivity(intent);

String ACTION_ACCESSIBILITY_SETTINGS
辅助功能模块的显示设置。 Activity Action: Show settings for accessibility modules.
String ACTION_ADD_ACCOUNT
显示屏幕上创建一个新帐户添加帐户。 Activity Action: Show add account screen for creating a new account.
String ACTION_AIRPLANE_MODE_SETTINGS
显示设置,以允许进入/退出飞行模式。 Activity Action: Show settings to allow entering/exiting airplane mode.
String ACTION_APN_SETTINGS
显示设置,以允许配置的APN。 Activity Action: Show settings to allow configuration of APNs.
String ACTION_APPLICATION_DETAILS_SETTINGS
有关特定应用程序的详细信息的显示屏幕。 Activity Action: Show screen of details about a particular application.
String ACTION_APPLICATION_DEVELOPMENT_SETTINGS
显示设置,以允许应用程序开发相关的设置配置 Activity Action: Show settings to allow configuration of application development-related settings.
String ACTION_APPLICATION_SETTINGS
显示设置,以允许应用程序相关的设置配置 Activity Action: Show settings to allow configuration of application-related settings.
String ACTION_BLUETOOTH_SETTINGS
显示设置,以允许蓝牙配置 Activity Action: Show settings to allow configuration of Bluetooth.
String ACTION_DATA_ROAMING_SETTINGS
选择of2G/3G显示设置 Activity Action: Show settings for selection of2G/3G.
String ACTION_DATE_SETTINGS
显示日期和时间设置,以允许配置 Activity Action: Show settings to allow configuration of date and time.
String ACTION_DEVICE_INFO_SETTINGS
显示一般的设备信息设置(序列号,软件版本,电话号码,等) Activity Action: Show general device information settings (serial number, software version, phone number, etc.).
String ACTION_DISPLAY_SETTINGS
显示设置,以允许配置显示 Activity Action: Show settings to allow configuration of display.
String ACTION_INPUT_METHOD_SETTINGS
特别配置的输入方法,允许用户启用输入法的显示设置 Activity Action: Show settings to configure input methods, in particular allowing the user to enable input methods.
String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
显示设置来启用/禁用输入法亚型 Activity Action: Show settings to enable/disable input method subtypes.
String ACTION_INTERNAL_STORAGE_SETTINGS
内部存储的显示设置 Activity Action: Show settings for internal storage.
String ACTION_LOCALE_SETTINGS
显示设置,以允许配置的语言环境 Activity Action: Show settings to allow configuration of locale.
String ACTION_LOCATION_SOURCE_SETTINGS
显示设置,以允许当前位置源的配置 Activity Action: Show settings to allow configuration of current location sources.
String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS
显示设置来管理所有的应用程序 Activity Action: Show settings to manage all applications.
String ACTION_MANAGE_APPLICATIONS_SETTINGS
显示设置来管理安装的应用程序 Activity Action: Show settings to manage installed applications.
String ACTION_MEMORY_CARD_SETTINGS
显示设置为存储卡存储 Activity Action: Show settings for memory card storage.
String ACTION_NETWORK_OPERATOR_SETTINGS
选择网络运营商的显示设置 Activity Action: Show settings for selecting the network operator.
String ACTION_PRIVACY_SETTINGS
显示设置,以允许配置隐私选项 Activity Action: Show settings to allow configuration of privacy options.
String ACTION_QUICK_LAUNCH_SETTINGS
显示设置,以允许快速启动快捷键的配置 Activity Action: Show settings to allow configuration of quick launch shortcuts.
String ACTION_SEARCH_SETTINGS
全局搜索显示设置 Activity Action: Show settings for global search.
String ACTION_SECURITY_SETTINGS
显示设置,以允许配置的安全性和位置隐私 Activity Action: Show settings to allow configuration of security and location privacy.
String ACTION_SETTINGS
显示系统设置 Activity Action: Show system settings.
String ACTION_SOUND_SETTINGS
显示设置,以允许配置声音和音量 Activity Action: Show settings to allow configuration of sound and volume.
String ACTION_SYNC_SETTINGS
显示设置,以允许配置同步设置 Activity Action: Show settings to allow configuration of sync settings.
String ACTION_USER_DICTIONARY_SETTINGS
显示设置来管理用户输入字典 Activity Action: Show settings to manage the user input dictionary.
String ACTION_WIFI_IP_SETTINGS
显示设置,以允许配置一个静态IP地址的WiFi Activity Action: Show settings to allow configuration of a static IP address for Wi-Fi.
String ACTION_WIFI_SETTINGS
显示设置,以允许WiFi配置 Activity Action: Show settings to allow configuration of Wi-Fi.
String ACTION_WIRELESS_SETTINGS
显示设置,以允许配置,如WiFi,蓝牙和移动网络的无线控制 Activity Action: Show settings to allow configuration of wireless controls such as Wi-Fi, Bluetooth and Mobile networks.
String AUTHORITY
String EXTRA_AUTHORITIES
在推出活动的基础上给予的权力限制可选项。 Activity Extra: Limit available options in launched activity based on the given authority.
String EXTRA_INPUT_METHOD_ID

2、显示intent

//AndroidManifest.xml
//要注册一下类才可以启动这个activity
<activity android:name=".MyAty"/>
//创建一个子类绑定layout
public class MyAty extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //绑定layout
        setContentView(R.layout.activity_main);
    }
}
public class MainActivity extends AppCompatActivity {
    private Button btn;//声明按钮的变量
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=findViewById(R.id.nan_btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                btn.setText("按钮点击事件的第一种已经触发~~~");
                //说的很明确了要启动哪个类的activity所以是显示intent
                startActivity(new Intent(MainActivity.this,MyAty.class));
            }
        });
    }
}

3、隐式intent

AndroidManifest.xml
//android:exported=true则其它应用可以访问,如果为false那么其它应用就访问不了了
 <activity android:name=".MyAty"
            android:exported="true">
        //隐式intent是靠遍历合适条件的intent来启动的因此需要过滤器
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>
                //action 字符串叫什么都可以,可以被找到就行
                <action android:name="com.example.myapplication.intent.action.MyAty"/>
            </intent-filter>
        </activity>
//其它同上
public class MyAty extends Activity {
    public static final String ACTION = "com.example.myapplication.intent.action.MyAty";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        btn.setText("按钮点击事件的第一种已经触发~~~");
        //通过manifest里的intent过滤的android name来启动activity,
        //startActivity(new Intent("com.example.myapplication.intent.action.MyAty"));     
        startActivity(new Intent(MyAty.ACTION));
    }
});

4)try-catch的使用以及细节

1、基本语法

try{
//可疑代码
//将异常生成对象的异常对象传递给catch块
}catch(异常){
//对异常进行处理
}finally{
} //可以没有finally

2、语法细节

  1. 如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch
  2. 如果异常没有发生,则顺序执行try的代码块,不会进入到catch
  3. 如果希望发不发生异常都进入到某段代码段 例如:关闭数据库的连接,则使用 finally{ }
public static void main(String[] args) {

    //ctrl + alt +t 
    try {
        String str = "123";
        int a = Integer.parseInt(str);
        System.out.println(a);
    } catch (NumberFormatException e) {//判断数字格式的异常
        System.out.println("异常信息" + e.getMessage());
    }finally {
        System.out.println("finally代码块被执行");
    }
    System.out.println("程序继续");
}
*****************************************************************
运行结果为:
123
finally代码被执行
程序继续
*****************************************************************
当更改为"abc"时,语句运行结果为:
异常信息For input string: "abc"
finally代码块被执行
程序继续

3、多个catch语句情况

可以有多个catch语句,捕捉不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如Exception 在后 ,NullPointerException在前,如果发生异常,只会匹配一个catch

public class TryCatchDetai {
    public static void main(String[] args) {

        try {
            Person person = new Person();
            person = null;
            System.out.println(person.getName());
            int n1 = 10;
            int n2 = 0;
            int res = n1/n2;
        }catch (NullPointerException e){
            System.out.println("空指针异常 " + e.getMessage());
        }
        catch (Exception e){		//Exception e为任意异常
            System.out.println("算术异常" + e.getMessage());
        }finally {

        }
    }
}

class Person{
    private String name = "yayaya";

    public String getName(){
        return name;
    }
}
****************************************************
运行的结果为:
空指针异常:null
但是实际上还有一个异常为算术异常 及分母不能为0,因为已经找到一个异常就不会捕获下一个异常。
如果更改获取异常的顺序,编译器就会报错。所以父类异常需要在后,子类异常需要在前。

5)Handler消息机制

1、应用程序启动的时候,在主线程会默认调用了LOOPER.preper()方法,初始化Looper对象并绑定到当前线程中,并在LOOPER内部维护一个MessageQueue。

2、接着调用handler.sendMessage()发送消息,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息。

3、主线程调用LOOPER.kooper()开启循环,不断轮询消息队列,通过MessageQueue.next()取出消息。

4、取出的message部位空则调用msg.target.dispatchMessage()传递分发消息,目标handler收到消息后会执行handler.handlerMessage()方法处理消息。


子线程:handler.sendMessage(msg)
主线程:handler.handleMessage(msg)

5、looper对象

一个looper对应一个handler再对应一个messageQueue。

安卓中如果在子线程中对UI进行更改则会抛出警告,这个时候就要用handler。

looper对象用于对一个线程开启消息循环。新创建的子线程是没有looper对象的,主线程除外。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-je6EWFoD-1680608690283)(图库/android底层开发9.png)]

Looper创建线程过程

import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.os.Handler;

public class LooperThread extends Thread{
    public Handler handler1; //声明一个Handler对象

    @Override
    public void run() {
        super.run();
        Looper.prepare(); //初始化looper对象

        handler1 = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Log.i("Looper",String.valueOf(msg.what));
            }
        };

        Message m = handler1.obtainMessage();//获取一个消息
        m.what = 0x11; //设置Message的what属性值的值
        handler1.sendMessage(m);
        Looper.loop();
    }
}
public class MainActivity extends Activity {

    private Thread thread;
    private static MediaPlayer mp = null;//声明一个播放器对象
    int i;//循环变量

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

        LooperThread thread = new LooperThread(); //创建线程
        thread.start();// 开启线程
    }
}
//logcat
2022-11-13 22:21:13.749 10983-11028/com.example.myapplication I/Looper: 17

6、Handler类

Handler类用于子线程与主线程之间的通讯。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2UAkECgD-1680608690286)(图库/android底层开发10.png)]

7、Message类

Message存放在消息队列中。Message类的使用方式比较简单,有几点:

  • 通常使用message.obtain()或handler.message方法来从消息池中获取空消息对象以节省资源
  • 如果一个message只需要获取简单的整形信息,因优先使用arg1、arg2来传递消息
  • 尽可能用msg.what来标记信息

例:获取网络图片参数为URI

public class MainActivity extends Activity {

    private ImageView iv; //声明ImageView组件的对象

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

        iv = (ImageView) findViewById(R.id.imagView1);// 获取布局管理器中添加的imageView

        new Thread(new Runnable() {
            @Override
            public void run() {
                final Bitmap bitmap = getPicture("https://pics4.baidu/feed/3b292df5e0fe99252bd6683eba2d1ed48cb17112.jpeg@f_auto?token=899cd1cfd0f29ba5b566d76a6c8eb742"); //网络上获取资源
                try {
                    Thread.sleep(2000);     //线程休眠2秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //发送一个Runnable对象
                iv.post(new Runnable() {
                    @Override
                    public void run() {
                        iv.setImageBitmap(bitmap); //在imageView中显示从网络上获取到的图片
                    }
                });
            }
        }).start();//开启线程
    }

    /**
     * 功能:根据网址获取图片对应的Bitmap对象
     *
     * @param path
     * @return
     */
    public Bitmap getPicture(String path) {
        Bitmap bm = null;
        try {
            URL uri = new URL(path); //创建URL对象
            URLConnection conn = uri.openConnection(); //获取URL对象对应的连接
            conn.connect();// 打开连接
            InputStream is = conn.getInputStream(); //获取输入流对象
            bm = BitmapFactory.decodeStream(is); //根据输入流对象创建Bitmap对象
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bm;
    }
}

8、例:显示电子广告图

public class MainActivity extends Activity implements Runnable {
    private ImageView iv;//声明一个显示图片的image对象
    private Handler handler;//声明一个handler对象
    private int[] path = new int[] {R.drawable.ic_launcher_1,R.drawable.ic_launcher_10,R.drawable.ic_launcher_100,R.drawable.ic_launcher_10000};
    private String[] title = new String[] {"系列产品", "高校开发", "快乐分享", "快速学习"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        UserAPI.getInstance().init(getApplicationContext());
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.imageView1);// 获取显示广告图片的ImageView
        Thread t = new Thread(this);// 创建新线程
        t.start();// 开启线程
        //实例化一个Handler对象
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg){
                TextView tv = (TextView) findViewById(R.id.textView1);// 获取TextView组件
                if (msg.what == 0x101){
                    tv.setText(msg.getData().getString("title"));// 设置标题
                    iv.setImageResource(path[msg.arg1]);// 设置要显示的图片
                }
                super.handleMessage(msg);
            }
        };

    }

    @Override
    public void run() {
        int index = 0;
        while(!Thread.currentThread().isInterrupted()){
            index = new Random().nextInt(path.length);// 产生一个随机数
            Message m = handler.obtainMessage();// 获取一个Message对象
            m.arg1 = index;//保存要显示广告的索引值
            Bundle bundle = new Bundle();// 获取Bundle对象
            m.what = 0x101;// 设置消息标识
            bundle.putString("title", title[index]);
            m.setData(bundle);// 将Bundle对象保存到Message中
            handler.sendMessage(m);// 消息
        }
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();// 输出异常信息
        }
    }
}

6)context用于访问全局变量

路径:/values/strings.xml,左边是id,右边是对应的值
<resources>
    <string name="app_name">My Application</string>
    <string name="hello_world">woshifeiwu</string>
</resources>
//MainActivity.java
private TextView tv;
//显示一句话
tv = new TextView(this);
tv.setText("woshifeiwu");
tv.setText(R.string.hello_world);
setContentView(tv);
//显示一张图
ImageView iv = new ImageView(this);
iv.setImageResource(R.mipmap.ic_launcher);
setContentView(iv);

7)boardcast广播

1、动态注册

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView mBatteryLevelText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        findViewById(R.id.btnSendMsg).setOnClickListener(this);
        registerBatteryReceiver();
        initView();
    }

    private void initView() {
        mBatteryLevelText = this.findViewById(R.id.battery_level);
    }
//
    private void registerBatteryReceiver() {
        //第二步:我们要收听的频道是:电量变化
        IntentFilter intentFilter = new IntentFilter();
        //第三步:设置频道
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
        intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
        intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        //第四步:电池服务实例
        BatteryLevelReceiver batteryLevelReceiver = new BatteryLevelReceiver();
        //第五步:注册广播
        this.registerReceiver(batteryLevelReceiver,intentFilter);
    }

    private class BatteryLevelReceiver extends BroadcastReceiver{
    /*
     *第一步,就是创建一个广播接收者,继承自BatteryLevelReceiver
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
            int currentLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL,0);
            Log.d(TAG, "收到了电量变化的广播:actions is = " + action);
            Log.d(TAG,"当前电量 : " + currentLevel);
//开机系统上来的时候可能还没有实例化,因此先判空。
            if (mBatteryLevelText != null) {
                mBatteryLevelText.setText("当前电量:" + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0));
            }
            int maxLevel = intent.getIntExtra(BatteryManager.EXTRA_SCALE,0);
            float percent = currentLevel * 1.0f / maxLevel;
            Log.d(TAG,"当前电量百分比是:" + percent + "%");
        }else if (Intent.ACTION_POWER_CONNECTED.equals(action)){
            Log.d(TAG,"usb连接上了...");
        }else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)){
            Log.d(TAG,"usb线断开了...");
        }
    }
        protected void onDestroy() {
            super.onDestroy();
            //取消广播注册,否则会导致内存泄露
            if(mBatteryLevelReceiver != null){
                this.unregisterReceiver(mBatteryLevelReceiver);
            }
        }
}
************************************************************
logcat:
2022-10-29 15:28:30.401 22716-22716/com.example.app1 D/ContentValues: 收到了电量变化的广播:actions is = android.intent.action.BATTERY_CHANGED
2022-10-29 15:28:30.402 22716-22716/com.example.app1 D/ContentValues: 当前电量 : 42

封装小技巧:框选几行代码右键->Refactor->extractedMethod

2、静态注册

静态注册可以即使不打开apk也可以

//BootCompleteReceiver.java
/**
 * 第一步,创建一个类,并且继承自BroadcastReceiver
 */
public class BootCompleteReceiver extends BroadcastReceiver {

    private static final String TAG = "BootCompleteReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG,"action is = " + action);
        Log.d(TAG,"开机完成");
        Toast.makeText(context,"收到开机完成的广播...", Toast.LENGTH_SHORT).show();
    }
}
//AndroidManifest.xml
        <receiver android:name=".BootCompleteReceiver"
            android:exported="true">
            <!--第二步,其实就是跟动态注册设置这个action是一样的-->
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>
    <!--第三步,给广播加上权限-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

3、自定义广播以及添加

1)广播添加

//AndroidManifest.xml       
<receiver android:name=".MessageReceiver" android:exported="true">
            <intent-filter>
                <action android:name="com.intent.action.SEND_MSG"/>
            </intent-filter>
        </receiver>
//SendBroadcastActivity.java
package com.example.app1;

import static com.example.app1.Constants.ACTION_SEND_MSG;
import static com.example.app1.Constants.KEY_CONTENT;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.NoCopySpan;
import android.view.View;
import android.widget.EdgeEffect;
import android.widget.EditText;

import androidx.annotation.Nullable;

public class SendBroadcastActivity extends Activity {
    private EditText minputBox;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_send);
        minputBox = this.findViewById(R.id.be_send_msg_input_box);
    }

    /**
     * 当我们点击完发广播以后,这个方法就会被调用
     */
    public void sendBroadcast(View view) {
        //被调用以后我们就去发广播
        String content = minputBox.getText().toString();
        Intent intent = new Intent();
        intent.setAction(Constants.ACTION_SEND_MSG);
        intent.putExtra(KEY_CONTENT,content);
        //发射广播
        sendBroadcast(intent);
    }
}
//Constants.java
package com.example.app1;

public class Constants {
    public static final String ACTION_SEND_MSG = "com.intent.action.SEND_MSG";
    public static final String KEY_CONTENT = "content";
}

2)广播接收者添加,同静态广播

package com.example.app1;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MessageReceiver extends BroadcastReceiver {
    private static final String TAG = "MessageReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG,"action is -- > " + action);
        String content = intent.getStringExtra(Constants.KEY_CONTENT);
        Log.d(TAG,"content is -- > " + content);
    }
}

8)android线程

1、创建线程

1)继承Runnable类来重写run方法
public class MainActivity extends AppCompatActivity implements Runnable{

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

    @Override
    public void run() {

    }
}

2、创建Thread类实例来实现run方法
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
    }

2、开启线程

thread.start();

3、线程的休眠

thread.sleep(1000);

4、中断线程

thread.interrupt();

5、例1:创建Runnable来实现计数

public class MainActivity extends AppCompatActivity implements Runnable{

    private Thread thread;
    int i;//循环变量
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startButton = (Button)findViewById(R.id.Button1);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                i = 0;
                thread = new Thread(MainActivity.this);
                thread.start();
            }
        });
        Button stopButton = (Button)findViewById(R.id.Button2);
        stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(thread!=null){
                    thread.interrupt();//中断线程
                    thread=null;
                }
                Log.i("提示:","中断线程");
            }
        });
    }

    @Override
    protected void onDestroy() {
        if(thread!=null){
            thread.interrupt();//中断线程
            thread=null;
        }
        Log.i("提示:","中断线程");
        super.onDestroy();
    }

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()){
            i++;
            Log.i("循环变量:",String.valueOf(i));
        }
    }
}

6、开启一个新线程播放背景音乐

public class MainActivity extends AppCompatActivity implements Runnable{

    private Thread thread;
    private static MediaPlayer mp = null;//声明一个播放器对象
    int i;//循环变量
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button playerButton = (Button)findViewById(R.id.Button3);
        playerButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ((Button) view).setEnabled(false);//设置按钮不可用
                //创建一个用于播放背景音乐的线程
                thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        playBGSound();
                    }
                });
                thread.start();
            }
        });

    }

    private void playBGSound() {
        if(mp != null){
            mp.release();//释放资源
            mp = MediaPlayer.create(MainActivity.this, R.raw.media);
            mp.start();
            mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    try {
                        Thread.sleep(5000);//线程休眠5秒钟
                        playBGSound();// 重新播放音乐
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    @Override
    protected void onDestroy() {
        Log.i("提示:","音乐停止");
        if(mp != null){
            mp.stop();// 停止播放
            mp.stop();// 释放资源
            mp = null;
        }
        if(thread!=null){
            thread.interrupt();//中断线程
            thread=null;
        }
        super.onDestroy();
    }

9)Service的分类

(21条消息) android入门之创建service_android studio 创建service_晓翔仔的博客-CSDN博客

[(21条消息) framework层Service的基础知识和常见问题_AmyTan小小燕的博客-CSDN博客_attempted to start a foreground service with a bro](https://blog.csdn/xiaoyantan/article/details/119992728?ops_request_misc=%7B%22request%5Fid%22%3A%22167696267616800211520263%22%2C%22scm%22%3A%2220140713.130102334…%22%7D&request_id=167696267616800211520263&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-119992728-null-null.142v73control_1,201v4add_ask,239v2insert_chatgpt&utm_term=Background start not allowed&spm=1018.2226.3001.4187)

(21条消息) 浅析Android Service 中 onStartCommand方法及注意事项_WiseSun7的博客-CSDN博客_onstartc
(21条消息) startService流程详解_尤教授的博客-CSDN博客_start service

  • started(启动):当应用组件(例如Activity)通过调用startService()方法启动服务时,服务处于“started状态”。一但启动服务会在后台无限期运行,即使启动它的组件已经被销毁。通常,启动服务执行单个操作不会回调。例如,它它可能通过网络下载或者上传文件。如果操作完成,服务器需要停止自身。
  • bound类,当应用程序通过调用bindService()方法绑定到服务时,服务处于“bound”状态。绑定服务提供CS接口以允许组件与服务交互、发送请求、获得结果、甚至使用IPC跨进程操作。

继承Service类

package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.format.Time;
import android.util.Log;

import java.security.Provider;

public class CurrentTimeService extends Service {
    public IBinder onBind(Intent intent){
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Time time = new Time(); // 创建Time对象
        time.setToNow(); // 设置时间为当前时间
        String currentTime = time.format("%Y-%m-%d %H:%H:%S");// 设置时间格式
        Log.i("CurrentTimeService", currentTime);// 记录当前时间
        return START_STICKY;
    }
}

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button currentTime = (Button) findViewById(R.id.Button3);
        currentTime.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startService(new Intent(MainActivity.this, CurrentTimeService.class));//启动服务
            }
        });
    }
}

继承Binder类

实现步骤如下:

1、在服务中,创建Binder类实例来完成下列操作之一

  • 包含客户端能调用的公共方法
  • 返回当前service实例,其中包含客户端能够调用的公共方法
  • 或者,返回服务管理的其他类的实例吗,其中包含客户端能调用的公共方法。

2、从onBind()回调方法中返回Binder实例。

3、在客户端,从onServiceConnected()回调方法接收Binder类实例,并且使用提供的方法调用绑定服务。

使用Message类

如果开发人员需要服务与远程进程通讯,则可以使用Message来为服务提供接口,该技术允许不适用AIDL执行进程间通讯IPC。下面是关于如何使用Message的总结():

  • 实现了Handler的服务因为每次从客户端调用而接收回调
  • Handler用于创建Message对象(它是handler的引用)
  • Message创建IBinder,服务从onBind()方法将其返回到客户端。
  • 客户端使用IBinder来实例化Message,并通过发送Message对象到服务
  • 服务在其Handler的handlerMessage()方法接收Message

此时,没有供客户端在服务上调用的方法。

绑定到服务

如果需要从客户端绑定到服务,需要完成以下操作:

1、实现ServiceConnection,这需要重写onServiceConnected()和onServiceDisconnected()两个回调方法:

2、调用bindService()方法,传递ServiceConnection实现:

3、当系统调用onServiceConnected()回调方法时,就可以使用接口定义的方法调用服务;

4、调用unbindService()方法解绑定

服务启动线程例子

启动方式

context.startService(HttpGetInformationService.getIntent(context));
package com.example.myapplication;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.lang.ref.WeakReference;

public class SysInfoReportService extends Service {
    private static final boolean DEBUG = true;
    private static final String TAG = "SysInfoReportService";


    MyHandler readHandler;

    public static final int MSG_START_SERIAL_PORT = 0;

    public static Intent getIntent(Context context) {
        return new Intent(context, SysInfoReportService.class);
    }

    public SysInfoReportService() {
    }

    public void test() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");

        HandlerThread readThread = new HandlerThread("readThread");
        readThread.start();
        readHandler = new MyHandler(this, readThread.getLooper());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        readHandler.sendEmptyMessage(MSG_START_SERIAL_PORT);
        return super.onStartCommand(intent, flags, startId);
    }



    private static class MyHandler extends Handler {
        private WeakReference<SysInfoReportService> mServices;

        public MyHandler(SysInfoReportService spb, Looper l) {
            super(l);
            mServices = new WeakReference<SysInfoReportService>(spb);
        }

        @Override
        public void handleMessage(Message msg) {
            SysInfoReportService srs = mServices.get();
            if (srs == null) {
                return;
            }

            switch (msg.what) {
                case MSG_START_SERIAL_PORT:
                    Log.d(TAG, Thread.currentThread().getName());
                    srs.test();
                    sendEmptyMessageDelayed(MSG_START_SERIAL_PORT, 10 * 1000);
                    break;
            }
        }
    }
}

10)init进程

1、init进程任务

  • 第一个应用程序
  • 创建目录,挂载分区
  • 解析启动脚本
  • 启动服务
  • 守护服务

2、源码对应接口

3、Android启动脚本

路径:device/rockchip/common/recovery/etc/init.rc

基本语法:1、import:加入其它.rc脚本

2、on 设定服务的启动时间(trigger)

on <trigger>   ##触发条件
     <command1>  ##执行命令
     <command2>  ##可以同时执行多个命令
     <command3>
service xbh_sdcard_preinstall /system/bin/xbh_preinstall.sh
    user root
    group root
    oneshot//只跑一次
    disabled
    seclabel u:r:init:s0

on property:ro.sdcard.preinstall!=NULL
    start xbh_sdcard_preinstall

Action是有名字的一系列的命令。Action有一个trigger(触发器),用于决定该Action应在何时执行。当一个事件发生并匹配了一个Action的trigger,相应的Action将被添加到即将执行(to-be-executed)队列的尾部(除非该Action存在与队列上了)。

每个action在队列中顺序排列,每个action中的command将会顺序执行。init在执行command的过程中同时会执行其他活动(设备节点的创建/销毁,属性设置,进程重启)。

init.rc中常见的trigger如下:

triggerDescription
bootinit程序启动后触发的第一个事件
<name>=<value>当属性<name>满足<value>时触发
device-added/removed-<patch>当设备节点添加/删除时触发此事件
sevice-exited-<name>当指定服务<name> 存在时触发
property:ro.sdcard.preinstall!=NULL当系统熟悉==/!=时触发
on boot                     #系统启动触发
on early-init               #在初始化之前触发
on init                     #在初始化时触发(在启动配置文件/init.conf被装载之后)
on late-init                #在初始化晚期阶段触发
on charger                  #当充电时触发
on property:<key>=<value>   #当属性值满足条件时触发
on post-fs                  #挂载文件系统
on post-fs-data             #挂载data
on device-added-<path>      #在指定设备被添加时触发
on device-removed-<path>    #在指定设备被移除时触发
on service-exited-<name>    #在指定service退出时触发
on <name>=<value>           #当属性<name>等于<value>时触

#当init被触发时执行
on init


#当属性sys.boot_completed被设置为1时执行
on property:sys.boot_completed=1

3、services 指定被守护进程守护的服务

service <name> <pathname> [ <argument> ]*
     <option>
     <option>
     ...
  • <name> ——表示service 的名字;
  • <pathname> ——表示service所在路径,此处的service是可执行文件,所以一定有存储路径;
  • <argument> ——启动service所带的参数;
  • <option> ——对此service的约束选项,后面将详细讲解;

选项 Option

Option用来定义Service的行为,决定了Service将在何时启动,如何运行等。常用的Option有包括以下一些。

  • critical
    这是十分关键的服务。如果在四分钟内退出超过四次,手机将会重启并进入recovery模式。
  • disabled
    这种类型的服务不会自动启动。它必须明确的使用名字启动。
  • setenv <name> <value>
    设置环境变量<name>=<value>在加载的进程中。
  • socket <name> <type> <perm> [ <user> [ <group> [ <context> ] ] ]
    创建一个名为/dev/socket/<name>的UNIX域socket并将fd传递到加载的进程中。

<type>必须是"dgram", “stream”, "seqpacket"中的一种。
<user><group>默认为0.
<context>是 SELinux socket 安全上下文,默认为service安全级别,可以指定为seclabel或根据service的可执行文件的安全级别计算。

  • user <username>
    在执行该service前改变用户名,默认为root。如果你的进程请求Linux的特殊能力,就不要用这个命令。需以进入进程仍是root->请求特权->切换到你期望的uid来替换此法。
  • group <groupname> [ <groupname> ]\*
    在执行该service前改变组名。第一个以后的附加组名用于设定进程的附加组(通过setgroups())。当前默认是root。
  • seclabel <securitycontext>
    在执行服务之前改变安全级别。主要用于从rootfs执行服务,比如ueventd, adbd. 在system分区上可以用基于文件安全级别的策略定义的transition,如果没有指定且没有定义策略的transition,默认是init上下文。
  • oneshot
    退出不重启服务(名副其实,一次性)。
  • class <name>
    为一service指定一个类名,所有有相同类名的service可以一同启动或停止。如果没有用class选项指定类名,该service属于"default"。
  • onrestart
    在service重启的时候执行。

4、cmd

exec [ ]* fork并执行一个程序,其路径为 ,这条命令将阻塞直到该程序启动完成,因此它有可能造成init程序在某个节点不停地等待
export 设置某个环境变量的值为,这是对全局有效的,即其后所有进程都将继承这个变量
ifup 使网络接口成功连接
import 引入一个名为的文件
hostname 设置主机名
chdir 更改工作目录为
chmod 更改文件访问权限
chown 更改文件所有者和组群
chroot 更改根目录位置
class_start 如果它们不在运行状态的话,启动由类名指定的所有相关服务
class_stop 如果它们在运行状态的话,停止
domainname 设置域名
insmod 在 路径上安装一个模块
mkdir [mode][owner][group] 在 路径上新建一个目录
mount

[]* 尝试在指定路径上挂载一个设备
setkey 目前未定义
setprop 设置系统属性的值为
setrlinit 设置一种资源的使用限制。这个概念亦存在于Linux系统中,表示软限制,表示硬限制
start 启动一个服务
stop 停止一个服务
symlink 创建一个 路径的软链接,目标为
sysclk<mins_west_of_gmt> 设置基准时间,如果当前时间时GMT,这个值是0
trigger 触发一个事件
write []* 打开一个文件,并写入字符串

5、验证脚本有没有跑通

重新生成固件并刷入,查看文件是否成功被复制,从而验证 test.sh 脚本是否被执行,可以通过 
adb shell dmesg >julian.log 命令查看开机日志

11、AIDL的理解和应用(优秀按钮实例)

1)跨应用启动Service

模块一:app

package com.example.startservicefromanotherapp;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //开启一个服务startService(new Intent(this, 类的定义))
        startService(new Intent(this,AppService.class));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(new Intent(this,AppService.class));
    }
}
package com.example.startservicefromanotherapp;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class AppService extends Service {
    public AppService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("Service started");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("Service destory");
    }
}

模块二:anotherapp

package com.example.anotherapp;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;

import com.google.android.material.snackbar.Snackbar;

import androidx.appcompat.app.AppCompatActivity;

import android.view.View;

import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;

import com.example.anotherapp.databinding.ActivityMainBinding;

import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private AppBarConfiguration appBarConfiguration;
    private ActivityMainBinding binding;

    private Intent serviceIntent;


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

        serviceIntent = new Intent();
        //通过包名和类名显式指定Intent
        serviceIntent.setComponent(new ComponentName("com.example.startservicefromanotherapp","com.example.startservicefromanotherapp.AppService"));

        findViewById(R.id.btnStartAppService).setOnClickListener(this);
        findViewById(R.id.btnStopAppService).setOnClickListener(this);
    }

    @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 boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        return NavigationUI.navigateUp(navController, appBarConfiguration)
                || super.onSupportNavigateUp();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btnStartAppService:
                //Android 8.0 不再允许后台进程直接通过startService方式去启动服务,改为startForegroundService方式启动
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(serviceIntent);
                } else{
                    startService(serviceIntent);
                }
                break;
            case R.id.btnStopAppService:
                stopService(serviceIntent);
                break;
        }
    }
}

中间键

中间件3.0

1、logcat

//打开中间键logcat
setprop persist.xbh.app_log.enable true
//输出日志
logcat > /sdcard/log.txt
//查看并过滤日志,没有的话看一下中间键的.apk和板子的型号是否一样
cat /sdcard/log.txt |grep sys_rotate

2、生成的.apk记得给权限。

安卓底层开发

仓库介绍

目录说明
bionicC库
build编译系统规则基础开发板配置
cts兼容性测试
dalvikjava虚拟机
externalAndroid引入的第三方模块
frameworksAndroid核心框架
hardware硬件适配层
system底层文件系统库,应用和组健
device产品目标目录
out编程生成目标文件目录
packages/appsAndroid系统原生的app

Android编译过程

  1. 初始化参数设置
  2. 检查环境变量与目标环境
  3. 选择lunch并读取目标配置和平台信息
  4. 清空输出目录
  5. 编译
  6. 生成升级包

1、分析一个最简单的Android.mk

LOCAL_PATH := $(call my-dir)//环境变量配置local_path
include $(CLEAR_VARS)//清除一下变量
LOCAL_MODULE := test//生成目标文件
//源文件,\为连接符,即同时编译两个文件
LOCAL_SRC_FILES := test.c \
                   test1.c
LOCAL_MODULE_PATH := $(LOCAL_PATH)//目标文件生成路径
include $(BUILD_EXECUTABLE)//生成的目标格式
LOCAL_PATH := $(call my-dir)//定义了当前模块的相对路径
include $(CLEAR_VARS)//清空了除LOCAL_PATH以外的环境变量
LOCAL_MODULE := test//编译生成的目标名称
LOCAL_SRC_FILES := test.c//编译该模块需要的源文件
include $(BUILD_EXECUTABLE)//编译所生成的目标文件格式

PS:编译出现问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTkUqLI7-1680608690293)(图库/android底层开发2.png)]

可能是LOCAL_MODULE(生成目标文件)的名字和其他目标文件重名。可改成dddtest。

2、深入学习Android.mk

将工程下的所有源码文件添加到变量中

  1. 将每个文件添加到Android.mk中

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := dddtest
    LOCAL_SRC_FILES := src/main.c \
                       src/test.c
    # LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin
    include $(BUILD_EXECUTABLE)
    
  2. 使用系统提供的函数处理

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := dddtest
    #调用all-c-files-under可以拿到当前目录下所有的.c文件
    LOCAL_C_ALL_FILES := $(call all-c-files-under)
    LOCAL_SRC_FILES := $(LOCAL_C_ALL_FILES)
    # LOCAL_SRC_FILES := src/main.c \
    #                    src/test.c
    # LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin
    include $(BUILD_EXECUTABLE)
    

输出两个文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := dddtest
LOCAL_C_ALL_FILES := $(call all-c-files-under)
LOCAL_SRC_FILES := $(LOCAL_C_ALL_FILES)
# LOCAL_SRC_FILES := src/main.c \
#                    src/test.c
# LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin
include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)
LOCAL_MODULE := dddtest1
LOCAL_C_ALL_FILES := $(call all-c-files-under)
LOCAL_SRC_FILES := $(LOCAL_C_ALL_FILES)
# LOCAL_SRC_FILES := src/main.c \
#                    src/test.c
# LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin
include $(BUILD_EXECUTABLE)

3、编译动态库:将编译类型修改为BUILD_SHARED_LIBRARY

include $(BUILD_SHARED_LIBRARY)

4、编译静态库:将编译类型修改为BUILD_STATIC_LIBRARY

include $(BUILD_STATIC_LIBRARY)

项目中引用系统的库

LOCAL_SHARED_LIBRARIES += libxxxx : \

3、Android.mk文件jar包导入问题

Android.mk 文件Jar包导入问题 - 一颗苹果!! - 博客园 (cnblogs)

函数参考

//SystemProperties.getxxx("系统属性",默认值)
public final static boolean ENABLE_HOME = SystemProperties.getBoolean("persist.sys.lg.home", false);

项目问题

1、安卓input子系统实现按键上报(待补充)

2、判断某系统APP是否正在运行

public static boolean isRunning(Context context, String packageName) {
		ActivityManager am = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);
		List<RunningAppProcessInfo> list = am.getRunningAppProcesses();
		for (RunningAppProcessInfo appProcess : list) {
			String processName = appProcess.processName;
			if (processName != null && processName.equals(packageName)) {
				return true;
			}
		}
		return false;
	}

3、系统属性的名字长度不能超过31位否则就会报错

4、通过属性干预逻辑,否则不干预逻辑写法

//我的写法,直接阻碍广播发送。这样会引起其它想要收到这个广播的代码收不到。
if(SystemProperties.getBoolean("persist.sys.mute_hide_icon",false)){
    sendBroadcastToAll(intent);
}
//露飞写法,只用属性干预一种情况。另一种维持原本逻辑。
//isShowIcon是传进来的参数,因为有其它
if(SystemProperties.getBoolean("persist.sys.mute_hide_icon",false)){
intent.putExtra(AudioManager.EXTRA_MUTEICON_ISSHOW, false);
} else {
intent.putExtra(AudioManager.EXTRA_MUTEICON_ISSHOW, isShowIcon);
}
sendBroadcastToAll(intent);
//候工写法
final String total = Formatter.formatFileSize(mContext, emmcsize > 0 ? emmcsize * 1000 * 1000 * 1000 : totalBytes);

5、反复打开网页,检查是否能成功打开

原理:通过调用webView方法打开网页,通过命令ps -A | grep android.webview查询当前进程是否有同目标的包名来查看网页是否打开成功。

注意如果在app没有关闭前reboot视为非正常重启/关机。这之前如果有文字操作将会无效(例如写入数据库)。

解决方法:如果要进行非正常关机,关机前要sync一下。

public class MainActivity extends Activity {
    private Thread thread;
    private WebView webView;
    Context context = this;
    int count;
    int flag = 0;
    public static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        UserAPI.getInstance().init(getApplicationContext());
        count = Settings.Global.getInt(context.getContentResolver(),
                "rebootCount", 0);
        Settings.Global.putInt(context.getContentResolver(), "rebootCount", count);

        webView = (WebView) findViewById(R.id.web_view);
        webView.loadUrl("http://www.baidu");
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                webView.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            checkWebview(context);
                        } catch (IOException | InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }).start();
    }

    private void checkWebview(Context context) throws IOException, InterruptedException {
        boolean mWebviewIsStart = true;
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        SystemHelper mSystemHelper = new SystemHelper();
        if (mSystemHelper != null) {
            String result = mSystemHelper.executeCommand("ps -A | grep android.webview");
            if (!TextUtils.isEmpty(result)) {
                Log.d("MainActivity", "exe command = " + result);
                count = Settings.Global.getInt(context.getContentResolver(),
                        "rebootCount", 0);
                Log.i("MainActivity", "当前重启次数为:" + count);

//                count++;
                Settings.Global.putInt(context.getContentResolver(), "rebootCount", ++count);
                Log.i("MainActivity", "数据库中的值为:" + Settings.Global.getInt(context.getContentResolver(),
                        "rebootCount", 0));

                //不同步固件写数据库写了白写
                mSystemHelper.executeCommand("sync");

                Log.d("MainActivity", "exe command =  reboot");
                mSystemHelper.executeCommand("reboot");
                flag = 1;
            } else {
                Log.d("MainActivity", "exe command = " + result);
                return;
            }
        }
    }
}

6、install安装失败

D:\tools>adb install D:\androidProject\Demo1\app\release\app-release.apk
Performing Streamed Install
adb: failed to install D:\androidProject\Demo1\app\release\app-release.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.example.myapplication signatures do not match previously installed version; ignoring!]

解决办法:1、在AS里打包run->Generate Signed Bundle or APK->…->release

2、删掉launcher3里的同名文件后再install

3、再不行就再签个名

7、预置文件名字有中文出现乱码

1)可以先push到板子中看看会不会乱码,如果乱码则将文件放到U盘中再试试看会不会乱码

2)如果不会则打包一份放到预置目录下即可

tar cvf kkk.tar 汉字描红.zip 科海融生-2018.mp4

tar xvf kkk.tar -C /sdcard/

8、8.0以后系统广播收不到

frameworks\base\services\core\java\com\android\server\am\BroadcastQueue.java:1264

  //加上这个标志位才能跳过检查
  if(r.intent.getAction().equals("android.app.action.DEVICE_ADMIN_ENABLED")){
            r.intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
        }
        //下面是拒收条件
        if (!skip) {
            final int allowed = mService.getAppStartModeLocked(
                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                // We won't allow this receiver to be launched if the app has been
                // completely disabled from launches, or it was not explicitly sent
                // to it and the app is in a state that should not receive it
                // (depending on how getAppStartModeLocked has determined that).
                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                    Slog.w(TAG, "Background execution disabled: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                        || (r.intent.getComponent() == null
                            && r.intent.getPackage() == null
                            && ((r.intent.getFlags()
                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                            && !isSignaturePerm(r.requiredPermissions))) {
                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                            component.getPackageName());
                    Slog.w(TAG, "Background execution not allowed: receiving "
                            + r.intent + " to "
                            + component.flattenToShortString());
                    skip = true;
                }
            }
        }

9、反射调用安卓接口

                               BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
                                try {
                                    Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
                                    //反射:根据函数名字与变量获得接口
                                    Method setScanMode = BluetoothAdapter.class.getMethod("setScanMode", int.class, long.class);
            
                                    setDiscoverableTimeout.invoke(adapter, 0);
                                    setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 0);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }

10、无操作一段时间后进行其它操作(基于handle)

package com.android.server;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.provider.Settings;

import android.os.SystemClock;
import android.os.SystemProperties;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.policy.SystemGesturesPointerEventListener;

import android.app.xbh.XbhServiceManager;

public class XbhIdleState {
    private static final boolean DEBUG = true;
    private static final String TAG = "XbhIdleState";
    public Context mContext;
    private static final int MSG_XBH_EVENT_OCCUR = 60; // 有人为操作就会重新计时
    private final static int idleTime = 10000;
    private XbhIdleStateHandler mHandler;
    private static XbhIdleState sInstance = null;
    private static final int FUNC_CUSTOMER_PICTURE_START = 0x4000;
    public static final int FUNC_ID_B_PICTURE_SETBACKLIGHTVALUE = FUNC_CUSTOMER_PICTURE_START + 5;

    private XbhIdleState(Context context) {
        mContext = context;
        ensureHandler();
        mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, idleTime);
    }

    public static XbhIdleState getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new XbhIdleState(context);
        }
        return sInstance;
    }

    private synchronized void ensureHandler() {
        Log.d(TAG, "ensureHandler start!");
        if (mHandler == null) {
            Log.d(TAG, "ensureHandler start2!");
            HandlerThread thread = new HandlerThread(TAG);
            thread.start();
            mHandler = new XbhIdleStateHandler(thread.getLooper());
        }
    }

    private class XbhIdleStateHandler extends Handler {
        public XbhIdleStateHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "handleMessage start!");
            switch(msg.what) {
                case MSG_XBH_EVENT_OCCUR:
                    // if(SystemProperties.getBoolean("persist.sys.idleState", false)){
                        enableIdleState();
                    // }
                    break;
            }
        }
    }

    public void start() {
        ensureHandler();
        mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, 0);
    }

    private long getLastEventTime() {
        return Math.max(SystemGesturesPointerEventListener.getLastMotionEventTime(),
                PhoneWindowManager.getLastKeyEventTime());
    }

    private void enableIdleState() {
        try {
            final long timeNow = SystemClock.uptimeMillis();
            final long timeSinceLastEvent = timeNow - getLastEventTime();   // 距上次操作事件时间

            Log.d(TAG, "timeSinceLastEvent is " + timeSinceLastEvent);
            if (timeSinceLastEvent > idleTime) {
                // ((XbhServiceManager)mContext.getSystemService("xbh")).getFuncAPI().executeFunc_B(FUNC_ID_B_PICTURE_SETBACKLIGHTVALUE, 0, 0, 0, (String)null);
                Settings.System.putInt(mContext.getContentResolver(), "screen_brightness", 0);
            } else {
                // ((XbhServiceManager)mContext.getSystemService("xbh")).getFuncAPI().executeFunc_B(FUNC_ID_B_PICTURE_SETBACKLIGHTVALUE, 60, 0, 0, (String)null);
                Settings.System.putInt(mContext.getContentResolver(), "screen_brightness", 60);
                if (DEBUG) {
                    Log.d(TAG, "Someone consumes the device");
                }
            }

            mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, idleTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

11、基于安卓原生去实现自启动

                Intent intent1 = new Intent();
                intent1.setAction("com.android.lango.installapp");
                intent1.setAction("com.xbh.action.INSTALL_APP_SILENT");
                intent1.putExtra("apppath", patchString);
                sendBroadcast(intent1);
                

                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setAction(Intent.ACTION_VIEW);
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                Uri uri = null;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    //第二个参数是manifest中定义的`authorities`
                    uri = FileProvider.getUriForFile(mContext, "com.example.myapplication.provider", new File(patchString));
                } else {
                    uri = Uri.fromFile(new File(patchString));
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                intent.setDataAndType(uri, "application/vnd.android.package-archive");
                startActivity(intent);
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
    <provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.myapplication.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data  android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_path"/>
    </provider>
file_path对应上面的路径
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android/apk/res/android">
    <external-path path="." name="external_path"/>
    <cache-path path="." name="cache_path" />
    <files-path path="." name="files_path" />
    <external-files-path path="." name="external_files_path" />
    <external-cache-path path="." name="external_cache_path" />
</paths>

内部的element可以是files-path,cache-path,external-path,external-files-path,external-cache-path,分别对应Context.getFilesDir(),Context.getCacheDir(),Environment.getExternalStorageDirectory(),Context.getExternalFilesDir(),Context.getExternalCacheDir()等几个方法

12、7.0以后广播收不到

  • 1.发送方AndroidManifest.xml需要注册当前要发送的广播:
    <protected-broadcast - android:name=“com.android.xbh.LMEMORY” />
  • 2.接收方AndroidManifest.xml也需要同步进行广播注册:
  • 3.如果接收或发送方是framework层,则在以下文件中注册:
    frameworks/base/core/res/AndroidManifest.xml

13、系统数据库Setting

1.数据库项名称

frameworks/base/core/java/android/provider/Settings.java

2.SettingProvider

目录:
frameworks\base\packages\SettingsProvider
初始化DataBase:
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
初始化DB可能会加载的默认配置:
frameworks/base/packages/SettingsProvider/res/values/defaults.xml

3.数据库存放位置

/data/data/com.android.providers.settings/databases/settings.db

Android 7.1的Setting数据不再以db存储,而是在
/data/system/users/0/settings_xxxx.xml

14、6.0预置App权限

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
 String[] appAllowList = { "com.enht.jzc.face", "com.example.jzc.test123", "com.example.jzc.face"};
        if(Arrays.asList(appAllowList).contains(pkg.packageName)
           || pkg.packageName!=null &amp;&amp; pkg.packageName.startsWith("com.enht.")){
            final int permsSize = pkg.requestedPermissions.size();
            for (int i = 0; i < permsSize; i++){
                final String name = pkg.requestedPermissions.get(i);
                final BasePermission bp = mSettings.mPermissions.get(name);
                if(null != bp
                    ; permissionsState.grantInstallPermission(bp) != PermissionsState.PERMISSION_OPERATION_FAILURE) {
                    Slog.d(TAG, "---grant permission " + name + " to package " + pkg.packageName);
                    changedInstallPermission = true;
                }
            }
        }
        //pgz preset apk permission---
        
         if ((changedInstallPermission || replace) &amp;&amp; !ps.installPermissionsFixed
                 !isSystemApp(ps) || isUpdatedSystemApp(ps)){
             // This is the first that we have heard about this package, so the
--
Gitblit v1.8.0

15、关于build.prop系统属性

一、属性分类和特点

1、 以"sys."开头的属性为临时属性,可读可修改,但关机后不保存。
如sys.usb.state

2、以"persist."开头的属性为永久属性,可读可修改,关机保存。
非全擦升级时不会恢复默认值。

3、以"ro."开头的属性为永久属性,只读。
无论如何升级,均会恢复默认值。

4、属性使用时有boolean, int 和 String三种数据类型。String是最基本的类型,其他两种类型可以用String来读取或写入。

二、使用建议

1、需要跟随UI状态变化,或可在app中动态修改的需求时,使用可读写的永久属性以"persist."开头的属性。如要恢复默认值,则恢复出厂设置。

2、如不需要动态修改,而是随固件设定的需求,采用"ro."开头的永久只读属性,方便固件升级时不需要恢复出厂来还原默认值。

3、只是临时需要记录的属性,开机后又动态更新的需求,采用以"sys."开头的临时属性,免以上次保存的值影响下次开机的状态。

4、有时需要临时修改/system/build.prop文件,一般是把文件拷贝下来,如用adb pull拷贝出来,修改后,再push回去。这时要注意/system/build.prop文件的权限是644,权限变化会导致开机进不了系统。

三、相关接口

1、java接口
在系统SDK编译时可以import以下包,如果是外部编译的apk要用反射的方法:
import android.os.SystemProperties;

String str1 = SystemProperties.get(“ro.goto.sleep.wake.simulate”, “false”);
int number = SystemProperties.getInt(“ro.goto.sleep.wake.simulate”, 16);
boolean bod = SystemProperties.getBoolean(“ro.goto.sleep.wake.simulate”, true);

注意:
get后面的两个参数都必须是string类型。
getInt第一参数为string,第二参数为int
getBoolean 第一参数是string,第二参数是boolean

SystemProperties.set则要求两个参数都是string类型。

2、C/C++接口
只有char字符串类型的接口,传参和返回值都是char字符串:
写属性:
property_set(“sys.lgo.logoExit”, “true”);

读属性:
char value[PROPERTY_VALUE_MAX];
property_get(“sys.lgo.count”, value, “0”);

16、截屏

1、通过位图方式截图

// 截屏
    public boolean getScreenshotResult(Activity mActivity,String SavePath, String FileName){
        //1.构建Bitmap
        final int[] res = HitvManager.getInstance().getFactory().getPanelResolution();
        boolean result = false;
        Bitmap Bmp = Bitmap.createBitmap( res[0], res[1], Bitmap.Config.ARGB_8888 );

        //2.获取屏幕
        View decorview = mActivity.getWindow().getDecorView();
        decorview.setDrawingCacheEnabled(true);
        Bmp = decorview.getDrawingCache();

        //3.保存Bitmap
        try {
            File path = new File(SavePath);
            //文件
            String filepath = SavePath + FileName;

            File file = new File(filepath);
            if(!path.exists()){
                path.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }

            FileOutputStream fos = null;
            fos = new FileOutputStream(file);
            if (null != fos) {
                result = Bmp.compress(Bitmap.CompressFormat.JPEG, 90, fos);
                fos.flush();
                fos.close();

                return result;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 获取SDCard的目录路径功能
     * @return
     */
    private String getSDCardPath(){
        File sdcardDir = null;
//判断SDCard是否存在
        boolean sdcardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
        if(sdcardExist){
            sdcardDir = Environment.getExternalStorageDirectory();
        }
        return sdcardDir.toString();
    }

2、通过执行shell命令来实现截图

screencap -p +图片生成路径
ps:不加p文件可能会损坏而且会很占内存
海思接口:
 result = HitvManager.getInstance().getCusEx().cus_set_extra_shellexcute("screencap -p /system/usr/112.jpeg");
通用接口:
Runtime.getRuntime().exec("screencap -p "+ Environment.getExternalStorageDirectory().getPath()+ "/shot.png");

17、java中的Environment类

本文目的完全是参考交流学习,一起学习java编程,以下是上述链接的全文内容,若有转载请标注原文的出处,即上面的链接。

Environment 是一个提供访问环境变量的类。

Environment 包含常量:

 MEDIA_BAD_REMOVAL
解释:返回getExternalStorageState() ,表明SDCard 被卸载前己被移除
MEDIA_CHECKING
解释:返回getExternalStorageState() ,表明对象正在磁盘检查。
MEDIA_MOUNTED
解释:返回getExternalStorageState() ,表明对象是否存在并具有读/写权限
MEDIA_MOUNTED_READ_ONLY
解释:返回getExternalStorageState() ,表明对象权限为只读
MEDIA_NOFS
解释:返回getExternalStorageState() ,表明对象为空白或正在使用不受支持的文件系统。
MEDIA_REMOVED
解释:返回getExternalStorageState() ,如果不存在 SDCard 返回
MEDIA_SHARED
解释:返回getExternalStorageState() ,如果 SDCard 未安装 ,并通过 USB 大容量存储共享 返回
MEDIA_UNMOUNTABLE
解释:返回getExternalStorageState() ,返回 SDCard 不可被安装 如果 SDCard 是存在但不可以被安装
MEDIA_UNMOUNTED
解释:返回getExternalStorageState() ,返回 SDCard 已卸掉如果 SDCard  是存在但是没有被安装
Environment 常用方法:

方法:getDataDirectory() 
解释:返回 File ,获取 Android 数据目录。
方法:getDownloadCacheDirectory() 
解释:返回 File ,获取 Android 下载/缓存内容目录。
方法:getExternalStorageDirectory() 
解释:返回 File ,获取外部存储目录即 SDCard
方法:getExternalStoragePublicDirectory(String type) 
解释:返回 File ,取一个高端的公用的外部存储器目录来摆放某些类型的文件
方法:getExternalStorageState() 
解释:返回 File ,获取外部存储设备的当前状态  
方法:getRootDirectory() 
解释:返回 File ,获取 Android 的根目录
 

StatFs 一个模拟linux的df命令的一个类,获得SD卡和手机内存的使用情况
StatFs 常用方法:

getAvailableBlocks() 
解释:返回 Int ,获取当前可用的存储空间
getBlockCount() 
解释:返回 Int ,获取该区域可用的文件系统数
getBlockSize() 
解释:返回 Int ,大小,以字节为单位,一个文件系统
getFreeBlocks() 
解释:返回 Int ,该块区域剩余的空间
restat(String path) 
解释:执行一个由该对象所引用的文件系统

17、获取顶层应用的方法

方法一:通过获取最上层Activity的包名进行对比:

private String getTopActivityPkgName() {
    String topPkgName = "";
    //get top activity package name
    ActivityManager am = (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
    ComponentName comp = am.getRunningTasks(1).get(0).topActivity;
    topPkgName = comp.getPackageName();
    return topPkgName;
}
    private static final String CLOUD_COMPUTER_PKG_NAME = "com.ctg.itrdc.clouddesk";//xbh add

        Log.i(TAG, "************start2 " + getTopActivityPkgName());
        if(CLOUD_COMPUTER_PKG_NAME.equals(getTopActivityPkgName())){
        // if(isRunning(mContext, CLOUD_COMPUTER_PKG_NAME)){
            Log.i(TAG, "************start3");
            if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_META_LEFT){
                Log.i(TAG, "************start4");
                // return 0;
            }
        }

通过对比topPkgName与 我们感兴趣的应用 是否一致,来判断 我们感兴趣的应用 是否运行在最上层。

注意:通过am.getRunningTasks(1).get(0).topActivity获取到的Activity会经常出现获取到的Activity为桌面(原因:从一个应用切换到另一个应用,有几率判断错误,判断为显示为桌面(launcher))

**方法二:**通过getRunningAppProcesses来进行判断

private String getTopActivityPkgName() {
    String topPkgName = "";
    //get top activity package name
    ActivityManager am = (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> pis = am.getRunningAppProcesses();
    ActivityManager.RunningAppProcessInfo topAppProcess = pis.get(0);
    if (topAppProcess != null && topAppProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
        topPkgName = topAppProcess.processName;
    }
    return topPkgName;
}

通过对比topPkgName与targetPkgName是否一致,来判断targetPkgName是否运行在最上层。

注意:通过这种方式获取到的topPkgName会出现为空的情况,导致判断异常。

**方法三:**通过getFocusedStackInfo方式来获取

public String getTopActivityPkgName() {
    String topPackageName = "";
    try {
        final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
        if (focusedStack != null) {
            if (focusedStack.stackId != 0
                    && focusedStack.topActivity != null
                    && focusedStack.topActivity.getClassName() != null) {
                topPackageName =  focusedStack.topActivity.getPackageName();
            }
        }
    } catch (RemoteException e) {
        e.rethrowFromSystemServer();
    }
    return topPackageName;
}

通过对比topPkgName与targetPkgName是否一致,来判断targetPkgName是否运行在最上层。

注意:通过这种方式获取topPkgName的时候会出现没有权限异常的情况,从而导致失败:
ActivityManager: Permission Denial: getStackInfo() from pid=1053, uid=1047 requires android.permission.MANAGE_ACTIVITY_STACKS。

**方法四:**直接通过uid getUidProcessState来进行判断是否在最上层运行(当前运行应用)

private boolean isAppTop(int callingUid) {
    final ActivityManagerInternal ami =
            LocalServices.getService(ActivityManagerInternal.class);
    if (ami == null) {
        return false;
    }
 
    final int procState = ami.getUidProcessState(callingUid);
    final boolean isUidActive = ami.isUidActive(callingUid);
    return (isUidActive && procState <= ActivityManager.PROCESS_STATE_TOP);
}

18、控制Audio输出通道切换

Audio 输出通道有很多,Speaker、headset、bluetooth A2DP等。通话或播放音乐等使用Audio输出过程中,可能发生Audio输出通道的切换。比如,插入有线耳机播放音乐时,声音是从耳机发出的;而此时拔出耳机,Audio输出通道会发生切换。如果音乐播放器不做处理,Audio输出是被切换到扬声器的,声音直接从Speaker发出。

Android中可以通过android.media.AudioManager查询当前Audio输出的情况,并且在Audio输出发生变化时,捕获并处理这种变化。

1)Audio输出状态查询与控制

android.media.AudioManager提供的下列方法可以用来查询当前Audio输出的状态:

  • isBluetoothA2dpOn():检查A2DPAudio是否通过蓝牙耳机;
  • isSpeakerphoneOn():检查扬声器是否打开;
  • isWiredHeadsetOn():检查线控耳机是否连着;注意这个方法只是用来判断耳机是否是插入状态,并不能用它的结果来判定当前的Audio是通过耳机输出的,这还依赖于其他条件。

另外还有一些设置这些Audio输出的setXYZ()方法,这些方法在一般使用Audio输出的应用程序不要直接调用,他们由系统来管理,实现Audio输出通道的自动切换。除非,界面提供给用户切换的菜单或按钮,而用户选择了却换,比如要直接选择扬声器发声,可直接调用setSpeakerphoneOn()。

2)Audio输出通道切换的事件的捕获与处理

因为耳机插拔、蓝牙耳机的断开,Audio输出通路会自动切换。此时正在播放Audio的程序要获得通知,知道这一事件的发生。Android中是通过广播ACTION_AUDIO_BECOMING_NOISY这个Intent通知的。

处理广播的较好的方式,是动态注册/注销自己所关心的广播。下面代码演示了,开始播放时注册广播的Receiver;停止播放时注销广播的Receiver。对Audio输出通道切换的处理是暂停当前的播放,不直接从新的通道里发出声来。

private class NoisyAudioStreamReceiver extends BroadcastReceiver {  
    @Override  
    public void onReceive(Context context, Intent intent) {  
        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {  
            // Pause the playback   
        }  
    }  
}  
  
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);  
  
private void startPlayback() {  
    registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);  
}  
  
private void stopPlayback() {  
    unregisterReceiver(myNoisyAudioStreamReceiver);  
}  
private class NoisyAudioStreamReceiver extends BroadcastReceiver {  
    @Override  
    public void onReceive(Context context, Intent intent) {  
        if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {  
            // Pause the playback  
        }  
    }  
}  
  
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);  
  
private void startPlayback() {  
    registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);  
}  
  
private void stopPlayback() {  
    unregisterReceiver(myNoisyAudioStreamReceiver);  
}

3)Audio输出通道切换的典型场景—— 用耳机听音乐时,拔出耳机

  • AudioNoisy Client注册了侦听广播AudioManager.ACTION_AUDIO_BECOMING_NOISY;
  • 用耳机一直在听音乐;
  • HeadsetObserver一直在监视耳机状态的变化。检测到耳机被拔出之后,发出广播AudioManager.ACTION_AUDIO_BECOMING_NOISY;
  • AudioNoisy Client收到了广播,发送暂停命令给MediaPaybackService去暂停当前的播放。

19、Android 耳机插入过程分析 (AudioManager部分)

初始化:
10-29 12:50:39.542  1400  1400 I SystemServer: StartAudioService
10-29 12:50:39.542  1400  1400 I SystemServiceManager: Starting com.android.server.audio.AudioService$Lifecycle
10-29 12:50:39.587  1400  1400 D AudioService: Master mono false
10-29 12:50:39.589  1400  1400 D AudioService: Master mute false, user=0
10-29 12:50:39.592  1400  1400 D AudioService: Mic mute false, user=0
10-29 12:50:39.684  1400  1400 D SystemServerTiming: StartAudioService took to complete: 142ms
10-29 12:50:40.614  1400  1706 D AudioService: Touch exploration enabled=false stream override delay is now 0 ms
10-29 12:50:40.614  1400  1706 D AudioService: Accessibility volume enabled = false
10-29 12:50:41.702  1400  1946 D AudioService: Volume policy changed: VolumePolicy[volumeDownToEnterSilent=true,volumeUpToExitSilent=true,doNotDisturbWhenSilent=true,vibrateToSilentDebounce=400]
10-29 12:50:41.802  1400  1621 D AudioService: Volume controller: VolumeController(android.os.BinderProxy@9c596d8,mVisible=false)
10-29 12:50:42.217  1400  1400 D AudioService: Master mono false
10-29 12:50:42.225  1400  1400 D AudioService: Master mute false, user=0
10-29 12:50:42.228  1400  1400 D AudioService: Mic mute false, user=0
 
插入:
11-07 11:39:55.551  2349  2349 I AudioService: setWiredDeviceConnectionState(1 nm:  addr:)
11-07 11:39:55.552  2349  2349 I AudioService: setWiredDeviceConnectionState(1 nm:  addr:)
11-07 11:39:55.552  2349  3588 I AudioService: onSetWiredDeviceConnectionState(dev:4 state:1 address: deviceName: caller: android);
11-07 11:39:55.552  2349  3588 I AudioService: handleDeviceConnection(true dev:4 address: name:)
11-07 11:39:55.552  2349  3588 I AudioService: deviceKey:0x4:
11-07 11:39:55.552  2349  3588 I AudioService: deviceSpec:null is(already)Connected:false
11-07 11:39:55.593  2349  3588 I AudioService: sendDeviceConnectionIntent(dev:0x4 state:0x1 address: name:);
11-07 11:39:55.594  2349  3588 I AudioService: updateAudioRoutes MAIN_HEADSET
11-07 11:39:55.594  2349  3588 I AudioService: updateAudioRoutes connType != 0, state: 1
11-07 11:39:55.594  2349  3588 I AudioService: updateAudioRoutes connType MSG_REPORT_NEW_ROUTES
11-07 11:39:55.594  2349  3588 I AudioService: onSetWiredDeviceConnectionState(dev:80000010 state:1 address: deviceName: caller: android);
11-07 11:39:55.594  2349  3588 I AudioService: handleDeviceConnection(true dev:80000010 address: name:)
11-07 11:39:55.594  2349  3588 I AudioService: deviceKey:0x80000010:
11-07 11:39:55.594  2349  3588 I AudioService: deviceSpec:null is(already)Connected:false
11-07 11:39:55.610  2349  3588 I AudioService: sendDeviceConnectionIntent(dev:0x80000010 state:0x1 address: name:);
11-07 11:39:55.610  2349  3588 I AudioService: updateAudioRoutes Liutao
11-07 11:39:55.611  2349  3588 I AudioService: onAccessoryPlugMediaUnmute newDevice=4 [headset]
11-07 11:39:55.611  2349  3588 I AudioService: mAudioHandler 5060, N: 3
11-07 11:39:55.618  2349  3588 I AudioService: onAccessoryPlugMediaUnmute newDevice=-2147483632 [-2147483632]
 
拔出;
11-07 11:41:08.165  2349  2349 I AudioService: setWiredDeviceConnectionState(0 nm:  addr:)
11-07 11:41:08.168  2349  2349 I AudioService: setWiredDeviceConnectionState(0 nm:  addr:)
11-07 11:41:08.870  2349  3588 I AudioService: onSetWiredDeviceConnectionState(dev:4 state:0 address: deviceName: caller: android);
11-07 11:41:08.876  2349  3588 I AudioService: handleDeviceConnection(false dev:4 address: name:)
11-07 11:41:08.876  2349  3588 I AudioService: deviceKey:0x4:
11-07 11:41:08.877  2349  3588 I AudioService: deviceSpec:[type:0x4 name: address:] is(already)Connected:true
11-07 11:41:08.885  2349  3588 I AudioService: sendDeviceConnectionIntent(dev:0x4 state:0x0 address: name:);
11-07 11:41:08.887  2349  3588 I AudioService: updateAudioRoutes MAIN_HEADSET
11-07 11:41:08.887  2349  3588 I AudioService: updateAudioRoutes connType != 0, state: 0
11-07 11:41:08.887  2349  3588 I AudioService: updateAudioRoutes connType MSG_REPORT_NEW_ROUTES
11-07 11:41:08.887  2349  3588 I AudioService: mAudioHandler 5060, N: 3
11-07 11:41:08.914  2349  3588 I AudioService: onSetWiredDeviceConnectionState(dev:80000010 state:0 address: deviceName: caller: android);
11-07 11:41:08.914  2349  3588 I AudioService: handleDeviceConnection(false dev:80000010 address: name:)
11-07 11:41:08.914  2349  3588 I AudioService: deviceKey:0x80000010:
11-07 11:41:08.914  2349  3588 I AudioService: deviceSpec:[type:0x80000010 name: address:] is(already)Connected:true
11-07 11:41:08.918  2349  3588 I AudioService: sendDeviceConnectionIntent(dev:0x80000010 state:0x0 address: name:);
 
 
frameworks\base\media\java\android\media\AudioManager.java
 public void setWiredDeviceConnectionState(int type, int state, String address, String name) {
        final IAudioService service = getService();
        try {
            service.setWiredDeviceConnectionState(type, state, address, name,
                    mApplicationContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    
    
frameworks\base\services\core\java\com\android\server\audio\AudioService.java
        public void setWiredDeviceConnectionState(int type, int state, String address, String name,
            String caller) {
        synchronized (mConnectedDevices) {
            if (DEBUG_DEVICES) {
                Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
                        + address + ")");
            }
            int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE);
            queueMsgUnderWakeLock(mAudioHandler,
                    MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
                    0 /* arg1 unused */,
                    0 /* arg2 unused */,
                    new WiredDeviceConnectionState(type, state, address, name, caller),
                    delay);
        }
    }
    
frameworks\base\services\core\java\com\android\server\audio\AudioService.java
       private void queueMsgUnderWakeLock(Handler handler, int msg,
            int arg1, int arg2, Object obj, int delay) {
        final long ident = Binder.clearCallingIdentity();
        // Always acquire the wake lock as AudioService because it is released by the
        // message handler.
        mAudioEventWakeLock.acquire();
        Binder.restoreCallingIdentity(ident);
        sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay);
    }
    
    
        private static void sendMsg(Handler handler, int msg,
            int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
 
        if (existingMsgPolicy == SENDMSG_REPLACE) {
            handler.removeMessages(msg);
        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
            return;
        }
        synchronized (mLastDeviceConnectMsgTime) {
            long time = SystemClock.uptimeMillis() + delay;
            handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
            if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
                    msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
                    msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) {
                mLastDeviceConnectMsgTime = time;
            }
        }
    }
    
    
       private class AudioHandler extends Handler {
       
                      case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
                    {   WiredDeviceConnectionState connectState =
                            (WiredDeviceConnectionState)msg.obj;
                        mWiredDevLogger.log(new WiredDevConnectEvent(connectState));
                        onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
                                connectState.mAddress, connectState.mName, connectState.mCaller);
                        mAudioEventWakeLock.release();
                    }
                    break;
    
    
    private void onSetWiredDeviceConnectionState(int device, int state, String address,
            String deviceName, String caller) {
        if (DEBUG_DEVICES) {
        
        //打印结果AudioService: onSetWiredDeviceConnectionState(dev:8 state:1 address: deviceName: caller: android);
            Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
                    + " state:" + Integer.toHexString(state)
                    + " address:" + address
                    + " deviceName:" + deviceName
                    + " caller: " + caller + ");");
        }
 
        synchronized (mConnectedDevices) {
            if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
                setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
            }
            // 打印AudioService: handleDeviceConnection(true dev:8 address: name:)
            if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
                // change of connection state failed, bailout
                return;
            }
            if (state != 0) {
                if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
                    setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
                }
                if ((device & mSafeMediaVolumeDevices) != 0) {
                    sendMsg(mAudioHandler,
                            MSG_CHECK_MUSIC_ACTIVE,
                            SENDMSG_REPLACE,
                            0,
                            0,
                            caller,
                            MUSIC_ACTIVE_POLL_PERIOD_MS);
                }
                // Television devices without CEC service apply software volume on HDMI output
                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
                    checkAllFixedVolumeDevices();
                    if (mHdmiManager != null) {
                        synchronized (mHdmiManager) {
                            if (mHdmiPlaybackClient != null) {
                                mHdmiCecSink = false;
                                mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
                            }
                        }
                    }
                }
            } else {
                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
                    if (mHdmiManager != null) {
                        synchronized (mHdmiManager) {
                            mHdmiCecSink = false;
                        }
                    }
                }
            }
            // 发送耳机插入的广播
            sendDeviceConnectionIntent(device, state, address, deviceName);
            // 更新音频通路
            updateAudioRoutes(device, state);
        }
    }
    
 //如果要修改蓝牙耳机与有线耳机的优先级可以修改AudioDeviceInventory::onSetWiredDeviceConnectionState函数
frameworks\base\services\core\java\com\android\server\audio\AudioDeviceInventory.java
    private void sendDeviceConnectionIntent(int device, int state, String address,
            String deviceName) {
        if (DEBUG_DEVICES) {
            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
                    " state:0x" + Integer.toHexString(state) + " address:" + address +
                    " name:" + deviceName + ");");
        }
        Intent intent = new Intent();
 
        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
            intent.setAction(Intent.ACTION_HEADSET_PLUG);
            intent.putExtra("microphone", 1);
        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
                   device == AudioSystem.DEVICE_OUT_LINE) {
            intent.setAction(Intent.ACTION_HEADSET_PLUG);
            intent.putExtra("microphone",  0);
        } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
            intent.setAction(Intent.ACTION_HEADSET_PLUG);
            intent.putExtra("microphone",
                    AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
                        == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
        } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
            if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
                    == AudioSystem.DEVICE_STATE_AVAILABLE) {
                intent.setAction(Intent.ACTION_HEADSET_PLUG);
                intent.putExtra("microphone", 1);
            } else {
                // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
                return;
            }
        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
            configureHdmiPlugIntent(intent, state);
        }
 
        if (intent.getAction() == null) {
            return;
        }
 
        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
 
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
        final long ident = Binder.clearCallingIdentity();
        // 发送广播,上层可以通过接受广播来判断耳机的状态。
        try {
            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
    
    
    
    private void updateAudioRoutes(int device, int state)
    {
        int connType = 0;
        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
         //Slog.i(TAG, "updateAudioRoutes MAIN_HEADSET");进入这里
            connType = AudioRoutesInfo.MAIN_HEADSET;
        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
                   device == AudioSystem.DEVICE_OUT_LINE) {
            connType = AudioRoutesInfo.MAIN_HEADPHONES;
        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
            connType = AudioRoutesInfo.MAIN_HDMI;
        } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
                device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
            connType = AudioRoutesInfo.MAIN_USB;
        }
 
        synchronized (mCurAudioRoutes) {
            if (connType != 0) {
                int newConn = mCurAudioRoutes.mainType;
                if (state != 0) {
                    newConn |= connType;
                } else {
                    newConn &= ~connType;
                }
                if (newConn != mCurAudioRoutes.mainType) {
                    mCurAudioRoutes.mainType = newConn;
                    // 发送消息给Handler
                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
                            SENDMSG_NOOP, 0, 0, null, 0);
                }
            }
        }
    }
    
    
    private class AudioHandler extends Handler {
       
    public void handleMessage(Message msg) {
     case MSG_REPORT_NEW_ROUTES: {
                    int N = mRoutesObservers.beginBroadcast();
                    if (N > 0) {
                        AudioRoutesInfo routes;
                        synchronized (mCurAudioRoutes) {
                            routes = new AudioRoutesInfo(mCurAudioRoutes);
                        }
                        while (N > 0) {
                            N--;
                            IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
                            try {
                                obs.dispatchAudioRoutesChanged(routes);
                            } catch (RemoteException e) {
                            }
                        }
                    }
                    mRoutesObservers.finishBroadcast();
                    observeDevicesForStreams(-1);
                    break;
                }               
 //如果要修改蓝牙耳机与有线耳机的优先级可以修改mediaRouter::updateAudioRoutes函数
frameworks\base\media\java\android\media\MediaRouter.java
        final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
            @Override
            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
                mHandler.post(new Runnable() {
                    @Override public void run() {
                        updateAudioRoutes(newRoutes);
                    }
                });
            }
        };

20、获得当前最顶层app的包名

    private String getTopActivityPkgName() {
        String topPkgName = "";
        //get top activity package name
        ActivityManager am = (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
        ComponentName comp = am.getRunningTasks(1).get(0).topActivity;
        topPkgName = comp.getPackageName();
        return topPkgName;
    }

21、android按键拦截按键事件(framework层)

相关链接:

(8条消息) Android事件拦截_chongyuzhao的博客-CSDN博客_android拦截事件

(8条消息) Android10 InputManagerService事件输入输出_tangedegushi的博客-CSDN博客_android sendkeydownupsync

事件在分发给应用前,会分别调用interceptKeyBeforeQueueing()和interceptKeyBeforeDispatching(),这也就是说PhoneWindowManager是最先处理输入事件的地方。

22、Android利用系统广播—静默安装监听应用程序安装和卸载

1、确认安装包是否存在,并可读写
2、隐示启动:action和data的schema来控制弹出安装工具类APP,然后点击安装…
3、升级完:BootReceiver 监听到Intent.ACTION_PACKAGE_REPLACED,然后自启动

在android系统中,安装和卸载都会发送广播,当应用安装完成后系统会发android.intent.action.PACKAGE_ADDED广播。可以通过intent.getDataString()获得所安装的包名。当卸载程序时系统发android.intent.action.PACKAGE_REMOVED广播。同样intent.getDataString()获得所卸载的包名。

新建监听类:BootReceiver继承BroadcastReceiver

public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
    //接收广播:系统启动完成后运行程序
    if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
        Intent newIntent = new Intent(context, WatchInstall.class);
        newIntent.setAction("android.intent.action.MAIN");
        newIntent.addCategory("android.intent.category.LAUNCHER");
        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newIntent);
    }
    //接收广播:设备上新安装了一个应用程序包后自动启动新安装应用程序。
    if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) {
        String packageName = intent.getDataString().substring(8);//前8个字符是不要的
        System.out.println("---------------" + packageName);
        Intent newIntent = new Intent();
        newIntent.setClassName(packageName,packageName+ .MainActivity");
        newIntent.setAction("android.intent.action.MAIN");
        newIntent.addCategory("android.intent.category.LAUNCHER");
        newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newIntent);
    }
    //接收广播:设备上删除了一个应用程序包。
    if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) {
        System.out.println("********************************");
        DatabaseHelper dbhelper = new DatabaseHelper();
        dbhelper.executeSql("delete from users");
    }
}

23、安卓预置文件到板子(.mk)

//system、product这些目录是可以在系统起来之前准备好的,如果要放在其它目录需要先放在前面两个目录后再复制到其它目录下
app预置:
#<Lango Software>

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)

LOCAL_MODULE := BOE_CMS
#LOCAL_OVERRIDES_PACKAGES := Browser2
#LOCAL_PRIVILEGED_MODULE := true
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
#LOCAL_CERTIFICATE := platform
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_DEX_PREOPT := false
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)

文件预置:
#<Lango Software>
#将用户文件提前预置到/product/sdcard/,用于后面脚本解压和复制
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := aimeishi_file
LOCAL_SRC_FILES := aimeishi_file
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := EXECUTABLE
LOCAL_MODULE_PATH := $(PRODUCT_OUT)/product/sdcard

$(shell mkdir -p $(PRODUCT_OUT)/product/sdcard)
$(shell cp  -rf $(LOCAL_PATH)/*  $(PRODUCT_OUT)/product/sdcard/)

include $(BUILD_PREBUILT)

24、Android通过包名获取应用UID

 try {
         PackageManager pm = getPackageManager();
         ApplicationInfo ai = pm.getApplicationInfo("com.gesoft.bit.lavendercloud", PackageManager.GET_ACTIVITIES);
         Log.d("!!", "!!" + ai.uid);
        } catch (NameNotFoundException e) {
          e.printStackTrace();
        }

25、获取手机已安装app的名称和包名

public void getAppProcessName(Context context) {
    //当前应用pid
    final PackageManager packageManager = context.getPackageManager();
    final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    // get all apps
    final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
    for (int i = 0; i < apps.size(); i++) {
        String name = apps.get(i).activityInfo.packageName;
        if (!name.contains("huawei") && !name.contains("android")) {
            Log.i("TAG", "getAppProcessName: " +
                    apps.get(i).activityInfo.applicationInfo.loadLabel(packageManager).toString() + "---" +
                    apps.get(i).activityInfo.packageName);
        }
    }
}

26、安卓执行shell脚本的类(CommandResult )

package com.example.test;
 
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
 
import android.util.Log;
 
/**
 * 执行shell脚本工具类
 * @author Mountain
 *
 */
public class CommandExecution {
 
	public static final String TAG = "CommandExecution";
	
	public final static String COMMAND_SU       = "su";
	public final static String COMMAND_SH       = "sh";
	public final static String COMMAND_EXIT     = "exit\n";
	public final static String COMMAND_LINE_END = "\n";
 
	/**
	 * Command执行结果
	 * @author Mountain
	 *
	 */
	public static class CommandResult {
		public int result = -1;
		public String errorMsg;
		public String successMsg;
	}
 
	/**
	 * 执行命令—单条
	 * @param command
	 * @param isRoot
	 * @return
	 */
	public static CommandResult execCommand(String command, boolean isRoot) {
		String[] commands = {command};
		return execCommand(commands, isRoot);
	}
 
	/**
	 * 执行命令-多条
	 * @param commands
	 * @param isRoot
	 * @return
	 */
	public static CommandResult execCommand(String[] commands, boolean isRoot) {
		CommandResult commandResult = new CommandResult();
		if (commands == null || commands.length == 0) return commandResult;
		Process process = null;
		DataOutputStream os = null;
		BufferedReader successResult = null;
		BufferedReader errorResult = null;
		StringBuilder successMsg = null;
		StringBuilder errorMsg = null;
		try {
			process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
			os = new DataOutputStream(process.getOutputStream());
			for (String command : commands) {
				if (command != null) {
					os.write(command.getBytes());
					os.writeBytes(COMMAND_LINE_END);
					os.flush();
				}
			}
			os.writeBytes(COMMAND_EXIT);
			os.flush();
			commandResult.result = process.waitFor();
			//获取错误信息
			successMsg = new StringBuilder();
			errorMsg = new StringBuilder();
			successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
			errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
			String s;
			while ((s = successResult.readLine()) != null) successMsg.append(s);
			while ((s = errorResult.readLine()) != null) errorMsg.append(s);
			commandResult.successMsg = successMsg.toString();
			commandResult.errorMsg = errorMsg.toString();
			Log.i(TAG, commandResult.result + " | " + commandResult.successMsg
					+ " | " + commandResult.errorMsg);
		} catch (IOException e) {
			String errmsg = e.getMessage();
			if (errmsg != null) {
				Log.e(TAG, errmsg);
			} else {
				e.printStackTrace();
			}
		} catch (Exception e) {
			String errmsg = e.getMessage();
			if (errmsg != null) {
				Log.e(TAG, errmsg);
			} else {
				e.printStackTrace();
			}
		} finally {
			try {
				if (os != null) os.close();
				if (successResult != null) successResult.close();
				if (errorResult != null) errorResult.close();
			} catch (IOException e) {
				String errmsg = e.getMessage();
				if (errmsg != null) {
					Log.e(TAG, errmsg);
				} else {
					e.printStackTrace();
				}
			}
			if (process != null) process.destroy();
		}
		return commandResult;
	}
	
}

27、字符串切割函数split

split函数的使用
Java中的 split 函数是用于按指定字符(串)或正则去分割某个字符串,结果以字符串数组形式返回。

一个参数:代表根据什么来分(这个必须位于字符串里面)
两个参数:第一个代表根据什么来分割,第二个代表分成几份,分完之后后面的不在继续分
如果想根据多个字符来分,用 | 隔开(所以要注意当本来就要用这个来分割的情况,见后面)

public static void main(String[] args) {
        String str="12@34@56&ab@c";
        
        String[] a = str.split("@");//根据'@'来分
        for(String x:a)
            System.out.println(x);// 输出12   34   56&ab  c
            
        String[] b=str.split("@",3);//根据'@'来分,分成3份
        for(String x:b)
            System.out.println(x);//输出12   34  56&ab@c
            
        String [] c=str.split("!");//若原字符串没有这个,则输出原字符串
        for(String x:c)
            System.out.println(x);//输出12@34@56&ab@c
            
        String [] d=str.split("@|&");//如果既想用@,又想用&,则用|隔开
        for (String x:d)
            System.out.println(x);//输出12  34  56  ab  c
    }

1、如果用“.”作为分隔的话,必须是如下写法,String.split(“\.”),这样才能正确的分隔开,不能用String.split(“.”);
2、如果用“|”作为分隔的话,必须是如下写法,String.split(“\|”),这样才能正确的分隔开,不能用String.split(“|”);“.”和“|”都是转义字符,必须得加"\";

String str="5678|XYZ";  
String[] b = str.split("\\|");  //注意这里用两个 \\,而不是一个\  
System.out.println("处理结果: "+b[0]+","+b[1]);   //输出的是: 处理结果: 5678,XYZ  
public String[] split(String regex)根据给定的正则表达式的匹配来拆分此字符串。
然后就要明确正则表达式的含义了:
\\s表示 空格,回车,换行等空白符,
+号表示一个或多个的意思,所以...

28、默认允许apk修改系统设置

关键类:appops是在现有权限机制上新增的一套权限管理机制,主要针对一些高危的非必须系统应用的权限,比如在其他应用上显示悬浮窗。

(13条消息) AppOpsService服务流程分析_好久没来过的博客-CSDN博客_appopsservice

packages\apps\Settings\src\com\android\settings\applications\appinfo\WriteSettingsDetails.java

    // 要求允许修改系统设置权限的apk
    private static final String usrAppCanWriteSettings = SystemProperties.get("persist.app.canWriteSettings", null);
    private static final ArrayList<String> appCanWriteSettings = new ArrayList<String>(){{}};
***************************************************************************************************************
	if(usrAppCanWriteSettings != null){
            addAppCanWriteSettings();
        }
    }

    private void addAppCanWriteSettings(){
        String [] arr = usrAppCanWriteSettings.split("\\s+");

        for(String tmp : arr){
            if(!appCanWriteSettings.contains(tmp)){
                appCanWriteSettings.add(tmp);
            }
        }
    }
*******************************************************************************************************************    
		AppOpsManager mAppOpsManager;
            for(String tmp : appCanWriteSettings){
                if(appCanWriteSettings.contains(pkgName)){
                    try {
                        final PackageManager pm = mContext.getPackageManager();
                        ApplicationInfo mAppInfo = pm.getApplicationInfo(pkgName, PackageManager.GET_ACTIVITIES);
                        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
                        mAppOpsManager.setMode(AppOpsManager.OP_WRITE_SETTINGS,
                        mAppInfo.uid, pkgName, AppOpsManager.MODE_ALLOWED);
                    } catch (NameNotFoundException e) {
                        Slog.e(TAG, "NameNotFoundException failed: " + e.getMessage());
                    }
                }
            }

29、海思android随笔之hippo数据交互

(13条消息) 海思android随笔之hippo数据交互_hmz0303hf的博客-CSDN博客_hisi hippo

前言

海思的代码结构很清晰,db相关操作基本都在device/hisilicon/bigfish/hippo/vendor/common/db目录里,结构如下图

主要用到的是hi_mw_dbo和hi_mw_db_def

一、上层调用方法

Set:调用相关命令,返回该命令的执行结果(成功或失败)
HitvManager.getInstance().excuteCommandSet();

    /**
     * Excute command set int.
     *
     * @param cmd_id the cmd id
     * @return 1(success) or 0(failure)
     */
    public int excuteCommandSet(int cmd_id) {
        Parcel request = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        request.writeInt(cmd_id);
        int ret = invoke(request, reply);
        request.recycle();
        reply.recycle();

        return ret;
    }

Get:调用相关命令,返回命令中replay里的数据
HitvManager.getInstance().excuteCommandGet();

    /**
     * Excute command get int.
     *
     * @param cmd_id the cmd id
     * @param arg1   the arg 1
     * @return the int
     */
    public int excuteCommandGet(int cmd_id, int arg1) {
        Parcel request = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        request.writeInt(cmd_id);
        request.writeInt(arg1);
        invoke(request, reply);
        int ret = reply.readInt();
        request.recycle();
        reply.recycle();

        return ret;
    }

函数实体都在device/hisilicon/bigfish/hippo/api/java/com/hisilicon/android/tvapi/HitvManager.java

二、上层可调用命令列表

static LocalAtomicInfo g_astSystemModuleLocalAtomicInfo[] = {
    /* int */ /* method */
    { CMD_SERVICE_INIT,                   Init },
    { CMD_SERVICE_DEINIT,                 Deinit },
    { CMD_SYSTEM_RESTOREDEFAULT,          RestoreDefault },
    { CMD_SYSTEM_SETSCREENBLUE,           MuteScreen },
    { CMD_SYSTEM_GETSCREENBLUE,           GetScreenStatus },
    { CMD_SYSTEM_SHUTDOWN,                Shutdown },
    { CMD_SYSTEM_SUSPEND,                 Suspend },
    { CMD_SYSTEM_RESUME,                  Resume },
    { CMD_SYSTEM_SETPOWERONPANEL,         SetPowerOnPanel },
    { CMD_SYSTEM_GETPOWERONPANEL,         GetPowerOnPanel },
    { CMD_SYSTEM_SETLSADCKEY,             SetLSADCKey },
    { CMD_SYSTEM_GETLSADCKEY,             GetLSADCKey },
    { CMD_SYSTEM_GETBOOTVERSION,          GetBootVersion },
    { CMD_SYSTEM_SETPANELFIXOUTRATE,      SetPanelFixOutRate },
    { CMD_SYSTEM_SETRESUMETIMER,          SetResumeTimer },
    { CMD_SYSTEM_SETHDMIEDIDTYPE,         SetHdmirxEdidType },
    { CMD_SYSTEM_GETHDMIEDIDTYPE,         GetHdmirxEdidType },
    { CMD_SYSTEM_GETHDMIEDIDCAP,          GetHdmirxEdidCap },
    { CMD_SYSTEM_SETCOUNTRY,              SetCountry },
    { CMD_SYSTEM_GETCOUNTRY,              GetCountry },
    { CMD_SYSTEM_GETWAKEUP,               GetWakeup },
    { CMD_SYSTEM_GETCURRENTLOCK,          IsCurrentLocked },
    { CMD_SYSTEM_SETLOCKENABLE,           SetLockEnable },
    { CMD_SYSTEM_GETLOCKENABLE,           GetLockEnable },
    { CMD_SYSTEM_SETLOCKPWD,              SetPwdStatus },
    { CMD_SYSTEM_GETLOCKPWD,              GetPwdStatus },
......
}

三、流程图

下图以CMD_SYSTEM_GETSCREENBLUE为例,大致流程如下:

app------excuteCommandGet()->HitvManager.java------GetScreenStatus(in, out)->hi_mw_logic_system_module.cpp------GetScreenStatus(*penScreenColor)

->hi_mw_logic_system.cpp------db Query(HI_MW_ATTR_SYSTEM)->hi_mw_dbo.cpp

hi_mw_dbo.cpp------*pData.enScreenColor->hi_mw_logic_system.cpp------*penScreenColor->hi_mw_logic_system_module.cpp------out->HitvManager.java

------out->app

四、续~

30、蓝牙sco通话

APP调用AudioManager::startBluetoothSco()

// frameworks/base/media/java/android/media/AudioManager.java
public void startBluetoothSco(){
    service.startBluetoothSco(mICallBack,
                    getContext().getApplicationInfo().targetSdkVersion);
}
 
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
    final int scoAudioMode =
                (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
                 BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
    final String eventSource = new StringBuilder("startBluetoothSco()")
                .append(") from u/pid:").append(uid).append("/")
                .append(pid).toString();
    startBluetoothScoInt(cb, uid, pid, scoAudioMode, eventSource);
}
 
void startBluetoothScoInt(IBinder cb, int uid, int pid, int scoAudioMode, @NonNull String eventSource) {
        final long ident = Binder.clearCallingIdentity();
        mDeviceBroker.startBluetoothScoForClient(cb, uid, pid, scoAudioMode, eventSource);
        Binder.restoreCallingIdentity(ident);
        mmi.record();
}
 
// frameworks/base/services/core/java/com/android/server/audio/AudioDeviceBroker.java
void startBluetoothScoForClient(IBinder cb, int uid, int pid, int scoAudioMode, @NonNull String eventSource) {
    AudioDeviceAttributes device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
    setCommunicationRouteForClient(cb, uid, pid, device, scoAudioMode, eventSource);
}
 
void setCommunicationRouteForClient(IBinder cb, int uid, int pid, AudioDeviceAttributes device, int scoAudioMode, String eventSource) {
    ......
    mBtHelper.startBluetoothSco(scoAudioMode, eventSource);
    ......
}
 
// frameworks/base/services/core/java/com/android/server/audio/BtHelper.java
synchronized boolean startBluetoothSco(int scoAudioMode,  @NonNull String eventSource) {
    AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
    return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
 
private boolean requestScoState(int state, int scoAudioMode) {
    if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
        connectBluetoothScoAudioHelper(mBluetoothHeadset, mBluetoothHeadsetDevice, mScoAudioMode)
        ......
    } 
    // 断开连接的时候发送断连的广播
}

这里做了两件事,一个是发送广播,一个是调用connectBluetoothScoAudioHelper

先看广播发送

private void broadcastScoConnectionState(int state) {
    mDeviceBroker.postBroadcastScoConnectionState(state);
}
 
// frameworks/base/services/core/java/com/android/server/audio/AudioDeviceBroker.java
void postBroadcastScoConnectionState(int state) {
   sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
}
 
// 收到消息MSG_I_BROADCAST_BT_CONNECTION_STATE之后调用onBroadcastScoConnectionState
 
// frameworks/base/services/core/java/com/android/server/audio/BtHelper.java
synchronized void onBroadcastScoConnectionState(int state) {
   Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
   newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
   newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
                mScoConnectionState);
   sendStickyBroadcastToAll(newIntent);
   mScoConnectionState = state;
}
 
private void sendStickyBroadcastToAll(Intent intent) {
    mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
// 最终这个广播发出去就被一个app接收去显示蓝牙俩呢及状态了。

再看connectBluetoothScoAudioHelper

// frameworks/base/services/core/java/com/android/server/audio/BtHelper.java
 private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode) {
        switch (scoAudioMode) {
            case SCO_MODE_RAW:
                return bluetoothHeadset.connectAudio();
            case SCO_MODE_VIRTUAL_CALL:
                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
            case SCO_MODE_VR:
                return bluetoothHeadset.startVoiceRecognition(device);
            default:
                return false;
        }
}

31、常见Property属性

我们把以下常见属性进行归类:

init.svc.xxx

通过启动脚本启动的后台Native服务, 都会自动设置该属性,用于记录服务的启动状态

dalvik.vm.xxx

pm.dexopt.xxx

Android ART或Dalvik虚拟机运行相关属性,很多属性都是在mk中定义, 以下属性就是针对ART编译和运行时的参数,包括影响JIT编译选项, dex优化, pm安装器安装参数等, 详细可参考:

https://source.android/devices/tech/dalvik/configure?hl=zh-cn

[dalvik.vm.heapstartsize]: [16m]: 系统分配给应用冷启动的初始堆大小

[dalvik.vm.usejit]: [true] 是否启用 JIT

[pm.dexopt.boot]: [verify] 无线下载更新后使用的编译过滤器

pm.dexopt.bg-dexopt]: [speed] 后台优化模式

[pm.dexopt.boot]: [verify] 无线下载更新后的启动模式

[pm.dexopt.install]: [speed-profile] 应用安装模式

xxx_completed

用于记录系统是否完成启动,或者某些服务是否完成启动。比较重要的一个就是sys.boot_completed,表示Android系统是否完成启动。

sys.xxx.xxx

如sys.usb.config,sys.boot.reason
sys.powerctl

ro.kernel.xx

由内核通过启动参数传递过来的参数(androidboot开头的字符串, 如果是qemu启动, 就会设置ro.kernel…xxx):

ro.boot.xx

Init启动会额外设置几个属性:

{ “ro.boot.serialno”, “ro.serialno”, “”, },

{ “ro.boot.mode”, “ro.bootmode”, “unknown”, },

{ “ro.boot.baseband”, “ro.baseband”, “unknown”, },

{ “ro.boot.bootloader”, “ro.bootloader”, “unknown”, },

{ “ro.boot.hardware”, “ro.hardware”, “unknown”, },

{ “ro.boot.revision”, “ro.revision”, “0”, },

ro.build.xxx

记录编译镜像的情况的属性, 如:编译时间,系统版本, sdk api,系统补丁时间。

[ro.build.date]: [Tue Dec 14 17:21:12 CST 2021]

[ro.build.descriptioun]: [qh100_rk3399-userdebug 10 QD:4A.200805.003 eng.20211214.172125 release-keys]

[ro.build.display.id]: [qh100_rk3399-userdebug 1b0 QD4A.200805.003 eng.2021j1214.172125 release-keys]

ro.boottime.xx

记录各种服务器启动时间,这个时间是从某个时间基准开始累计的时间值,ns为单位,

[ro.boottime.init]: [1353] init 的第一阶段开始的时间

[ro.boottime.init.cold_boot_wait]: [273] :init 等待 ueventd 的冷启动阶段结束的时间

[ro.boottime.init.selinux]: [171] 第一阶段初始化 SELinux 花了多长时间

[ro.boottime.<服务名称>]:以 ns 为单位启动后的时间,该服务首次启动的时间,如

ro.product.xxx

在编译源码时,会选择产品, 这些属性就是用来记录当前源码选择产品所对应的信息,其实我们在配置一个新产品时, 产品mk文件的内容就会在属性中展示。

ro.vendor.xxx

厂商相关属性, 更多的是针对soc硬件厂商,以下属性和ro.product.xx很想

[ro.vendor.build.date]: [Tue Dec 14 17:21:12 CST 2021]

persist.xxx

可持久化的属性,这些属性被初始化之后,会被写入到文件进行持久化存储

存储在: /data/property/persistent_properties

[persist.sys.boot.reason]:

[persist.vendor.sys.hid]:

各种进程或者用户自定义设置的属性

如: setprop my.first.name qihao

ctrl.start

ctrl.stop

ctl.restart

这几个属性是用于控制启动和关闭,重启服务的, 但是需要selinux权限(会去看是否有访问以下客体的权限)

u:object_r:ctl_restart_prop:s0

u:object_r:ctl_rildaemon_prop:s0

u:object_r:ctl_sigstop_prop:s0

u:object_r:ctl_start_prop:s0

u:object_r:ctl_stop_prop:s0

logd相关属性

persist.logd.size 所有日志缓存区大小的默认大小,默认为256K只能编译版本的时候修改。

persist.logd.size.main main日志缓冲区大小,默认为256K

persist.logd.size.system system日志缓冲区大小,默认为256K

setprop ro.logd.kernel true控制内核日志输出到logd的buffer中,通过logcat抓取

setprop ro.logd.auditd true控制selinux日志输出到logd的buffer中,通过logcat抓取

32、如何开机自启动app但不打开UI

答:将app写成服务的形式,接收开机广播启动服务。

33、App收不到系统广播

例如开机广播,这时候可以将app作为系统应用添加到板子里。

1、将apk放入到系统app目录

aadb.exe push D:\androidProject\MyApplication6\app\release\app-release.apk /system/app/

2、在AndroidManifest.xml加上uid

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/tools"
    package="com.example.myapplication"
    android:sharedUserId="android.uid.system">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
<!--        <service-->
<!--            android:name=".SysInfoReportService"-->
<!--            android:enabled="true"-->
<!--            android:exported="true"></service>-->

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <!-- <category android:name="android.intent.category.DEFAULT"/> -->
            </intent-filter>
        </activity>

        <receiver
            android:name=".LockReceiver"
            android:description="@string/app_name"
            android:exported="true"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/lock" />

            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
        </receiver>
        <receiver
            android:name=".HttpGetInfReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>

        <service
            android:name=".HttpGetInformationService"
            android:exported="true"
            tools:ignore="Instantiatable" />
    </application>

</manifest>

34、判断当前网络状态的广播

public static final String CONNECTIVITY_ACTION = "android.conn.CONNECTIVITY_CHANGE";

该广播可以分辨有线和无线的网络状态且只能动态注册,不支持静态注册。以下为使用例子:

public void onReceive(Context context, Intent intent) {


        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){
            String action = intent.getAction();
            if(DEBUG) Log.d(TAG,"action is -- > " + action);
            if(DEBUG) Log.d(TAG,"version106 -- > " + action);

//            context.startService(SysInfoReportService.getIntent(context));
            context.startService(HttpGetInformationService.getIntent(context));
            if(DEBUG) Log.d(TAG,"start1 -- > " + action);
        }

        if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)){
            String action = intent.getAction();
            NetworkHelper mNetworkHelper = new NetworkHelper();
            if(DEBUG) Log.d(TAG,"action is**** -- > " + action);

            long time = getTime();
//            if (time != WIFI_TIME && time != ETHERNET_TIME && time != NONE_TIME) {
                int netWorkState = getNetWorkState(context);
            if(DEBUG) Log.d(TAG, "netWorkState:" + netWorkState);
                if (netWorkState == 0 && LAST_TYPE != 0) {
                    WIFI_TIME = time;
                    LAST_TYPE = netWorkState;
                    CUR_IP = mNetworkHelper .getWifiIPAddress();
                    if(DEBUG) Log.d(TAG, "CUR_IP:" + CUR_IP);
                    if(DEBUG) Log.d(TAG, "LAST_IP:" + LAST_IP);
                    if(!CUR_IP.equals(LAST_IP)){
                        HttpGetInformationService.getInstance(context).start();
                        LAST_IP = CUR_IP;
                    }
                    if(DEBUG) Log.e(TAG, "wifi:" + time);
                } else if (netWorkState == 1 && LAST_TYPE != 1) {
                    ETHERNET_TIME = time;
                    LAST_TYPE = netWorkState;
                    if(DEBUG) Log.e(TAG, "数据网络:" + time);
                    if(DEBUG) Log.d(TAG, "CUR_IP:" + CUR_IP);
                    if(DEBUG) Log.d(TAG, "LAST_IP:" + LAST_IP);
                    CUR_IP = mNetworkHelper.getWifiIPAddress();
                    if(!CUR_IP.equals(LAST_IP)){
                        HttpGetInformationService.getInstance(context).start();
                        LAST_IP = CUR_IP;
                    }
                } else if (netWorkState == -1 && LAST_TYPE != -1) {
                    NONE_TIME = time;
                    LAST_TYPE = netWorkState;
                    if(DEBUG) Log.e(TAG, "无网络:" + time);
                } else if (netWorkState == 2 && LAST_TYPE != 2) {
                    NONE_TIME = time;
                    LAST_TYPE = netWorkState;
                    CUR_IP = mNetworkHelper .getEthernetIPAddress();
                    if(DEBUG) Log.d(TAG, "CUR_IP:" + CUR_IP);
                    if(DEBUG) Log.d(TAG, "LAST_IP:" + LAST_IP);
//                    if(!CUR_IP.equals(LAST_IP)){
                        HttpGetInformationService.getInstance(context).start();
                        LAST_IP = CUR_IP;
//                    }
                    if(DEBUG) Log.e(TAG, "有线网络:" + time);
                }
            }
//        }
    }


    public long getTime() {
        SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
        String date = sDateFormat.format(new java.util.Date());
        return Long.valueOf(date);
    }

    private static final int NETWORK_NONE = -1; //无网络连接
    private static final int NETWORK_WIFI = 0; //wifi
    private static final int NETWORK_MOBILE = 1; //数据网络
    private static final int NETWORK_ETHERNET = 2; //数据网络
    //判断网络状态与类型
    public static int getNetWorkState(Context context) {
        ConnectivityManager connectivityManager = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);
        @SuppressLint("MissingPermission") NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
            if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
                return NETWORK_WIFI;
            } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) {
                return NETWORK_MOBILE;
            } else if(activeNetworkInfo.getType() == (ConnectivityManager.TYPE_ETHERNET)){
            return NETWORK_ETHERNET;
            }
        } else {
            return NETWORK_NONE;
        }
        return NETWORK_NONE;
    }

35、HttpGet接口获取服务器数据

例如:中国电信非蜂窝类智慧家庭类终端自注册信息数据归集接口规范

package com.example.myapplication;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import androidx.annotation.Nullable;

import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.HttpEntity;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.methods.CloseableHttpResponse;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.client.methods.HttpGet;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.impl.client.CloseableHttpClient;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.impl.client.HttpClientBuilder;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.util.EntityUtils;
import com.xbh.sdk3.Network.NetworkHelper;
import com.xbh.sdk3.client.UserAPI;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;

public class HttpGetInformationService extends Service {
    private static final boolean DEBUG = false;
    private static final String TAG = "HttpGetInfService";
    public Context mContext;
    private static final int MSG_XBH_EVENT_OCCUR = 60;
    private HttpGetInformationServiceHandler mHandler;
    private static HttpGetInformationService sInstance = null;
//    private static int timeout = 24 * 60 * 1000;
    private static int timeout = 6 * 1000;
//    private static int dismissTimeout = 30 * 1000;
    private static int dismissTimeout = 2 * 1000;
    int timeoutTimes = 0;
    HttpGetInfReceiver httpGetInfReceiver = new HttpGetInfReceiver();

    public HttpGetInformationService(Context context) {
        mContext = context;
    }

    public HttpGetInformationService() {
        Log.d(TAG, "HttpGetInformationService()");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate()");
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        this.registerReceiver(httpGetInfReceiver,intentFilter);
    }
    public static HttpGetInformationService getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new HttpGetInformationService(context);
        }
        UserAPI.getInstance().init(context);
        return sInstance;
    }

    private synchronized void ensureHandler() {
        if (mHandler == null) {
            HandlerThread thread = new HandlerThread(TAG);
            thread.start();
            mHandler = new HttpGetInformationServiceHandler(thread.getLooper());
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public static Intent getIntent(Context context) {
        return new Intent(context, HttpGetInformationService.class);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand()");
        if(DEBUG) Log.d(TAG,"start2 -- > " );
        if(sInstance == null){
            mContext = getApplicationContext();
            sInstance = getInstance(getApplicationContext());
        }
        start();
        if(DEBUG) Log.d(TAG,"start3 -- > ");
        return START_REDELIVER_INTENT;
    }


    private class HttpGetInformationServiceHandler extends Handler {
        public HttpGetInformationServiceHandler(Looper looper) {
            super(looper);
        }


        @Override
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case MSG_XBH_EVENT_OCCUR:
                    if(checkNetworkAvailable()){
                        httpGetMessage();
                    } else{
                        if(DEBUG) System.out.println("目前网络还没有通");
//                        mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, 10 * 1000);
                    }
                    break;
            }
        }
    }

    public void start() {
        ensureHandler();
        mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, 0);
    }

    public boolean checkNetworkAvailable() {
        //获取手机所有链接管理对象(包括对Wi-Fi,net等连接的管理)
        ConnectivityManager manager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (manager == null) {
            return false;
        } else {
            //获取NetworkInfo对象
            NetworkInfo[] info = manager.getAllNetworkInfo();
            if (info != null && info.length > 0) {
                for (int i = 0; i < info.length; i++) {
                    if(DEBUG) System.out.println(i + "状态" + info[i].getState());
                    if(DEBUG) System.out.println(i + "类型" + info[i].getTypeName());

                    // 判断当前网络状态是否为连接状态
                    if (info[i].getState() == NetworkInfo.State.CONNECTED) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    @SuppressLint("LongLogTag")
    public void httpGetMessage() {
        NetworkHelper mNetworkHelper = new NetworkHelper();
        if(DEBUG) Log.d(TAG, "eth mac=" + mNetworkHelper.getEthernetMacAddr());
        if(DEBUG) Log.d(TAG, "eth ip=" + mNetworkHelper .getEthernetIPAddress());

        SimpleDateFormat formatter =  new  SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date curDate = new Date(System.currentTimeMillis());//获取当前时间
        String str = formatter.format(curDate);
        String wifiEthAddress = null;
        if(DEBUG) Log.d(TAG, "curDate=" + str);
        // 简单建立一个客户端
        CloseableHttpClient client = HttpClientBuilder.create().build();
        // 建立请求url
//        StringBuilder sb = new StringBuilder("http://typdm.tydevice/?jsonstr={\"VER\":\"01\",\"CTEI\":\"CT1234561234567\",\"MAC\":\"24:e2:71:f4:d7:b0\",\"IP\":\"192.168.0.1\",\"UPLINKMAC\":\"01:02:03:04:05:06\",\"LINK\":\"1" +
//                "\",\"FWVER\":\"1.1\",\"DATE\":\"2018-07-01 12:05:30\",\"REMOTEIP\":\"198.122.1.2\",\"RECEIVEDATE\":\"2018-07-01 12:10:30\",\"PORT\":\"8090\"}");
        StringBuilder sb = new StringBuilder("http://typdm.tydevice/?jsonstr={\"VER\":\"01\",\"CTEI\":\"CT1234561234567\",");
        wifiEthAddress = mNetworkHelper.getEthernetIPAddress();
        if(wifiEthAddress == null){
            wifiEthAddress = mNetworkHelper.getWifiIPAddress();
        }
        sb.append("\"MAC\":\"" + mNetworkHelper.getEthernetMacAddr() + "\",");
        sb.append("\"IP\":\"" + wifiEthAddress + "\",");
        sb.append("\"UPLINKMAC\":\"01:02:03:04:05:06\",");
        sb.append("\"LINK\":\"1\",");
        sb.append("\"FWVER\":\"1.1\",");
        sb.append("\"DATE\":\"" + str + "\"}");


        if(DEBUG) Log.d(TAG, "curDate=" + sb.toString());
//        StringBuilder sb = new StringBuilder("http://typdm.tydevice/?jso");
//                System.out.println(sb.toString());
        URL url = null;
        try {
            url = new URL(sb.toString());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        URI uri = null;
        try {
            uri = new URI("http",url.getUserInfo(),url.getHost(),url.getPort(),url.getPath(),url.getQuery(),null);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        if(DEBUG) System.out.println(uri);

        HttpGet get = new HttpGet(uri);
        // 设置请求头          get.setHeader("","");
        CloseableHttpResponse response1 = null;
        try {
            response1 = client.execute(get);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(DEBUG) System.out.println("Login form get: " + response1.getStatusLine());
        HttpEntity entity = response1.getEntity();
        String result = null;
        try {
            result = EntityUtils.toString(response1.getEntity(), "utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(DEBUG) System.out.println(result);
        Log.d(TAG, "httpGet result: " + result);

        if(result.contains("300")){
            if(timeoutTimes > 3){
                mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, timeout);
                timeoutTimes = 0;
                if(DEBUG) Log.d(TAG, "timeoutTimes: " + timeoutTimes);
            } else {
                mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, dismissTimeout);
                timeoutTimes += 1;
            }
        } else {
            mHandler.sendEmptyMessageDelayed(MSG_XBH_EVENT_OCCUR, timeout);
        }
    }
}

36、Android系统ANR错误实战分析

Android开发中经常碰到一个叫做ANR(Application Not Responding)的问题,如果这个apk是你们自己开发的,报ANR,有经验的程序员自己肯定会较为容易的检查出代码哪里出了问题。好,那么问题来了,如果是原厂(MTK,全志,RK,amlogic等)系统报出ANR,我们怎么解决?下面就用我公司实际碰到的问题为例,为大家实战讲解。

相信大家都知道有个东西叫小部件Widget,我们手机上的时钟小部件可以选择大小,开机的时候加载到我们的桌面上显示时间。前几天公司发现一个bug,直接上图。

如图,时钟Widget一直显示正在加载,几分钟后依然不显示时间,抓取log,发现ANR错误,如下:

使用的板子和源码均为amlogic平台,Android版本为9.0 。 由于是编译完直接烧录,可以100%确定是由源码引起ANR,从log信息看,似乎是com.android.phone 这个应用包下的VvmSimStateTracker这个类引起的,那是这样么?我们找到这个类

可以看出,VvmSimStateTracker就是一个广播接收器,就是用来监听一些sim卡热插拔的工具类,从刚才的log中看,是由于开机广播的处理出现了问题导致ANR,好,那么我们把这个android.intent.action.BOOT_COMPLETED给它注释掉,编译,看下结果。结果发现竟然正常了,时钟部件开机5秒内正常显示时间。好多人有疑问了,为什么只是简单接收了一个开机广播,就报ANR了呢?我们又不是第一天接收它了,留个疑问在这里。

Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。具体来说,ANR会在以下几种情况中出现:

  • 输入事件(按键和触摸事件)5s内没被处理: Input event dispatching timed out
  • BroadcastReceiver的事件(onRecieve方法)在规定时间内没处理完(前台广播为10s,后台广播为60s):Timeout of broadcast BroadcastRecord
  • service 前台20s后台200s未完成启动 Timeout executing service
  • ContentProvider的publish在10s内没进行完:timeout publishing content providers

看完这个很多人就会恍然大悟,第二条这个报的错和上面日志的那个不是一样么。

那这下原因找到了,就是因为UI线程阻塞导致了VvmSimStateTracker类没有在规定时间内处理完BroadcastReceiver事件。_ 那问题又来了,UI线程为什么会阻塞?谁把它搞阻塞了?这里我就要告诉大家怎么分析解决系统ANR。

首先,adb shell 进入 data/system 目录下会看到一个叫做dropbox 的文件夹(adb操作我就不多赘述了,可以查看其他博客)

只要你的Android系统出现ANR或者Crash等,系统就会保存日志到这个文件夹中,对你分析问题的产生有巨大帮助。话不多说,我们把这个dropbox拷出来,看看里面的内容。

好家伙,东西还挺多,可以看到里面有很多的日志,那么我们所需要分析ANR的日志正是 system_app_anr@*******.txt.gz,不同平台的命名规则可能大同小异,总之大家看anr这几个关键字就可以了。解压,打开看看。

前面几行我们刚才见过了,下面红色标注的这里就是系统在发生ANR时CPU的实时使用情况。

可以看到,我们的CPU在发生ANR 的时候总共才使用了12%左右的性能。这里要注意了,

  1. 如果发生ANR的进程CPU占用较高,如到了80%或90%以上,则可以怀疑是应用内一些代码不合理消耗掉了CPU资源,比如死循环或者一些算法库进行大量高精度复杂运算导致CPU长期占用较高,这就要结合trace和ANR前后的log进一步分析了。
  2. 如果某些进程的CPU占用百分比较高,几乎占用了所有CPU资源,而发生ANR的进程CPU占用为0%或非常低,则认为CPU资源被占用,进程没有被分配足够的资源,从而发生了ANR。这种情况多数可以认为是系统状态的问题,并不是由本应用造成的。
  3. 如果CPU总用量不高,那么很有可能是一些耗时操作或者锁的问题使主线程被阻塞 ,导致ANR。
  4. 如果iowait 占用率过高,很可能是系统等待I/O耗时操作,导致ANR。

这里我们明显看到CPU使用情况在各项指标中都表现正常,那么我们怀疑是第三条,也就是一些耗时操作或者锁的问题使主线程被阻塞 ,既然怀疑是主线程阻塞,那么我们往下看,找到主线程的相关日志。

"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x75429ee0 self=0xaea49000
  | sysTid=4078 nice=0 cgrp=default sched=0/0 handle=0xb2d34494
  | state=S schedstat=( 168928298 466851782 531 ) utm=6 stm=10 core=0 HZ=100
  | stack=0xbb365000-0xbb367000 stackSize=8MB
  | held mutexes=
  native: #00 pc 00019d58  /system/lib/libc.so (syscall+32)
  native: #01 pc 0001d215  /system/lib/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+88)
  native: #02 pc 000633b1  /system/lib/libc.so (pthread_cond_timedwait+84)
  native: #03 pc 0004a005  /system/lib/libc++.so (_ZNSt3__118condition_variable15__do_timed_waitERNS_11unique_lockINS_5mutexEEENS_6chrono10time_pointINS5_12system_clockENS5_8durationIxNS_5ratioILx1ELx1000000000EEEEEEE+124)
  native: #04 pc 0001ebf1  /system/lib/libhidltransport.so (android::hardware::details::Waiter::wait()+224)
  native: #05 pc 0001f31f  /system/lib/libhidltransport.so (android::hardware::details::getRawServiceInternal(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, bool, bool)+826)
  native: #06 pc 000b07c5  /system/lib/libandroid_runtime.so (JHwBinder_native_getService(_JNIEnv*, _jclass*, _jstring*, _jstring*, unsigned char)+168)
  at android.os.HwBinder.getService(Native method)
  at android.hardware.radio.V1_0.IRadio.getService(IRadio.java:40)
  at com.android.internal.telephony.RIL.getRadioProxy(RIL.java:369)
  at com.android.internal.telephony.RIL.getHardwareConfig(RIL.java:3367)
  at com.android.internal.telephony.TelephonyDevController.registerRIL(TelephonyDevController.java:112)
  at com.android.internal.telephony.RIL.<init>(RIL.java:484)
  at com.android.internal.telephony.PhoneFactory.makeDefaultPhone(PhoneFactory.java:172)
  - locked <0x051877b9> (a java.lang.Object)
  at com.android.internal.telephony.PhoneFactory.makeDefaultPhones(PhoneFactory.java:99)
  at com.android.phone.PhoneGlobals.onCreate(PhoneGlobals.java:286)
  at com.android.phone.PhoneApp.onCreate(PhoneApp.java:41)
  at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1154)
  at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5871)
  at android.app.ActivityThread.access$1100(ActivityThread.java:199)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  at android.os.Looper.loop(Looper.java:193)
  at android.app.ActivityThread.main(ActivityThread.java:6669)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

“main” 这个标志就代表主线程的相关日志,我们看到在 PhoneFactory 类中 makeDefaultPhone方法是有Object对象锁的。不急,我们找到日志中显示的几个类看看,

PhoneApp类 onCreate 时会调用 PhoneGlobals的 onCreate。

public class PhoneApp extends Application {
    PhoneGlobals mPhoneGlobals;
    TelephonyGlobals mTelephonyGlobals;
 
    public PhoneApp() {
    }
 
    @Override
    public void onCreate() {
        if (UserHandle.myUserId() == 0) {
            // We are running as the primary user, so should bring up the
            // global phone state.
            mPhoneGlobals = new PhoneGlobals(this);
            mPhoneGlobals.onCreate();
 
            mTelephonyGlobals = new TelephonyGlobals(this);
            mTelephonyGlobals.onCreate();
        }
    }
}

PhoneGlobals类 onCreate时会调用 PhoneFactory类的 makeDefaultPhones

public void onCreate() {
        if (VDBG) Log.v(LOG_TAG, "onCreate()...");
 
        ContentResolver resolver = getContentResolver();
 
        // Cache the "voice capable" flag.
        // This flag currently comes from a resource (which is
        // overrideable on a per-product basis):
        sVoiceCapable =
                getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
        // ...but this might eventually become a PackageManager "system
        // feature" instead, in which case we'd do something like:
        // sVoiceCapable =
        //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
 
        if (mCM == null) {
            // Initialize the telephony framework
            PhoneFactory.makeDefaultPhones(this);
 
            // Start TelephonyDebugService After the default phone is created.
            Intent intent = new Intent(this, TelephonyDebugService.class);
            startService(intent);
 
            mCM = CallManager.getInstance();
            for (Phone phone : PhoneFactory.getPhones()) {
                mCM.registerPhone(phone);
            }

而PhoneFactory类的 makeDefaultPhones方法是有对象锁的

那么问题迎刃而解,就是由于开机启动的时候,PhoneApp类调用PhoneFactory类的makeDefaultPhones 方法,而makeDefaultPhones还是被对象锁住的状态,导致主线程阻塞,进而导致VvmSimStateTracker类没有在规定时间内处理完开机广播,发生ANR。那么我们试试解决这个问题,把PhoneAPP初始化操作放在一个子线程中去进行。

@Override
    public void onCreate() {
        if (UserHandle.myUserId() == 0) {
            // We are running as the primary user, so should bring up the
            // global phone state.
           /* mPhoneGlobals = new PhoneGlobals(this);
            mPhoneGlobals.onCreate();
            mTelephonyGlobals = new TelephonyGlobals(this);
            mTelephonyGlobals.onCreate(); */
 
			new Thread(new Runnable() {
				@Override
				public void run() {
 
                    Looper.prepare();
					
					mPhoneGlobals = new PhoneGlobals(PhoneApp.this);
					mPhoneGlobals.onCreate();
 
					mTelephonyGlobals = new TelephonyGlobals(PhoneApp.this);
                    mTelephonyGlobals.onCreate(); 
            
                    Looper.loop();
					
				}
			}).start();
 
        }
    }

编译下,试试效果

37、全局获取Context 和 Intent传递数据

(21条消息) Android系统ANR错误实战分析_麻锦荣的博客-CSDN博客_anr 错误

一、全局获取Context

Android提供了一个Application类,每当程序启动的时候就会自动将这个类初始化。
定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如全局Context

public class MyApplication extends Application {
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }

    public static Context getContext(){
        return context;
    }
}

接着要告知系统程序启动初始化MyApplication类,而不是默认的Application类。修改AndroidManifest.xml

    <application
        android:name="com.example.day22_skills.MyApplication"

不过这里要加上完整的包名,不然没法找到这个类
这样就实现了一种全局获取Context的机制,之后不管在任何地方想要使用Context,只需调用一下MyApplication.getContext()就行

二、使用Intent传递对象

Intent一般使用putExtra()传递数值,可这样只能传递一些常用的数据类型,当想传递一些自定义对象却无从下手

1、Serializable方式

序列化是将一个对象转换成可存储或可传输的状态,序列化只需要实现Serializable接口即可

比如有一个自定义类People,为了将他序列化,在实现Serializable的同时设置所有字段的getter和setter即可
public class People implements Serializable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

发送数据:

Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intetn.putExtra("person_data", person);
startActivity(intent);

获取数据需要调用getSerializableExtra()将序列换对象向下转型:

Person person = (Person) getIntent().getSerializableExta("person_data");

2、Parcelable方式

这种方式比Serialzable效率更高,但麻烦一些,需要将一个完整的对象分解,分解后的每一部分都是Intent支持的类型:

public class People implements Parcelable {
    private String name;
    private int age;

......

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public static final Parcelable.Creator<People> CREATOR = new Parcelable.Creator<People>(){
        @Override
        public People createFromParcel(Parcel source) {
            People people = new People();
            people.name = source.readString();
            people.age = source.readInt();
            return people;
        }

        @Override
        public People[] newArray(int size) {
            return new People[size];
        }
    };
}

首先实现Parcelable接口必须describeContents()和writeToParcel(),其中describeContents直接返回0,而在writeToParcel()中需要调用Parcel.writeXXX()方法,将类其中的字段一一写出

接着在People类中提供一个名为CREATOR的常量,这里提供了一个Parcelable.Creator接口的一个实现,并将泛型指定为People,接着继续重写createFromParcel()和newArray():

  • createFromParcel():读取刚才写出的字段,并创建一个People对象并返回,这里读取的顺序必须和刚才写出的顺序相同
  • newArray():new出一个People数组,并使用方法中传入的size作为数组大小即可

这样获取数据:

People people = (People) getIntent().getParcelableExtra("person_data");

Parcelable方式具有更高的运行效率,所以更推荐

38、Service启动失败的原因

如果出现一下打印:

Slog.w(TAG,Background start not allowed: service “+ service(启动的Intent+ " to " + r.shortInstanceName(Service名字 )+ " from pid=” + callingPid(调用方的pid) + " uid=+ callingUid(调用方的uid)+ " pkg=" + callingPackage(调用方的包名) + " startFg?=" + fgRequired(是否是前台Service);

这是因为非系统应用没有uid后台会启动不了服务,这里应该加上uid

android:sharedUserId="android.uid.system
#define ASSIGN_TOUCHSCREEN_BY_VID_PID 0
#define ASSIGN_TOUCHSCREEN_BY_LOCATION 1

#if ASSIGN_TOUCHSCREEN_BY_VID_PID
static const uint16_t TOUCHSCREEN_EXTERNAL_DISPLAY[][2] = {};
#elif ASSIGN_TOUCHSCREEN_BY_LOCATION 
static const char* TOUCHSCREEN_EXTERNAL_DISPLAY[2] = {"usb-ff500000.usb-1.1/input0", "usb-ff500000.usb-1.2/input0"};
#endif
    ALOGV("%s:%u version200 external device name = \"%s\", id = %d, vid:pid = %04x:%04x, device_class = 0x%x",
        __func__, __LINE__, identifier.name.string(), device->id,
        identifier.vendor, identifier.product, device->classes);
        
#elif ASSIGN_TOUCHSCREEN_BY_LOCATION
            if(strcmp(device->identifier.location.string(),TOUCHSCREEN_EXTERNAL_DISPLAY[0]) == 0){
                        ALOGV("The External Screen is running %s",
                        device->identifier.location.string());
                    device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
            }
    char name[128] = {0};
    char location[128] = {0};
    char pid_vid[128] = {0};
    char targetPID_VID[128] = {0};

    ALOGV("*************************CurVersion == 146");
    memset(location, 0, sizeof(location));
    property_get("persist.sys.tpmain.location",location, "NULL");
                    ALOGV("device->identifier.location.string() %s",
                device->identifier.location.string());
    if(strcmp(device->identifier.location.string(),location)==0){
                ALOGV("The Main Screen is touched %s",
                device->identifier.location.string());
        return false;
    }
    property_get("persist.sys.tpext.location",location, "NULL");
    if(strcmp(device->identifier.location.string(),location)==0){
                ALOGV("The External Screen is touched %s",
                device->identifier.location.string());
        return true;
    }

39、Android打开所有日志

Android的编译参数中,加入了-DNDEBUG,也就是默认是no debug的,当然还需要LOG_NDEBUG LOG_NIDEBUG LOG_NDDEBUG这三个宏设置。

当-DNDEBUG被打上后,默认ALOGV会被禁止。

LOG_NDEBUG LOG_NIDEBUG LOG_NDDEBUG这三个宏控制

LOG_PRI(priority, tag, ...)


NDEBUG 是总开关,默认在编译参数中加入

打开ALOGV: #define LOG_NDEBUG 0
打开ALOGI:#define LOG_NIDEBUG 0
打开ALOGD:#define LOG_NDDEBUG 0

打开全部LOG:#undef NDEBUG
  -> Device Drivers                                                                                                                                                                    │
  │       -> Input device support                                                                                                                                                            │
  │         -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])                                                                                                            │
  │ (1)       -> handle angle,accel,compass,gyroscope,lsensor psensor etc (SENSOR_DEVICE [=y])     

40、改变网络优先级

framework/base/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java

    private int getCurrentScore(boolean pretendValidated) {
        // TODO: We may want to refactor this into a NetworkScore class that takes a base score from
        // the NetworkAgent and signals from the NetworkAgent and uses those signals to modify the
        // score.  The NetworkScore class would provide a nice place to centralize score constants
        // so they are not scattered about the transports.

        // If this network is explicitly selected and the user has decided to use it even if it's
        // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
        // selected and we're trying to see what its score could be. This ensures that we don't tear
        // down an explicitly selected network before the user gets a chance to prefer it when
        // a higher-scoring network (e.g., Ethernet) is available.
        // 更改Mobile优先级score
        int curScore = 0;
        if(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
            curScore = SystemProperties.getInt("persist.mobile.score", 50);
            if (VDBG) Log.d(TAG, "LANGO CurMobileScore is :::::" + curScore);
                return curScore;
        }
        // 更改wifi优先级score
        if(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
            curScore = SystemProperties.getInt("persist.wifi.score", 60);
            if (VDBG) Log.d(TAG, "LANGO CurWifiScore is :::::" + curScore);
                return curScore;
        }
        // 更改以太网优先级score
        if(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
            curScore = SystemProperties.getInt("persist.eth.score", 70);
            if (VDBG) Log.d(TAG, "LANGO CurEthScore is :::::" + curScore);
                return curScore;
        }
...
	}

更多推荐

安卓系统开发笔记