精华内容
下载资源
问答
  • 而Windows的窗口化操作也是Windows的一个重要性标志之一。(图片来源于网络)Android,如果拆分开来就是An和Droid,An就是一个。这个一个有几个意思,一个程序不能多开,前台只能运行一个应用等。(微信官方功能,网页...

    Windows本身就是窗户窗口的意思。而Windows的窗口化操作也是Windows的一个重要性标志之一。

    70377054_1

    (图片来源于网络)

    Android,如果拆分开来就是An和Droid,An就是一个。这个一个有几个意思,一个程序不能多开,前台只能运行一个应用等。

    (微信官方功能,网页标签页多开)

    有兴趣的看我之前的一篇文章【实测:一个微信可以同时开多少个窗口】

    后面,谷歌给安卓搞了个MultiDroid。什么微信的网页标签页多开,还有一些软件的多开工具,都是基于这个。

    今天我们讲的是,强行窗口化!

    70377054_3

    这款软件叫【Sky0lin】名字蛮怪的。

    70377054_4

    我们让程序窗口化运行用的就是它,它是一个xposed模块(又是xposed)

    70377054_5

    软件的界面还算可以,MaterialDesign化不够彻底,附带一个悬浮按钮。这个后面说。

    70377054_6

    程序列表就是用于窗口化运行程序的“白名单”。

    70377054_7

    右上角的问号按钮,点击附送一个小提示。。。

    70377054_8添加按钮点击后,也附送一个提示,额,不对,是使用说明。

    70377054_9

    点击任意一个复选框,都会同时勾选两个,但你可以手动反勾选第二个。可以只勾选第一个,但无法只勾选第二个。

    70377054_10

    添加了几个,测试下。

    70377054_11

    有个透明度,没啥用,屏幕旋转就比较有用了。

    注意,右下角有个三角形的按钮。

    70377054_12

    长按右下角的按钮可以隐藏标题栏,隐藏标题栏后,再长按右下角,可以弹出菜单。

    70377054_13

    开个微信吧。。。是不是感觉窗口小了,内容没有变小,显得拥挤放不下。。。

    70377054_14

    主界面更明显。。。

    70377054_15

    输入法无法窗口化。。。

    70377054_16

    QQ也是这样子。。。

    怎么办呢,因为DPI没变。。。软件有这个设置。

    70377054_17

    我们设置成280看下(1080P屏幕一般默认480)。。。

    70377054_18

    实力压缩。太小了。

    70377054_19

    这回我还成320了。这个效果差不多。

    70377054_20

    QQ也正常了。

    70377054_21

    70377054_22

    软件还有其它的设置。

    70377054_23

    这里吐个小槽,作者估计是用自适应格数的TableLayout写的布局?!但没有在子控件添加边距,所以导致了图标“无缝衔接”。

    好了,测试完了,退出窗口化,全屏运行。

    70377054_24

    额,这个什么鬼,DPI的后遗症。。。

    70377054_25

    好吧,我再改回来。。。

    其实,从Android 5.0开始,很多AOSP自带分屏功能,又或是窗口化运行的功能。

    70377054_26

    点击标签栏的方块键

    70377054_27

    70377054_28

    其实,分屏这功能用在手机上基本没太多意义,除非你真的很需要同屏多任务。

    而且,一些分屏功能无法适应输入法。

    其实,我前年就玩过一次分屏。但是不是这个软件,那个软件太坑,是根据Activity判定。跳转活动还会导致窗口出错。

    70377054_29

    而且,界面也很丑,也不支持DPI调节,这里就不讲它了。

    好了,就讲到这里,软件依旧是上传到QQ群99322260群文件里。

    本文结束,谢谢阅读!

    展开全文
  • 应用窗口化 1. 思路 1. 通过主题把整个应用窗口化 2. 通过WindowManager实现悬浮窗方式 2. 实现: 方式一: <style name="Theme.DialogActivity" parent="Theme.AppCompat.Light.Dialog"> <item name=...

    应用窗口化


    1. 思路

    1. 通过主题把整个应用窗口化
    2. 通过WindowManager实现悬浮窗方式

    2. 实现:

    方式一:
        <style name="Theme.DialogActivity" parent="Theme.AppCompat.Light.Dialog">
            <item name="android:windowBackground">@drawable/shape_act_dialog</item>
            <item name="windowActionBar">false</item>
            <item name="windowNoTitle">true</item>
        </style>
    
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <solid android:color="#FFFFFF" />
        <corners android:radius="5dp" />
        <padding
            android:bottom="10dp"
            android:left="10dp"
            android:right="10dp"
            android:top="10dp" />
    </shape>
    
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.zhu.demotest">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.DialogActivity">
            <activity android:name=".MainActivity"
                android:theme="@style/Theme.DialogActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    
    方式二:
    • UiGuardService
    package com.zhu.audiodemo;
    
    import android.app.Notification;
    import android.app.NotificationChannel;
    import android.app.NotificationManager;
    import android.app.Service;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.graphics.PixelFormat;
    import android.os.Build;
    import android.os.IBinder;
    import android.util.Log;
    import android.view.Gravity;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.WindowManager;
    
    import androidx.core.app.NotificationCompat;
    
    import com.zhu.audiodemo.ui.FloatWindowManager;
    
    
    public class UiGuardService extends Service {
    
        private static final String TAG = "UiGuardService";
        private final static int CHANNEL_ID = 100001;
    
        private WindowManager mWindowManager = null;
        private WindowManager.LayoutParams mLayoutParams = null;
        private ViewGroup mViewGroup = null;
        private View view = null;
    
        private final static String ACTION_STOP = "action.com.zhu.stop_service";
    
        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(ACTION_STOP)) {
                    stopForeground(true);
                    stopSelf();
                    Log.d(TAG, "onReceive: stop service ");
                }
            }
        };
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
    
            view = new MainContentView(UiGuardService.this);
    
            IntentFilter intentFilter = new IntentFilter(ACTION_STOP);
            registerReceiver(mReceiver, intentFilter);
    
            // 创建通知栏
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            Notification notification = new NotificationCompat.Builder(this, "123123")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentTitle("录屏")
                    .setContentText(getString(R.string.app_name) + "录屏中")
                    .build();
    
            if (Build.VERSION.SDK_INT >= 26) {
                // 推送通道
                NotificationChannel channel = new NotificationChannel("123123", "通道说明", NotificationManager.IMPORTANCE_DEFAULT);
                notificationManager.createNotificationChannel(channel);
            }
            // 展示前台服务
            startForeground(CHANNEL_ID, notification);
    
    //        int resultCode = intent.getIntExtra("resultCode", -1);
    //        Intent data = intent.getParcelableExtra("data");
    //        if (data != null) {
    //            //do something init
    //        } else {
    //            Log.d(TAG, "onStartCommand: data == null...");
    //        }
    
            showFloatRecordWindow();
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mReceiver);
        }
    
        private void showFloatRecordWindow() {
            FloatWindowManager.showPopupWindow(UiGuardService.this, view);
        }
    }
    
    
    • FloatWindowManager.java
    package com.zhu.audiodemo.ui;
    
    import android.content.Context;
    import android.graphics.PixelFormat;
    import android.view.Gravity;
    import android.view.View;
    import android.view.WindowManager;
    
    public class FloatWindowManager {
        private static final String TAG = "FloatWindowManager";
        private static View mView = null;
        private static WindowManager mWindowManager = null;
        private static Context mContext = null;
        public static Boolean isShown = false;
    
        private final static String ACTION_STOP = "action.com.zhu.stop_service";
    
        public static void showPopupWindow(final Context context, View view) {
    
            if (isShown) {
                // LogUtil.i(LOG_TAG, "return cause already shown");
                return;
            }
            isShown = true;
            // LogUtil.i(LOG_TAG, "showPopupWindow");
            // 获取应用的Context
            mContext = context.getApplicationContext();
            // 获取WindowManager
            mWindowManager = (WindowManager) mContext
                    .getSystemService(Context.WINDOW_SERVICE);
            mView = view;
    
            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
    
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
    
            // 类型
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    
            // 设置flag
            // WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
            params.flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            // 不设置这个弹出框的透明遮罩显示为黑色
            params.format = PixelFormat.TRANSLUCENT;
            // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
            // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
            // 不设置这个flag的话,home页的划屏会有问题
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.x = 30;
            params.y = 30;
            params.gravity = Gravity.START | Gravity.TOP;
            mWindowManager.addView(mView, params);
        }
    
        /**
         * 隐藏弹出框
         */
        public static void hidePopupWindow() {
            //  LogUtil.i(LOG_TAG, "hide " + isShown + ", " + mView);
            if (isShown && null != mView) {
                //  LogUtil.i(LOG_TAG, "hidePopupWindow");
                mWindowManager.removeView(mView);
                isShown = false;
            }
        }
    
    }
    
    
    • MainActivity.java
    package com.zhu.audiodemo;
    
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Intent;
    import android.media.audiofx.AcousticEchoCanceler;
    import android.media.audiofx.AutomaticGainControl;
    import android.media.audiofx.NoiseSuppressor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.provider.Settings;
    import android.util.Log;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "AUDIO-TEST";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //setContentView(R.layout.activity_main);
    
            startFloatingService();
    
    //        boolean mNoiseSuppressor = NoiseSuppressor.isAvailable();
    //        boolean mAutomaticGainControl = AutomaticGainControl.isAvailable();
    //        boolean mAcousticEchoCanceler = AcousticEchoCanceler.isAvailable();
    //        Log.d(TAG, "onCreate: NoiseSuppressor--AutomaticGainControl--AcousticEchoCanceler = " + mNoiseSuppressor + " - " + mAutomaticGainControl + " - " + mAcousticEchoCanceler);
            finish();
        }
    
        public void startFloatingService() {
            Intent intent = new Intent(MainActivity.this, UiGuardService.class);
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT).show();
                startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
            } else {
                startService(new Intent(MainActivity.this, UiGuardService.class));
            }
        }
    
    }
    
    • AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.zhu.audiodemo">
    
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
        <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.AudioDemo">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <service android:name=".UiGuardService"/>
        </application>
    
    </manifest>
    
    展开全文
  • Android插件——加载其他APP页面 1.分析 2.优点 3.详细过程 3.1 标准加载接口 3.2待加载的APP-B 3.3 APP-A 主加载工程 3.3.1 加载工具类PlugManager 3.3.2 页面加载器 ...

    Android插件化——加载其他APP页面

    1.分析

    插件化开发开发时将整个app拆分,包括一个宿主和多个插件,每个插件都是一个apk(组件化每个组件是lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。

    使用场景就是原生APP-A在不安装原生APP-B的情况下加载其页面。

    其实有很多APP都是采用这种方式,比如现在百度一下,烂大街的支付宝加载淘票票。。。那怎么判断是否为插件加载呢?
    1.没有明显过程动画,不需要安装apk。如果采用Intent启动的方式,会有明显过场动画。
    2.判断是否为webview加载。开启:开发者选项——显示边界布局,如果没有代表布局的彩色栅格,即为原生加载。

    2.优点

    • 宿主和插件分开编译。
    • 并行开发,节约时间。
    • 动态更新插件。
    • 按需下载插件模块(第一次下载较慢)。
    • 方法分离为多个APP,避免65535.

    3.详细过程

    废话不多说,边看代码边解释。最后实现场景是APP-A加载APP-B的Activity

    3.1 标准化加载接口

    既然需要A与B能够连接(通信),需要定义一个接口。因为是加载Activity,接口当然就要符合Activity的生命周期。同时,加载布局需要Context的支持,所以也需要Context的注入。
    新建一个lib,创建一个如上接口。(方便调试,所有的lib与module都在一个Project下),文件结构入下图所示:包含Interface的lib路径
    参考代码如下:

    package com.heima.plugs;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    /**
     * 符合加载标准(Activity生命周期 + Context注入)
     */
    public interface PlugInterface {
    
        void onCreate(Bundle saveInstance);
        void onStart();
        void onResume();
        void onRestart();
        void onDestroy();
        void onStop();
        void onPause();
    
        /**
         * 注入context
         * @param context
         */
        void attachContext(Activity context);
    }
    

    3.2待加载的APP-B

    新建module作为单独的被加载APP。
    由于B中的Activity需要被加载,所以选择写一个基类BaseActivity,实现Interface,同时注意注入的Context对象,所以还要重写与上下文对象相关的方法。让工程中Activity继承BaseActivity,方便操作。Module结构入下图所示:在这里插入图片描述
    从图中可以看出,module中除了BaseActivity外,还分别创建了3个Activity等待APP-A加载。当然,这些Activity都继承BaseActivity。
    这时候我们需要注意的是AndroidManifest.xml中的Activity注册信息。AndroidManifest.xml
    注意三个Activity的注册顺序,这个跟下面所讲的在A中加载有关系。这个顺序,跟在A中获得的Activity队列相关。后面会详细描述。

    上代码:

    package com.heima.otherapp;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Intent;
    import android.content.pm.ApplicationInfo;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.view.WindowManager;
    
    import com.heima.plugs.PlugInterface;
    
    public class BaseActivity extends Activity implements PlugInterface {
        public  static  final  String TAG="BaseActivity";
        protected Activity thatActivity;
    
        /**
         * 注入自己的上下文
         * 如果为空 使用父类
         *
         * @param layoutResID
         */
        @Override
        public void setContentView(int layoutResID) {
            if (thatActivity == null) {
                super.setContentView(layoutResID);
            } else {
                thatActivity.setContentView(layoutResID);
            }
        }
    
        @Override
        public void setContentView(View view) {
            if (thatActivity == null) {
                super.setContentView(view);
            } else {
                thatActivity.setContentView(view);
            }
        }
    
        @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            if (thatActivity == null) {
                super.setContentView(view, params);
            } else {
                thatActivity.setContentView(view, params);
            }
        }
    
        @Override
        public LayoutInflater getLayoutInflater() {
            if (thatActivity == null) {
                return super.getLayoutInflater();
            } else {
                return thatActivity.getLayoutInflater();
            }
        }
    
        @Override
        public Window getWindow() {
            if (thatActivity == null) {
                return super.getWindow();
            } else {
                return thatActivity.getWindow();
            }
        }
    
        @Override
        public View findViewById(int id) {
            if (thatActivity == null) {
                return super.findViewById(id);
            } else {
                return findViewById(id);
            }
        }
    
    
        @Override
        public ClassLoader getClassLoader() {
            if (thatActivity == null) {
                return super.getClassLoader();
            } else {
                return getClassLoader();
            }
    
        }
    
        @Override
        public WindowManager getWindowManager() {
            if (thatActivity == null) {
                return super.getWindowManager();
            } else {
                return thatActivity.getWindowManager();
            }
        }
    
    
        @Override
        public ApplicationInfo getApplicationInfo() {
            if (thatActivity == null) {
                return super.getApplicationInfo();
            } else {
                return thatActivity.getApplicationInfo();
            }
        }
    
        @Override
        public void attachContext(Activity context) {
            thatActivity = context;
        }
        
        public void onCreate(Bundle savedInstanceState) { }
        public void onStart() { }
        public void onResume() { }
        public void onRestart() { }
        public void onPause() { }
        public void onStop() {  }
        public void onDestroy() { }
        public void onSaveInstanceState(Bundle outState) { }
    
        public boolean onTouchEvent(MotionEvent event) {
            return false;
        }
    
        public void onBackPressed() {
            if (thatActivity == null) {
                super.onBackPressed();
            } else {
                thatActivity.onBackPressed();
            }
        }
    
        @Override
        public void finish() {
            if (thatActivity == null) {
                super.finish();
            } else {
                thatActivity.finish();
            }
        }
    
        /**
         * 注意上下文对象 thatActivity
         * @param intent
         */
        @Override
        public void startActivity(Intent intent) {
            if (thatActivity == null) {
                super.startActivity(intent);
            } else {
                intent.putExtra("className", intent.getComponent().getClassName());
                thatActivity.startActivity(intent);
            }
        }
        
    }
    
    

    待加载页面:

    /**
     * 待加载app主界面
     * 此app没有安装,仅存.apk文件在内存卡
     */
    public class MainActivity extends BaseActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        public void start(View view) {
            //使用注入的context
            startActivity(new Intent(thatActivity, SecondActivity.class));
        }
    }
    

    其他Activity一样。
    Module完成后,把其push到手机的sdcard中,等待加载。

    3.3 APP-A 主加载工程

    思路:

    • 1.获取读写内存卡权限。
    • 2.通过内存卡路径获取B的相关文件。
    • 3.承载页面。
      文件创建如图所示:
      在这里插入图片描述

    3.3.1 加载工具类PlugManager

    使用DexClassLoader+AssetManager获取外部APK资源。代码中有详细注解:

    package com.heima.teststart;
    
    import android.content.Context;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    import android.util.Log;
    
    import java.io.File;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    /**
     * DexClassLoader加载第三方app
     */
    public class PlugManager {
        public  static  final  String TAG="PlugManager";
        private static PlugManager ourInstance;
        private Context context;
        private DexClassLoader pluginDexClassLoader;
        private Resources pluginResources;
        private PackageInfo pluginPackageArchiveInfo;
        private String entryActivityName;
    
        private PlugManager() { }
    
        public static PlugManager getInstance() {
            if (ourInstance == null) {
                ourInstance = new PlugManager();
            }
            return ourInstance;
        }
    
        public void setContext(Context context) {
            this.context = context.getApplicationContext();
        }
    
        /**
         * 获取Plugin的字节码文件对象
         * 加载外部apk,重写getDexClassLoader()与getResources()
         * @param dexPath Plugin的路径
         */
        public void loadApk(String dexPath) {
            /*  optimizedDirectory   Plugin的缓存路径
             *  libraryPath          可以为null
             *  parent              为父类加载器
             */
            File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
            pluginDexClassLoader = new DexClassLoader(dexPath, dexOutFile.getAbsolutePath(), null, context.getClassLoader());
    
            // 获取包名
            PackageManager packageManager = context.getPackageManager();
            pluginPackageArchiveInfo = packageManager.getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
            //activity集合跟App-B的Manifest中注册的activity有关 顺序也有关
            entryActivityName = pluginPackageArchiveInfo.activities[1].name;
            for (int i=0;i<pluginPackageArchiveInfo.activities.length;i++){
                Log.e(TAG, pluginPackageArchiveInfo.activities[i].name);
            }
    
          /*  实例化resources
            Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) */
            AssetManager assets = null;
            try {
                assets = AssetManager.class.newInstance();
                Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assets, dexPath);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            //获取Plugin的Resources
            pluginResources = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
        }
    
        public PackageInfo getPluginPackageArchiveInfo() {
            return pluginPackageArchiveInfo;
        }
    
        public DexClassLoader getPluginDexClassLoader() {
            return pluginDexClassLoader;
        }
    
        public Resources getPluginResources() {
            return pluginResources;
        }
    
        public String getEntryActivityName() {
            return entryActivityName;
        }
    
    }
    
    

    这里一定要注意pluginPackageArchiveInfo.activities[i].name遍历这个数组你会发现,遍历的顺序跟B中AndroidManifest.xml的Activity注册顺序有关。

    3.3.2 页面加载器 ProxyActivity

    用来承载B中待加载Activity的内容。

    package com.heima.teststart;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.content.res.Resources;
    import android.os.Bundle;
    
    import com.heima.plugs.PlugInterface;
    
    
    /**
     * 承载页---加载第三方activity页面
     */
    public class ProxyActivity extends Activity {
    
        private PlugInterface pluginInterface;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //待启动的第三方Activity
            String className = getIntent().getStringExtra("className");
            try {
                //加载该Activity的字节码对象
                Class<?> aClass = PlugManager.getInstance().getPluginDexClassLoader().loadClass(className);
                //创建该Activity的实例
                Object newInstance = aClass.newInstance();
                //程序健壮性检查
                if (newInstance instanceof PlugInterface) {
                    pluginInterface = (PlugInterface) newInstance;
                    //将代理Activity的实例传递给三方Activity
                    pluginInterface.attachContext(this);
                    //创建bundle用来与三方apk传输数据
                    Bundle bundle = new Bundle();
                    //调用三方Activity的onCreate,
                    pluginInterface.onCreate(bundle);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 重新,通过className拿到类名
         *
         * @return
         */
        @Override
        public ClassLoader getClassLoader() {
            return PlugManager.getInstance().getPluginDexClassLoader();
        }
    
    
        /**
         * 注意:三方调用拿到对应加载的三方Resources
         *
         * @return
         */
        @Override
        public Resources getResources() {
            return PlugManager.getInstance().getPluginResources();
        }
    
        @Override
        public void startActivity(Intent intent) {
            Intent intent1 = new Intent(this, ProxyActivity.class);
           String className = intent.getStringExtra("className");
            //intent1.putExtra("className", intent.getComponent().getClassName());
            intent1.putExtra("className",className);
            super.startActivity(intent1);
        }
    
        @Override
        public void onStart() {
            if (pluginInterface != null)
                pluginInterface.onStart();
            super.onStart();
        }
    
        @Override
        public void onResume() {
            if (pluginInterface != null)
                pluginInterface.onResume();
            super.onResume();
        }
    
        @Override
        public void onPause() {
            if (pluginInterface != null)
                pluginInterface.onPause();
            super.onPause();
        }
    
        @Override
        public void onRestart() {
            if (pluginInterface != null)
                pluginInterface.onRestart();
            super.onRestart();
        }
    
        @Override
        public void onStop() {
            if (pluginInterface != null)
                pluginInterface.onStop();
            super.onStop();
        }
    
        @Override
        public void onDestroy() {
            if (pluginInterface != null)
                pluginInterface.onDestroy();
            super.onDestroy();
        }
    
    }
    
    

    3.3.3 启动页面 MainActivity

    通过PlugManager获取相关内容,传入ProxyActivity进行加载操作。

    package com.heima.teststart;
    
    import android.Manifest;
    import android.content.Intent;
    import android.os.Environment;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            loadApk();
        }
    
        public void loadApk() {
            //使用运行时权限
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
        }
    
        public void startApk(View view) {
            Intent intent = new Intent(this, ProxyActivity.class);
            String otherApkMainActivityName = PlugManager.getInstance().getEntryActivityName();
            intent.putExtra("className", otherApkMainActivityName);
            startActivity(intent);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            PlugManager.getInstance().setContext(this);
            //传入APP-B的绝对路径
            PlugManager.getInstance().loadApk(Environment.getExternalStorageDirectory().getAbsolutePath()+"/otherapp-debug.apk");
        }
    
    }
    
    

    OK,完成。
    附上工程连接,方便下载。GitHub传送门

     

    原文转载自:https://blog.csdn.net/ma598214297/article/details/88630310

    展开全文
  • 一、appium安装 1、nodejs依赖 ...三、Android SDK环境安装 1、安装包 2、配置环境变量(以win10为例) 3、adt-bundle升级 4、配置adb、aapt环境变量 四、Pycharm环境准备 1、安装Pycharm环境 1.1、通...

    一、appium安装

    1、nodejs依赖

    2、.net framework

    3、Appium桌面程序安装

    3.1、安装包获取

    3.2、安装过程

    二、Java_ jdk安装

    1、Java_jdk版本说明

    2、下载地址

    3、Java_jdk安装步骤

    4、配置环境变量(以win10为例)

    三、Android SDK环境安装

    1、安装包

    2、配置环境变量(以win10为例)

    3、adt-bundle升级

    4、配置adb、aapt环境变量

    四、Pycharm环境准备

    1、安装Pycharm环境

    1.1、通过pip安装

    1.2、在Pycharm中安装

    一、appium安装

    1、nodejs依赖

    nodejs是JavaScript的一个编程框架,appium是通过这个框架搭建起来的。当appium的版本在1.11以下时,需要先安装依赖nodejs。一般1.11版本以上的appium不需要安装nodejs,通常先安装appium,如果安装出错可以再安装nodejs依赖。

     

     

     

     

     

    Nodejs下载地址:http://nodejs.cn/download/,选择对应的安装包。

    安装时点击“next”即可,安装完后在CMD中运行“node --version”可显示版本信息,表示安装成功。

    2、.net framework

    Windows用户在安装nodejs依赖时可能会出错,如果出错需要先安装“net4.5.1.exe”,再安装nodejs依赖。一般只有Windows用户才会遇到这个问题。

    3、Appium桌面程序安装

    3.1、安装包获取

    Appium的官方网站https://appium.io/ (建议使用谷歌浏览器打开)进入可以下载。

     

     

     

     

     

     

     

     

     

     

     

    点击“Download”进入之后只显示最新版本的下载,需要先点击“Tags”,然后点击“Releases”就会显示其他的appium版本。

     

     

     

     

     

     

     

     

     

    有很多appium版本都有“Pre-release”标签,表示预发布版本。一般版本不稳定时才会有这个标签,所以尽量下载没有“Pre-release”标签的版本。

     

     

     

     

     

     

     

    选择版本后点击“Assets”下拉框,下载所需系统的安装包。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    3.2、安装过程

    安装第一步时,自己的电脑可以选择(所有的用户),公司的电脑最好选择“仅为我安装”。一般安装时都选择“仅为我安装”,避免运行appium时因权限报错。

     

     

     

     

     

     

     

     

     

     

     

    Appium的安装路径是默认的,不能自定义安装。默认安装路径一般为:C:\Users\DELL\AppData\Local\Programs\Appium(因电脑而异)

    如果安装的appium不是最新的版本,新版本又不稳定时,需要关闭更新。

     

     

     

     

     

     

     

     

     

     ### Tips:如果想卸载,担心在C盘卸载不干净,推荐一款专业卸载工具:geek uninstaller。下载地址:https://geekuninstaller.com/ 

    二、Java_ jdk安装

    1、Java_jdk版本说明

    Java_jdk必须是1.8(Java8)以上,且是64位的,32位的运行会出问题,尽量下载高版本。

    2、下载地址

    官网是国外源下载很慢,可以使用国内源下载。国内源(华为镜像):https://mirrors.huaweicloud.com/java/jdk/   

    下图以“jdk-8u202-windows-x64”为例:

     

     

     

     

     

     

     

    3、Java_jdk安装步骤

    下载完成后,双击进行安装,如下图所示:

     

     

     

     

     

     

     

     

     

     

     

     

     

    选择安装路径,最好保持默认安装路径,修改路径的在设置环境变量时要相应设置正确。我的安装路径是:E:\Program Files\Java\jdk1.8.0_202

     

     

     

     

     

     

     

     

     

     

     

     

    安装好后跳转的弹窗点击“确定”,然后就是安装jre的界面,同样最好保持默认安装路径,修改路径的在设置环境变量时要相应设置正确。我的安装路径是:E:\Program Files\Java\jre1.8.0_202

     

     

     

     

     

     

     

     

     

     

    点击“下一步”,最后点击“关闭”即安装完成。

     

     

     

     

     

     

     

     

     

    4、配置环境变量(以win10为例)

    设置环境变量的界面: “此电脑”右键-->属性-->高级系统设置-->高级-->环境变量-->系统变量-->新建

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     在“新建系统变量”中,变量名为:JAVA_HOME,变量值为:JDK的安装路径。

     

     

     

     

     

     

     

     

     

     

     

    新建一个CLASSPATH变量,在“新建系统变量”中,变量名为:CLASSPATH,变量值为:  .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar  (注意最前面的小点)

     

     

     

     

     

     

     

    在系统变量中找到“path”,然后点击“编辑”,新建一个 %JAVA_HOME%\bin   

    再次点击“新建”,创建一个 %JAVA_HOME%\jre\bin最后点击“确定”退出设置。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    在CMD中运行“java -version”,得到java的版本信息,表示安装设置成功。

     

     

     

     

     

     

     

    三、Android SDK环境安装

    1、安装包

    1.1、推荐使用安卓开发新手集成包: adt-bundle  (谷歌给安卓开发打包的一个环境)。 安装包的下载地址:http://tools.android-studio.org/index.php/adt-bundle-plugin。  通过链接下载的安装包一般只支持Android 4.0,需要手动更新。更新过程会经常出错,因为网络等原因,最好使用提供的版本。

    1.2、还可以使用android studio,是谷歌推出的一个Android集成开发工具,提供了集成的 Android 开发工具用于开发和调试。这个工具的安装以及环境配置很复杂,没经验的人先用adt-bundle工具

    2、配置环境变量(以win10为例)

    配置方法与JDK的一致。设置环境变量的界面: “此电脑”右键-->属性-->高级系统设置-->高级-->环境变量-->系统变量-->新建

    环境配置是使用的新手集成包adt-bundle

    在“新建系统变量”中,变量名为:ANDROID_HOME,变量值为Android SDK的安装路径。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    在系统变量中找到“path”,然后点击“编辑”,新建 %ANDROID_HOME%\platform-tools%ANDROID_HOME%\build-tools\28.0.3

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    3、adt-bundle升级

    Android系统在不断升级,所以安卓开发新手集成包adt-bundle也需要不断升级,但是因升级环境受限制,经常更新失败,需要经常尝试升级。

     

     

     

     

     

     

     

     

     

     

     

    4、配置adb、aapt环境变量

    adt-bundle集成了adb、aapt, adb存放在platform-tools目录下面,aapt存放在build-tools\28.0.3。

    第2步中,在“PATH”中配置%ANDROID_HOME%\platform-tools%ANDROID_HOME%\build-tools\28.0.3 就是分别部署adb、aapt的环境。

     

     

     

     

     

     

     

     

     

     确认adb安装完成,在CMD中运行adb version,结果如下图:

     

     

     

     

     

     

     

    确认aapt安装完成,在CMD中运行aapt不报错即可。

     

     

     

     

     

     

     

    四、Pycharm环境准备

    1、安装Pycharm环境

    Appium与Python连接需要安装Pycharm绑定包,安装方式有两种:

    1.1、通过pip安装

    进入CMD命令提示符窗口中,输入:pip install Appium-Python-Client

    1.2、在Pycharm中安装

    在Pycharm中按路径:File-->Settings-->Project interpreter

    进入安装窗口,搜索安装Appium-Python-Client。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     Tips:

          在第三步中提到的安卓开发集成包:adt-bundle, 如果升级多次不成功,可以联系QQ:328546246索取最新版本的adt-bundle。

    展开全文
  • 使用我的此代码,您可以在活动的初始时间显示任何类型的视图的弹出窗口.import android.app.Activity;import android.os.Bundle;import android.util.Log;import android.view.LayoutInflater;import android.view....
  • App分类Native App原生App优点直接依托于操作系统,交互性最强,性能最好,功能最为强大缺点开发成本高,更新缓慢,审核周期慢,维护成本高Hybrid App混合型App优点开发成本较低,可以跨平台,调试方便,维护成本低...
  • 1、滑动 使用场景:目标元素不在当前屏幕范围,需要滑动。 解决方法:模拟滑动。 Appium WebDriver的swipe方法:driver.swipe(x1,y1,x2,y2,time) swipe几个参数分别为:起点x,起点y,终点x,终点y,滑动需要的时间(单位...
  • import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; ...
  • Android App Bundle 是一种发布格式(并不是可安装文件),文件后缀名为 `.aab`,是一个压缩文件,其中包含了应用的所有经过编译的代码和资源,APK文件的生成和签名都由 Google Play 完成。Google Play 在使用 App ...
  • 卸载Android应用程序时,经常会弹出网页,做些用户数据的调查统计。Android是可以获取到其它应用被卸载掉这个事件,但是并不能获取自身被卸载这个事件。从纯粹的java层是没法实现的,本文介绍一种方法来实现它。原理...
  • android app冷启动优化

    2021-06-07 04:42:45
    二、应用启动流程在安卓系统上,应用在没有进程的情况下,应用的启动都是这样一个流程:当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建...
  • 一、环境搭建:环境变量不生效1、首先,在系统变量里添加变量名为:ANDROID_HOME,值为:D:\android-sdk2、然后,在系统变量里,在已有的Path变量的值里后面添加:D:\android-sdk\build-tools\22.0.1,记得在前面...
  • 安卓app逆向破解脱壳教程

    千次阅读 2021-01-19 01:16:32
    From:Hook 神器家族的 Frida 工具使用详解:... ... APP逆向神器之Frida【Android初级篇】:https://www.jianshu.com/p/2d755beb1c54 frida 官网文档:https://fr..
  • 下面说一下Android如何通过asp.net向服务器提交数据,我们此问题分成两块,第一块是asp.net,第二块是Android app。  asp.net:  1.首先Visual Studio打开或新建一个asp.net网站  2.在解决方案资源管理器窗口中...
  • 62001,然后执行adb devices,看到如下信息就表示成功了 5 在模拟器里安装测试apk文件,这里以一个计算器app(点击下载)为例,直接将apk文件拖到模拟器里即可 6 继续在刚刚的控制台窗口里输入aapt dump badging D:...
  • app图形编程近几天自己使用arduino nano板子仿做了一个4足机器人(如下图),由于...奈何安卓app开发功底差,电脑上装的Android Studio大半年没打开过。四足机器人偶然间百度到一个图形化安卓app开发环境,打开一看...
  • 目录安装电脑客户端配置客户端启动服务连接设备中的APP认识界面提供的功能开始连接制作脚本认识界面提供的功能基本制作步骤执行脚本以Android Studio和生成Java -JUnit工具的脚本为例脚本内容简化版和可执行版对比的...
  • 我就是重点大学毕业,大学学的是Java,我个人比较乐于学习于是自学Android一年。趁着这次疫情,大洗牌我凭借天生优势,——聪明的脑袋以及自己不断地刻苦的学习,在一众高手之中成功脱颖而出。 现在网上都喜欢这么搞...
  • 允许安装在发射器的快捷方式的应用程序, 允许应用程序卸载启动的快捷方式, 允许应用程序打开网络套接字, 允许程序访问有关网络的信息, 允许应用程序创建一个使用类型的窗口 TYPE_SYSTEM_ALERT,所有其他应用程序的...
  • 当我在调试模式下运行时,我似乎无法到达服务内部的任何断点,这是为什么呢?... 标签:android-appwidget,android-service,android-widget,android 来源: https://codeday.me/bug/20191102/1994784.html
  • 1、产生原因其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了window窗口背景,黑屏白屏就是window窗口背景。示例:2、解决办法通过设置设置Style(1)设置背景图Theme通过设置一张背景图。 当...
  • 文章可随意转载,但务必注明源地址发现当前Android的资料不是很多,而且对于Activity的介绍也很少,所以把官方文档的android.app.Activity的介绍翻译了一下,加入了一些自己的理解。各位如果觉得我自己理解的不对,...
  •  所以Android里还提供了用xml文件,用可视的方式来设计窗口布局.首先还是先用向导创建一个”Add No Activity”的项目, 然后菜单”File” –> “New” –> “Class”创建一个窗口类:/* MyActivity.java */p....
  • 安卓APP使用CH340进行串口通信

    千次阅读 2021-02-09 18:11:03
    初始流程3. 收发流程编写串口通信APP1. 导入lib库2. 布局3. 编写java代码4. 测试5. USB插拔检测附代码 缘由 毕业设计要求使用手机APP与单片机硬件进行有线通信,至于为什么不用蓝牙、无线,我也不知道 ????。有线...
  • Android中的应用进程可以分为两种:一种是“体验型”进程,一种是“性能型”进程。“体验型”进程即可以与用户进行交互的进程,“性能型”进程则为用户提供功能但是用户看不见界面的进程,他们之间是通过优先级进行...
  • 简单解释一下:android:alwaysRetainTaskState顾名思义就是【总是保留任务栈状态】Task指的是任务栈,是用于记录Activity打开顺序、保存状态等。如上图,打开客户端的顺序是 SplashActivity --> GuideActivity --...
  • 本文实例讲述了Android Appwidget用法。分享给大家供大家参考,具体如下:App Widgets 是一个小型应用程序的View 他可以嵌入到其他应用程序中(如 桌面程序) 并且可以得到周期性刷新。在创建App Widget之前需要了解...
  • 问题进程在前台时没问题,悬浮窗可以开关,进程转到后台以后就不行,即栈顶是其他应用的时候,悬浮窗弹不出,回到自己 app 查看,发现悬浮窗只显示在自己的 app 里面,无法在最顶层绘制。失败尝试( x )打开前台服务...
  • android 国际

    2021-03-17 16:33:20
    android 国际国际I18n简介实现要点1要点2国际案例预览 国际I18n简介 i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际”的简称。在资讯领域,国际(i18n)指让...
  • 安卓studio在使用小窗口时,如果我们点击取消了窗口的docked mode模式,窗口就会变成,你一旦触发窗口以外的区域,窗口就会龟缩回去。此时,如果你想要恢复回原来的docked mode的话,具体步骤是:1、选中要恢复的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,685
精华内容 19,074
关键字:

安卓app窗口化